#javascript #reactjs #react-native #redux #react-hooks
#javascript #reactjs #реагирующий-родной #redux #реагирующие перехваты
Вопрос:
Я использую useEffect hook и получаю список пользовательских данных с помощью вызова выборки, используя функцию getStoreUsers, которая отправляет действие в ответ и сохраняет shopUsers (который представляет собой массив) внутри хранилища redux.
В зависимости от массива я пишу [shopUsers]. Я не знаю, почему это вызывает бесконечный рендеринг.
Вот как я использую useEffect hook:
useEffect(() => {
const { getStoreUsers, shopUsers } = props;
setLoading(true);
getStoreUsers().then(() => {
setLoading(false);
}).catch(() => {
setLoading(false);
});
}, [shopUsers]);
Я хочу повторно отображать компонент только тогда, когда данные внутри массива shopUsers изменяются.
Если я напишу shopUsers.length внутри зависимости массива. Он останавливается для повторного рендеринга.
Но давайте предположим, что у меня есть страница, которая открывается, когда пользователь нажимает на список пользователей и обновляет пользовательские данные на следующей странице. После обновления я хочу, чтобы пользователь вернулся к тому же компоненту, который ранее не отключался. Таким образом, в этом случае длина массива остается прежней, но данные внутри индекса массива обновляются. Так что shopUsers.length в этом случае работать не будет.
Ответ №1:
Вы можете создать пользовательский хук, чтобы делать то, что вы хотите:
В этом примере мы заменяем последний элемент в массиве и видим результат в консоли.
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { isEqual } from "lodash";
const usePrevious = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const App = () => {
const [arr, setArr] = useState([2, 4, 5]);
const prevArr = usePrevious(arr);
useEffect(() => {
if (!isEqual(arr, prevArr)) {
console.log(`array changed from ${prevArr} to ${arr}`);
}
}, [prevArr]);
const change = () => {
const temp = [...arr];
temp.pop();
temp.push(6);
setArr(temp);
};
return (
<button onClick={change}>change last array element</button>
)
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Живой пример здесь.
Комментарии:
1. Спасибо, похоже, ваше решение может сработать для меня. Можете ли вы сначала объяснить несколько вещей, параметры, которые мы передаем массиву как зависимость, выполняет ли react неглубокое сравнение так же, как это делает isEqual? если нет, то не кажется ли вам, что это должно быть во-вторых, эта функция useRef представлена только с помощью перехватов redux или она была ранее также там?
2.
isEqual
использует глубокое сравнение.useRef
это хук, предоставляемый React, да. Он используется аналогичноReact.createRef()
.3. Да, извините, я говорил о глубоком сравнении 🙂
4. На данный момент React выполняет только поверхностные сравнения, но это может измениться в будущем!
Ответ №2:
Ваш эффект запускается на основе реквизита «shopUsers», который сам запускает действие redux, которое обновляет реквизит «shopUsers», и именно поэтому он продолжает срабатывать бесконечно.
Я думаю, что вы хотите оптимизировать рендеринг самого вашего компонента, поскольку вы уже используете redux, я предполагаю, что ваши реквизиты / состояние неизменны, поэтому вы можете использовать React.memo
для повторного рендеринга вашего компонента только при изменении одного из его реквизитов.
Также вы должны определить свою переменную state / props вне ваших хуков, поскольку они используются в области действия всей функции вот так.
В вашем случае, если вы передаете пустой массив в качестве второго параметра в memo, он будет срабатывать только при componentDidMount, если вы передаете null / undefined или ничего не передаете, он будет срабатывать при componentDidMount componentDidUpdate, если вы хотите оптимизировать его, чтобы даже при изменении реквизита / обновлении компонентаперехват не срабатывает, если не изменится определенная переменная, тогда вы можете добавить некоторую переменную в качестве второго аргумента
React.memo(function(props){
const [isLoading, setLoading] = useState(false);
const { getStoreUsers, shopUsers } = props;
useEffect(() => {
setLoading(true);
getStoreUsers().then(() => {
setLoading(false);
}).catch((err) => {
setLoading(false);
});
}, []);
...
})
Комментарии:
1. да, действие redux обновляет пользователей магазина при первом монтировании компонента. итак, давайте рассмотрим, что shopUsers изначально имеет значение null и когда компонент выполняет рендеринг. useEffect будет выполнен. Он обращается к API и устанавливает для пользователей shopUsers данные с пользователями, которые представляют собой массив объектов. Итак, теперь shopUsers изменили его повторный рендеринг и снова попытались подключиться к api, и снова получили shopUsers, но данные теперь такие же, как и предыдущие. таким образом, начиная со следующего раза, он не должен вызывать повторный рендеринг. У меня есть одно сомнение. выполняет ли useEffect неглубокое сравнение массива и объектов?
2. Я не понимаю, что вы пытаетесь сделать, мне это кажется странным.. почему вы хотите обновить переменную, которая сама используется для запуска вызова, который обновляет себя? Я не понимаю usecase, но чаще всего то, что вы хотите сделать, это иметь родительский компонент, который вызывает вызов API для componentDidMount , или useEffect(fn, []) с пустым массивом в качестве второго аргумента, который запускается один раз, и передает результат в качестве реквизита дочернему компоненту для рендерингаих. А затем всякий раз, когда вы хотите снова вызвать API, он должен быть в обработчике событий в родительском компоненте, который обновит реквизиты.
3. вы можете прослушать какое-либо событие или передать запрос обратного вызова, который запускается, когда пользователь переходит назад, чтобы снова вызвать вызов API для извлечения данных, это намного чище, чем выполнение вызова API каждый раз при обновлении компонента или изменении реквизита.
4. Вы правы, обратный вызов может быть решением. Но я в основном избегаю использования обратных вызовов. Это делает поток входа в систему непредсказуемым. В одном случае у меня есть третий экран в приложении, который после обновления переходит на ту же страницу. Поэтому мне нужно будет передать обратный вызов с одного экрана на другой, а затем на третий. и передача данных в виде параметров с использованием навигации react требует много работы. Я слишком ленив для этого
5. Вы были правы. Я был странным. Теперь я получил это сейчас.