Реагирующие крючки: как избавиться от зависимости от состояния

#javascript #reactjs #react-hooks #use-effect

#javascript #reactjs #реагирующие крючки #использование-эффект

Вопрос:

В приведенном ниже примере мне интересно, как избавиться от items зависимости в useEffect() я хочу выполнить только один раз, чтобы установить интервал. Какова наилучшая практика в этом отношении? Спасибо!

 const Component = () => {
  const [items, setItems] = useState([])

  useEffect(() => {
    const fetchItems = () => {
      fetchNewItemsSince(items.length ? items[items.length - 1].id : 0) // How to get rid of items dependency
        .then((newItems) => {
          setItems((oldItems) => [...oldItems, ...newItems])
        })
    }

    fetchItems()
    setInterval(fetchItems, 60 * 10000)

    return () => clearInterval()
  }, [items]) // <= I want to get rid of that dependency!!
}
  

Комментарии:

1. Вы заметили, что ваше условие всегда соответствует действительности? Этот код не имеет особого смысла

2. Также у вас есть закрытие items , поэтому оно не обновляет условие, как вы могли подумать

3. Извините, я исправил это. В любом случае, это было не главное. ;-D

4. Как бы вы установили интервал, который извлекает материал для добавления в массив состояний?

5. Этот «извлеченный материал» зависит от элементов в массиве, как вы хотите его вызвать, не имея состояния в dep?

Ответ №1:

В этих фрагментах есть несколько ошибок, таких как очистка интервала и вызов функции очистки useEffect , я бы переписал эту логику в:

 const Component = () => {
  const [items, setItems] = useState([]);
  const itemsRef = useRef(items);

  const fetchItems = useCallback(() => {
    const [first] = itemsRef.current;
    fetchNewItemsSince(first || 0).then((newItems) => {
      setItems((oldItems) => [...oldItems, ...newItems]);
    });
  }, []);

  // Update ref to dispose closure on `items` state
  useEffect(() => {
    itemsRef.current = items;
  }, [items]);

  // Call once on mount
  useEffect(() => {
    fetchItems();
  }, [fetchItems]);

  // Make an interval
  useEffect(() => {
    const id = setInterval(fetchItems, ONE_MINUTE);

    return () => {
      clearInterval(id);
    };
  }, [fetchItems]);
};
  

Комментарии:

1. Я пытался использовать useRef , но я сделал это неправильно, и этот способ работает, спасибо! fetchItems Однако в двух последних нам просто не хватает зависимости useEffect() . Спасибо!

2. Исправлено, хотя они не будут меняться, это гарантирует одинаковую функцию на протяжении всего срока службы компонента в данной конкретной ситуации. (Потому useCallback что dep пустой)

3. Это работает так, спасибо! Теперь у меня проблема, когда я предоставляю обратный вызов в качестве поддержки onError своему компоненту, поэтому он вызывается fetchItems обратным вызовом, когда это необходимо. Я добавил onError в качестве dep для этого обратного вызова и, как следствие, я попадаю в цикл при onError вызове. Почему, когда onError вызывается, вызывается все, что зависит от него как dep? У вас есть идея, как это исправить?

4. Просто задайте отдельный вопрос, пожалуйста

Ответ №2:

Я бы сделал что-то подобное, используя useInterval хук из react-use библиотеки:

 const Component = () => {
  const [items, setItems] = useState([]);
  const lastItemId = useMemo(
    () => (items.length ? items[items.length - 1].id : 0),
    [items],
  );
  const fetchNewItems = useCallback(async () => {
    const newItems = await fetchNewItemsSince(lastItemId);
    if(newItems.length) {
      setItems((oldItems) => [...oldItems, ...newItems]);
    }
  }, [lastItemId, setItems]);
  useInterval(fetchNewItems, 60 * 10000);
};
  

Комментарии:

1. Я не думаю, что это хорошее решение… Если вы проверяете useInterval реализацию, вы просто меняете обратный вызов, это довольно бесполезно, при каждом рендеринге вы очищаете интервал и перезапускаете интервал github.com/streamich/react-use/blob/master/src/useInterval.ts , и будет рендеринг, потому что вы всегда добавляете последний элемент

2. Справедливое замечание по очистке интервала. Добавлена защита от этого. Также есть github.com/streamich/react-use/blob/master/docs/useTimeoutFn.md который не сбрасывает тайм-аут при изменении функции.

3. Спасибо! Я не уверен, что это сработает, поскольку useInterval() будет вызываться при каждом рендеринге.

4. Это тоже неправильно … useTimeout не является интервалом, если ваша выборка завершится неудачно, другого вызова не будет… Когда мы хотим получать данные каждые 1 минуту

5. @Alpha60 На основе исходного кода useInterval вызывается не при каждом рендеринге.