сообщество! Это мой первый вопрос по 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 я не могу найти аналогичную функциональность.
Спасибо, что обратили внимание на этот вопрос.
@snnsnn Я ценю внимание к моей проблеме. Я знаю, что вы, возможно, хорошо знакомы с Solidjs, но, пожалуйста, поймите, что мой вариант использования — непростая проблема. Я уже пробовал Dynamic, но похоже, что это заставляет меня использовать новый Node из самого компонента, не перенося существующую дочернюю информацию, которая содержит собственные реквизиты пользователя (моей библиотеки). Я обновлю код, чтобы уменьшить путаницу.
Это ожидаемое поведение, поскольку 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);
Я прочитал все связанные статьи и до сих пор не думаю, что это та же самая проблема. В моем примере я вообще не уничтожил реквизит, а просто перебрал детей. Это упрощенный пример, поэтому мне интересно, есть ли обходной путь, позволяющий визуализировать дочерние элементы после указания значения контекста. В моем реальном случае использования этот порядок рендеринга детей после установки значения контекста имеет важное значение.
ОП отметил свой ответ как принятый. Вам нужно прочитать связанный ответ. Это точно такая же проблема. Поставщик контекста выполняется первым, поэтому вы получаете неопределенное значение. Возврат поставщика контекста и использование его в качестве дочернего компонента дают разные результаты.
Привет, Снснсн. это не та же самая проблема. Это не только проблема с поставщиком контекста, но и проблема с обработкой детей. И мой пример не становится неопределенным, то есть он получает значение из значения по умолчанию. Не могли бы вы попробовать код и вернуть ожидаемое значение в моем примере? Вы увидите, что это другая проблема. Связанный элемент не имеет нескольких дочерних элементов в примере, который обрабатывает индекс дочерних элементов. Я попробовал это и уверен, что это другая проблема.
@SonghyeonJun Я улучшил свой ответ, надеюсь, он поможет. Как я уже говорил, возврат поставщика неверен, вам следует обернуть компонент напрямую. Я считаю, что логика рендеринга Solid содержит ошибки и подвержена ошибкам, но, как было опубликовано по этой проблеме, они говорят, что это сделано намеренно. Я считаю, что это случайная сложность, потому что Solid пытается во многом помочь, сохраняя при этом реактивность.
Спасибо за ответ, но новый код, которым вы делитесь, — это не то, что мне нужно. В основе моего кода лежит предположение, что количество детей неизвестно. Таким образом, я не могу создавать поставщиков вручную. У дочерних элементов будут свои собственные реквизиты, но некоторые значения реквизитов от родителя должны автоматически передаваться дочерним элементам через контекст или каким-либо другим способом. Я создаю пользовательскую библиотеку и не могу заставить пользователей создавать провайдера вручную таким образом.
мой вариант использования на самом деле передает установщик в родительском компоненте дочерним элементам, и дети должны снова передать его реквизиты родительскому элементу с его динамически генерируемым индексом. Таким образом, родительский компонент соберет все данные для создания одного объединенного объекта, который будет использоваться для рендеринга фактического элемента DOM. Эта структура важна, поскольку количество дочерних элементов и ее реквизиты используются для расчета целых размеров как соотношения в моей библиотеке. @snnsnn
моя конечная цель — сделать что-то подобное. ``` <Parent width = {200}> <Child width = {1}/> <Child width = {2}/> <Child width = {3}/> </Parent> ``` и родительский элемент вычислит соотношение дочерних элементов и повторно отрегулируйте их в зависимости от их соотношения по ширине родительского элемента. Я могу заставить этих дочерних элементов «проталкивать» данные, но я хочу сохранить идемпотентное поведение структуры данных, поэтому ширина одного из дочерних компонентов обновляется, это не повреждает результат родительского компонента.
Похоже, для этого вам не нужен контекст. Контекст хорош для контекстных значений, значений, которые вам нужно перезаписать по мере углубления в дерево компонентов, например значений темы и т. д. Если все это предназначено для манипуляций с DOM, используйте ссылки, в противном случае используйте обычную цепочку областей и реактивные значения. В отличие от React, компоненты Solid могут получить доступ к области действия своего родителя. Если вам нужны вычисления, связанные с DOMRect, такие как вставка элементов и настройка их размера или положения, просто напишите конкретный компонент для этой задачи и запустите необработанные манипуляции с DOM над этим изолированным кодом.
Я думаю, возможно, SolidJs не предназначен для манипулирования данными, если он не имеет структуры DOM, если то, что вы описываете, верно. В моем примере DOM не создается до тех пор, пока не будет создан весь поток данных. Интерес представляет поток передачи данных из структур элементов. Мне нужен поток данных: дочерние реквизиты -> родительские реквизиты -> расчет -> создать DOM на основе данных. React поддерживает этот поток данных, если вы передаете метод установки дочерним элементам. Но в SolidJS я не вижу хорошего способа взаимодействия с таким потоком данных.
Я задал вопрос в репозитории Solid на GitHub (https://github.com/solidjs/solid/discussions/2233) и получил ответ о том, как решить эту проблему.
Быстрый ответ на обходной путь — использование этого шаблона:
код будет таким:
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 и СаргуэльУнда !
Наличие динамического контента не меняет результата, если это правильный компонент. Не ждите, что это как-то повлияет на результат. Solid предоставляет собственный динамический компонент. docs.solidjs.com/reference/comComponents/dynamic Я вижу, что вы мне не доверяете, но я знаю, что говорю. Я хорошо знаю кодовую базу и логику рендеринга.