Ожидание значений от неизвестного номера дочернего компонента перед запуском

#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 и добавить функциональность «обновление состояния завершения» к своим дочерним компонентам.