#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 вызывается не при каждом рендеринге.