#javascript #reactjs #react-hooks
#javascript #reactjs #реагирующие крючки
Вопрос:
Предполагая, что существует родительский компонент:
function Parent({ children }) {
useEffet(() => {
// Do something when all children
// have returned their values via props (or context)
})
return <div>{ children }</div>
}
Как я могу запустить эффект только после того, все дочерние элементы что-то сделали? Я не знаю точного количества дочерних элементов, и оно может варьироваться:
// In this example there are 4 children
function App() {
return (
<Parent>
<Children />
<Children />
<Children />
<div>
<Children />
</div>
</Parent>
);
}
Реальная проблема заключается в том, что все дочерние элементы могут подготовить запрос (скажем, запрос ElasticSearch), а родительский элемент отвечает за фактическое выполнение всего запроса.
Комментарии:
1. Управляющие реквизиты? вы передаете
prepareQuery={(query) => /* set parent's state, push in an array or whatever */}
prop каждому дочернему компоненту и позволяете им вызывать эту функцию внутренне.Parent
следует знать о количестве отображаемых дочерних элементов, так что это просто вопрос подсчета того, сколько дочерних элементов вызывали ихprepareQuery
, и запускать эффект только в этот момент.2. > «Родитель должен знать о количестве отображаемых дочерних элементов», Как это может быть известно? Номер дочернего компонента неизвестен (см. Пример) и может быть вложенным.
Ответ №1:
Ну, мы могли бы обернуть действие, выполняемое дочерними компонентами, в обещание:
function usePromises() {
const promises = useMemo([], []);
const [result, setResult] = useState(undefined);
useEffect(() => { // this gets deferred after the initialization of all childrens, therefore all promises were created already
Promise.all(promises).then(setResult);
}, []);
function usePromise() {
return useMemo(() => {
let resolve;
promises.push(new Promise(r => resolve = r));
return resolve;
}, []);
}
return [result, usePromise];
}
Теперь создайте один общий менеджер (внутри родительского):
const [queries, useQueryArrived] = usePromises();
// queries is either undefined or an array of queries, if all queries arrived this component will rerender
console.log(queries);
Затем передайте useQueryArrived
дочерним элементам и используйте его там (внутри дочерних элементов):
const queryArrived = useQueryArrived();
// somewhen:
queryArrived({ some: "props" });
Теперь логика выглядит следующим образом:
Родительский компонент отображается в первый раз, он создаст promises
массив. Дочерние элементы будут инициализированы, и каждый из них создаст обещание, которое будет привязано к promises
распознавателю обещания, который запоминается, так что один и тот же распознаватель будет возвращен при каждом повторном запуске дочернего компонента. Когда все дочерние компоненты были инициализированы, React выполняет рендеринг, и запускается эффект, который примет все обещания и вызовет Promise.all
их.
Теперь, когда queryArrived
вызывается дочерний компонент, обещания разрешаются, когда выполняется последнее обещание, которое затем вызывается setResult
со всеми результатами promises, что приведет к повторному отправлению родительского компонента, который теперь может использовать запросы.
Комментарии:
1. Вы не должны иметь возможности вызывать
useEffect
внутри обратного вызова. (или любые перехваты)2. Я этого не делаю.
3.
useEffect
внутриisDone
.react-hooks/rules-of-hooks
lint должен быть в состоянии обнаружить проблему.4. Это действительно интересное решение. Мой пример: codesandbox.io/s/4omm31729
5. @JonasWilms Я пробовал ваш код, но он выдает «nextCreate не является функцией»: gist.github.com/rap2hpoutre/0c0bdc8eca54b7973677b35abdf7dea5
Ответ №2:
Вы можете ввести finish
состояние, когда все дочерние элементы закончили «что-то делать».
useEffect(() => {
const totalChildren = React.Children.count(children);
if (totalChildren === finish) {
console.log("All Children Done Something");
setFinished(0);
}
}, [finish]);
Для примера:
function Children({ num, onClick }) {
return <button onClick={onClick}>{num}</button>;
}
function Parent({ finish, setFinished, children }) {
useEffect(() => {
const totalChildren = React.Children.count(children);
if (totalChildren === finish) {
console.log("All Children Done Something");
setFinished(0);
}
}, [finish]);
return <div>{children}</div>;
}
function App() {
const [num, setNum] = useState(0);
const [finish, setFinished] = useState(0);
const onClick = () => {
setNum(num 1);
setFinished(finish 1);
};
return (
<Parent finish={finish} setFinished={setFinished}>
<Children num={num} onClick={onClick} />
<Children num={num 1} onClick={onClick} />
<Children num={num 2} onClick={onClick} />
<div>
<Children num={num 3} onClick={onClick} />
</div>
</Parent>
);
}
Кроме того, вы можете использовать React.cloneElement
и добавить функциональность «обновление состояния завершения» к своим дочерним компонентам.