Контекст динамической упаковки SolidJs не загружается перед его дочерними элементами

сообщество! Это мой первый вопрос по StackOverflow, так как на данный момент мне удалось найти большинство ответов, но найти эту проблему очень сложно. Я мог видеть подобные вопросы, но это не решает моего намерения и не позволяет сосредоточиться на решении конкретной проблемы, а не на ответе на фундаментальный вопрос. (Я нашел подобную проблему в Solid github, но, похоже, ее не просматривали многие люди, и только один ответ говорит, что это невозможно)

Я хочу заполнить контекст после загрузки дочерних элементов. У меня есть ParentComponent, который будет содержать ContextProvider, и контекст будет передан.

import './App.css';
import { createContext, useContext, splitProps, createSignal, For, children as Children } from 'solid-js';
export const UserContext = createContext(0);

function App() {
    return (
        <>
            <UserContext.Provider value = {3}>
                <ParentComponent>
                    <Child />
                    <Child />
                    <Child />
                </ParentComponent>
            </UserContext.Provider>
        </>
    );
}

export default App;

export const ParentComponent = props => {
    console.info('parent');
    return (
        <For each = {Children(() => props.children)()}>
            {(child, idx) => {
                console.info('child in for:' + idx());
                return <UserContext.Provider value = {idx}>{child}</UserContext.Provider>;
            }}
        </For>
    );
};

export const Child = () => {
    const idx = useContext(UserContext);
    console.info('child', idx);
    return <div>I am child {idx}</div>;
};

я ожидал увидеть это.

I am child 0
I am child 1
I am child 2

но на самом деле я вижу вот это.

I am child 3
I am child 3
I am child 3

Я добавил console.info, чтобы увидеть, какой из них загружается первым, и в консоли я вижу следующее:

parent
child 3
child 3
child 3
child in for:0
child in for:1
child in for:2

Как видите, после загрузки родительского элемента дочерние элементы загружаются до логики поставщика контекста. и он никогда не перезагружается. что делает невозможным передачу информации ребенку.

Есть ли способ контролировать приоритет загрузки? Или это ошибка Solidjs?

поскольку я ожидаю этого, хотя дочерние элементы визуализируются первыми, как только задано значение контекста, дочерние элементы необходимо визуализировать еще раз, поскольку его значение изменяется. Но похоже, что он больше не рендерится. Я пытался заставить idx сигнализировать, но все равно безуспешно.

Спасибо!

Я пытаюсь передать дополнительные значения в дочерние компоненты не через реквизит, а через контекст. его значение в контексте выбирается на основе его индекса. Какое бы значение ни было передано из контекста дочерним элементам, дочерние элементы должны выполнить повторный рендеринг, чтобы обработать вновь полученные данные из контекста.

================

чтобы избежать путаницы, я добавлю больше контекста о том, чего я хочу достичь. В приведенном ниже примере представлен сложный поток данных. И я ожидаю передать ширину 1,2,3 от дочерних элементов в передаваемом компоненте приложения. Но результат говорит, что у него нет существующих реквизитов (ширина = 3,2,1).

import './App.css';
import { createContext, useContext, createEffect, createSignal, For, children as Children } from 'solid-js';
export const UserContext = createContext(0);

function App() {
    return (
        <>
            <ParentComponent>
                <Child width = {3} />
                <Child width = {2} />
                <Child width = {1} />
            </ParentComponent>
        </>
    );
}

export default App;

export const ParentComponent = props => {
    return (
        <For each = {Children(() => props.children)()}>
            {(child, idx) => {
                // I made up below child.props. cannot find any way to pass existing props (width)
                return (
                    <UserContext.Provider value = {idx}>
                        <Dynamic component = {Child} {...child.props} idx = {idx} />
                    </UserContext.Provider>
                );
            }}
        </For>
    );
};

export const Child = props => {
    const idx = useContext(UserContext);
    return (
        <div>
            I am child {props.idx ?? 'no-value'} with width {props.width ?? 'no-value'} and idx from context {idx}
        </div>
    );
};


результат:

I am child 0 with width no-value and idx from context 0
I am child 1 with width no-value and idx from context 1
I am child 2 with width no-value and idx from context 2

А если я использую дочерний из дочерних элементов, то реквизит и значение контекста не получены.

export const ParentComponent = props => {
    return (
        <For each = {Children(() => props.children)()}>
            {(child, idx) => {
                const c = Children(() => child); // is there a way to pass existing child's props?
                return (
                    <UserContext.Provider value = {idx}>
                        <Dynamic component = {c} idx = {idx} />
                    </UserContext.Provider>
                );
            }}
        </For>
    );
};

результат:

I am child no-value with width 3 and idx from context 0
I am child no-value with width 2 and idx from context 0
I am child no-value with width 1 and idx from context 0

Что мне НУЖНО, так это либо данные реквизиты из дочернего контекста, либо реквизиты, данные от родителя. В React я могу сделать это с помощью cloneElement, но в Solidjs я не могу найти аналогичную функциональность.

Спасибо, что обратили внимание на этот вопрос.

Наличие динамического контента не меняет результата, если это правильный компонент. Не ждите, что это как-то повлияет на результат. Solid предоставляет собственный динамический компонент. docs.solidjs.com/reference/comComponents/dynamic Я вижу, что вы мне не доверяете, но я знаю, что говорю. Я хорошо знаю кодовую базу и логику рендеринга.

snnsnn 22.07.2024 10:05

@snnsnn Я ценю внимание к моей проблеме. Я знаю, что вы, возможно, хорошо знакомы с Solidjs, но, пожалуйста, поймите, что мой вариант использования — непростая проблема. Я уже пробовал Dynamic, но похоже, что это заставляет меня использовать новый Node из самого компонента, не перенося существующую дочернюю информацию, которая содержит собственные реквизиты пользователя (моей библиотеки). Я обновлю код, чтобы уменьшить путаницу.

Songhyeon Jun 23.07.2024 01:07
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
59
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Это ожидаемое поведение, поскольку Solid позволяет вам контролировать порядок выполнения, используя структуру компонента + методы получения. Другими словами, вам решать сначала выполнить дочерние элементы или сначала родительские, хотя немного сложно понять логику выполнения, поскольку скомпилированный код создает замыкание и вызывает методы рендеринга, которые вызывают эффекты рендеринга, которые будут выполняться планировщиком. .

Однажды я открыл проблему на эту же тему, думая, что отсутствие детерминированного порядка рендеринга — это ошибка. Подробнее об этом можно прочитать: https://github.com/solidjs/solid/issues/1997

Вы можете увидеть ответ о том, как его решить: https://stackoverflow.com/a/76995880/7134134

Поставщик контекста — это обычный компонент. Как только вы поймете, как отображаются компоненты, вы сможете контролировать порядок их выполнения. Вы можете узнать больше о Context API: https://stackoverflow.com/a/74492899/7134134

PS: ОП отметил свой ответ как принятый в предоставленном ответе. Вам нужно прочитать связанный ответ. Это точно такая же проблема. Поставщик контекста выполняется первым, поэтому вы получаете значение undefined. Возврат поставщика контекста и использование его в качестве дочернего компонента дают разные результаты.

Я вижу, что ваша цель — установить значение контекста внутри компонента For, но это вынуждает вас вернуть компонент поставщика, что дает другой результат.

Правильный способ предоставления значения контекста — сохранить получателя в качестве дочернего элемента:

 <UserContext.Provider value = {1}><Child /></UserContext.Provider>

Проблема с вашим кодом заключается в том, что порядок выполнения меняется несколько раз во время выполнения дерева компонентов, что приводит к результату, который трудно обосновать.

Вот что выдает ваш код:

import { template as _$template } from "solid-js/web";
import { insert as _$insert } from "solid-js/web";
import { createComponent as _$createComponent } from "solid-js/web";
var _tmpl$ = /*#__PURE__*/_$template(`<div>I am child `);
import { For, createContext, useContext } from 'solid-js';
import { render } from 'solid-js/web';
export const UserContext = createContext(0);
const ParentComponent = props => {
  console.info('Parent');
  return _$createComponent(For, {
    get each() {
      return props.children;
    },
    children: (child, idx) => {
      console.info('child in for:' + idx());
      return _$createComponent(UserContext.Provider, {
        get value() {
          return idx();
        },
        children: child
      });
    }
  });
};
const Child = () => {
  const idx = useContext(UserContext);
  console.info('child', idx);
  return (() => {
    var _el$ = _tmpl$(),
      _el$2 = _el$.firstChild;
    _$insert(_el$, idx, null);
    return _el$;
  })();
};
function App() {
  console.info('App');
  return _$createComponent(UserContext.Provider, {
    value: 0,
    get children() {
      return _$createComponent(ParentComponent, {
        get children() {
          return [_$createComponent(Child, {}), _$createComponent(Child, {}), _$createComponent(Child, {})];
        }
      });
    }
  });
}
render(() => _$createComponent(App, {}), document.body);

Живая демоверсия: https://playground.solidjs.com/anonymous/79dd68ec-79bc-45f7-9076-464fe74d45ae

Вот как вы можете это сделать:

import { type Component, For, type JSXElement, createContext, useContext } from 'solid-js';
import {render } from 'solid-js/web';

export const UserContext = createContext(0);

const ParentComponent: Component<{ children: Array<JSXElement> }> = props => {
  console.info('Parent');
  return (
    <For each = {props.children}>
      {(child, idx) => {
        console.info('child in for:' + idx());
        return child
      }}
    </For>
  );
};

const Child: Component<any> = () => {
  const idx = useContext(UserContext);
  console.info('child', idx);
  return <div>I am child {idx}</div>;
};


function App() {
  console.info('App');
  return (
    <ParentComponent>
      <UserContext.Provider value = {1}><Child /></UserContext.Provider>
      <UserContext.Provider value = {2}><Child /></UserContext.Provider>
      <UserContext.Provider value = {3}><Child /></UserContext.Provider>
    </ParentComponent>
  );
}
render(() => <UserContext.Provider value = {0}><App /></UserContext.Provider>, document.body);

Живая демо: https://playground.solidjs.com/anonymous/2197fa8f-fbe0-40f5-a18c-deb54280bb7e

Скомпилированный код для приведенного выше примера:

import { template as _$template } from "solid-js/web";
import { insert as _$insert } from "solid-js/web";
import { createComponent as _$createComponent } from "solid-js/web";
var _tmpl$ = /*#__PURE__*/_$template(`<div>I am child `);
import { For, createContext, useContext } from 'solid-js';
import { render } from 'solid-js/web';
export const UserContext = createContext(0);
const ParentComponent = props => {
  console.info('Parent');
  return _$createComponent(For, {
    get each() {
      return props.children;
    },
    children: (child, idx) => {
      console.info('child in for:' + idx());
      return child;
    }
  });
};
const Child = () => {
  const idx = useContext(UserContext);
  console.info('child', idx);
  return (() => {
    var _el$ = _tmpl$(),
      _el$2 = _el$.firstChild;
    _$insert(_el$, idx, null);
    return _el$;
  })();
};
function App() {
  console.info('App');
  return _$createComponent(ParentComponent, {
    get children() {
      return [_$createComponent(UserContext.Provider, {
        value: 1,
        get children() {
          return _$createComponent(Child, {});
        }
      }), _$createComponent(UserContext.Provider, {
        value: 2,
        get children() {
          return _$createComponent(Child, {});
        }
      }), _$createComponent(UserContext.Provider, {
        value: 3,
        get children() {
          return _$createComponent(Child, {});
        }
      })];
    }
  });
}
render(() => _$createComponent(UserContext.Provider, {
  value: 0,
  get children() {
    return _$createComponent(App, {});
  }
}), document.body);

Я прочитал все связанные статьи и до сих пор не думаю, что это та же самая проблема. В моем примере я вообще не уничтожил реквизит, а просто перебрал детей. Это упрощенный пример, поэтому мне интересно, есть ли обходной путь, позволяющий визуализировать дочерние элементы после указания значения контекста. В моем реальном случае использования этот порядок рендеринга детей после установки значения контекста имеет важное значение.

Songhyeon Jun 22.07.2024 06:21

ОП отметил свой ответ как принятый. Вам нужно прочитать связанный ответ. Это точно такая же проблема. Поставщик контекста выполняется первым, поэтому вы получаете неопределенное значение. Возврат поставщика контекста и использование его в качестве дочернего компонента дают разные результаты.

snnsnn 22.07.2024 09:52

Привет, Снснсн. это не та же самая проблема. Это не только проблема с поставщиком контекста, но и проблема с обработкой детей. И мой пример не становится неопределенным, то есть он получает значение из значения по умолчанию. Не могли бы вы попробовать код и вернуть ожидаемое значение в моем примере? Вы увидите, что это другая проблема. Связанный элемент не имеет нескольких дочерних элементов в примере, который обрабатывает индекс дочерних элементов. Я попробовал это и уверен, что это другая проблема.

Songhyeon Jun 22.07.2024 15:49

@SonghyeonJun Я улучшил свой ответ, надеюсь, он поможет. Как я уже говорил, возврат поставщика неверен, вам следует обернуть компонент напрямую. Я считаю, что логика рендеринга Solid содержит ошибки и подвержена ошибкам, но, как было опубликовано по этой проблеме, они говорят, что это сделано намеренно. Я считаю, что это случайная сложность, потому что Solid пытается во многом помочь, сохраняя при этом реактивность.

snnsnn 22.07.2024 21:49

Спасибо за ответ, но новый код, которым вы делитесь, — это не то, что мне нужно. В основе моего кода лежит предположение, что количество детей неизвестно. Таким образом, я не могу создавать поставщиков вручную. У дочерних элементов будут свои собственные реквизиты, но некоторые значения реквизитов от родителя должны автоматически передаваться дочерним элементам через контекст или каким-либо другим способом. Я создаю пользовательскую библиотеку и не могу заставить пользователей создавать провайдера вручную таким образом.

Songhyeon Jun 23.07.2024 00:39

мой вариант использования на самом деле передает установщик в родительском компоненте дочерним элементам, и дети должны снова передать его реквизиты родительскому элементу с его динамически генерируемым индексом. Таким образом, родительский компонент соберет все данные для создания одного объединенного объекта, который будет использоваться для рендеринга фактического элемента DOM. Эта структура важна, поскольку количество дочерних элементов и ее реквизиты используются для расчета целых размеров как соотношения в моей библиотеке. @snnsnn

Songhyeon Jun 23.07.2024 00:43

моя конечная цель — сделать что-то подобное. ``` <Parent width = {200}> <Child width = {1}/> <Child width = {2}/> <Child width = {3}/> </Parent> ``` и родительский элемент вычислит соотношение дочерних элементов и повторно отрегулируйте их в зависимости от их соотношения по ширине родительского элемента. Я могу заставить этих дочерних элементов «проталкивать» данные, но я хочу сохранить идемпотентное поведение структуры данных, поэтому ширина одного из дочерних компонентов обновляется, это не повреждает результат родительского компонента.

Songhyeon Jun 23.07.2024 00:47

Похоже, для этого вам не нужен контекст. Контекст хорош для контекстных значений, значений, которые вам нужно перезаписать по мере углубления в дерево компонентов, например значений темы и т. д. Если все это предназначено для манипуляций с DOM, используйте ссылки, в противном случае используйте обычную цепочку областей и реактивные значения. В отличие от React, компоненты Solid могут получить доступ к области действия своего родителя. Если вам нужны вычисления, связанные с DOMRect, такие как вставка элементов и настройка их размера или положения, просто напишите конкретный компонент для этой задачи и запустите необработанные манипуляции с DOM над этим изолированным кодом.

snnsnn 23.07.2024 04:48

Я думаю, возможно, SolidJs не предназначен для манипулирования данными, если он не имеет структуры DOM, если то, что вы описываете, верно. В моем примере DOM не создается до тех пор, пока не будет создан весь поток данных. Интерес представляет поток передачи данных из структур элементов. Мне нужен поток данных: дочерние реквизиты -> родительские реквизиты -> расчет -> создать DOM на основе данных. React поддерживает этот поток данных, если вы передаете метод установки дочерним элементам. Но в SolidJS я не вижу хорошего способа взаимодействия с таким потоком данных.

Songhyeon Jun 23.07.2024 06:46
Ответ принят как подходящий

Я задал вопрос в репозитории Solid на GitHub (https://github.com/solidjs/solid/discussions/2233) и получил ответ о том, как решить эту проблему.

Быстрый ответ на обходной путь — использование этого шаблона:

  1. Дочерний компонент просто возвращает объект реквизита вместо элемента DOM, например null. Вместо того, чтобы пытаться получить некоторые реквизиты от дочернего «элемента», дочерний элемент сам будет реквизитом или любым другим объектом, возвращаемым Child.
  2. родитель будет иметь полный доступ к детям. используя эту информацию, мы можем создавать совершенно новых детей.

код будет таким:

import { createContext, For, children as Children } from 'solid-js';
export const UserContext = createContext(0);

function App() {
    return (
        <>
            <ParentComponent width = {100}>
                <Child width = {5}>child1</Child>
                <Child width = {3}>child2</Child>
                <Child width = {2}>child3</Child>
            </ParentComponent>
        </>
    );
}

export default App;

export const ParentComponent = props => {
    const c = Children(() => props.children).toArray();
    const totalWidths = c.reduce((acc, child) => acc + child.width, 0);

    return (
        <For each = {c}>
            {(child, idx) => {
                const width = (child.width * props.width) / totalWidths; 
                return (
                    <ChildRender idx = {idx} width = {width}>
                        {child.children}
                    </ChildRender>
                );
            }}
        </For>
    );
};

export const Child = props => {
    return props;
};

const ChildRender = ({ width, idx, children }) => {
    return (
        <div>
            Child {idx} width {width} - {children}
        </div>
    );
};

и я вижу ожидаемый результат

Child 0 width 50 - child1
Child 1 width 30 - child2
Child 2 width 20 - child3

использование контекста также работает с этим шаблоном:

import './App.css';
import { createContext, useContext, For, children as Children } from 'solid-js';
export const UserContext = createContext({ width: 0, idx: -1 });

function App() {
    return (
        <>
            <ParentComponent width = {100}>
                <Child width = {5}>child1</Child>
                <Child width = {3}>child2</Child>
                <Child width = {2}>child3</Child>
            </ParentComponent>
        </>
    );
}

export default App;

export const ParentComponent = props => {
    const c = Children(() => props.children).toArray();
    const totalWidths = c.reduce((acc, child) => acc + child.width, 0);

    return (
        <For each = {c}>
            {(child, idx) => {
                const width = (child.width * props.width) / totalWidths; 
                return (
                    <UserContext.Provider value = {{ width, idx }}>
                        <ChildRender>{child.children}</ChildRender>
                    </UserContext.Provider>
                );
            }}
        </For>
    );
};

export const Child = props => {
    return props;
};

const ChildRender = ({ children }) => {
    const { width, idx } = useContext(UserContext);
    return (
        <div>
            Child {idx} width {width} - {children}
        </div>
    );
};

спасибо за @snnsnn и СаргуэльУнда !

Songhyeon Jun 25.07.2024 04:34

Другие вопросы по теме