#reactjs #react-native #react-hooks #setstate
#reactjs #реагирующий-собственный #реагирующие перехваты #setstate
Вопрос:
Не получает обновленное значение состояния в функции, которое запускается EventListener. Даже пробовал с фоновым таймером. В чем может быть проблема?
displayIncomingCall() // this function gets called from UI.
const callApp = () => {
const [calls, setCalls] = useState({});
const addCall = (key, value) => {
setCalls({ ...calls, [key]: value });
};
const displayIncomingCall = number => {
const callUUID = getCurrentCallId();
addCall(callUUID, number);
...
};
const onAnswerCall = () => {
console.log('=== onAnswerCall ::: ===', calls); <--- always initial value. Not getting updated one.
}
useEffect(() => {
console.log('=== useEffect ::: ===', calls); // getting updated value
}, [calls]);
useEffect(() => {
RNCallKeep.addEventListener('answerCall', onAnswerCall);
return () => {
RNCallKeep.removeEventListener('answerCall', onAnswerCall);
};
}, []);
console.log('=== parent ::: ===', calls); // getting updated value
return (...)
}
Комментарии:
1. Это потому, что вы только
addEventListener
один раз, когда компонент впервые смонтирован, и не обновляете его при повторной визуализации компонента. Оно всегда закрывается над начальным значениемcalls
.2. Пожалуйста, попробуйте добавить возврат в setState
Ответ №1:
onAnswerCall
ссылается только в useEffect
обратном вызове, а useEffect
обратный вызов имеет пустой массив зависимостей, поэтому он выполняется только при первоначальном монтировании. Всякий раз, когда onAnswerCall
вызывается привязка calls
, которую он может видеть, находится в старом закрытии.
Используйте ссылку вместо состояния:
const callsRef = useRef({});
const addCall = (key, value) => {
callsRef.current[key] = value;
};
const onAnswerCall = () => {
console.log('=== onAnswerCall ::: ===', callsRef.current);
}
Если calls
необходимо находиться в состоянии, чтобы его изменение привело к повторному рендерингу, то вы можете либо использовать как состояние, так и ссылку и присваивать ссылку при вызове setCalls
, либо вы можете удалить массив зависимостей из useEffect
, таким образом, добавляя и удаляя слушателей RNCallKeep
при каждом рендеринге.
Комментарии:
1. Это сработает, но не приведет к повторному отображению компонента при обновлении вызовов, которые, как я полагаю, используются при рендеринге компонента, поэтому я полагаю, что ему необходимо запустить повторный рендеринг.
2. Поскольку возвращаемое значение компонента не отображается, неясно, нужно ли компоненту повторно отображать при выполнении нового вызова. Если требуется повторная визуализация, то, как я уже сказал, одним из методов является использование как ссылки, так и состояния. Другой способ заключается в добавлении и удалении прослушивателя при каждом вызове.
3.Смысл использования state вместо обычных переменных заключается в том, чтобы React запускал повторный рендеринг компонента. Если вы проверите мой ответ, слушатель обновляется (удаляется / добавляется) при каждом
onAnswerCall
useCallback
обновлении, что, я считаю, является наилучшим подходом, в конце концов, это цель этих хуков.4. Если повторный рендеринг необходим при изменении переменной, да, конечно, используйте state — но из кода в вопросе мне не ясно, нужен ли такой повторный рендеринг. К сожалению, возвращенный JSX компонента не отображается
5. На данный момент не используется при рендеринге. Было бы лучше, если бы у нас также была гибкость повторного рендеринга. Это решение работает
Ответ №2:
Состояние React hooks не сохраняет никаких данных о состоянии, вы должны использовать useCallback
и предыдущее состояние, указанное в set*
функциях состояния, чтобы не отставать от вашего состояния:
const callApp = () => {
const [calls, setCalls] = useState({});
const addCall = (key, value) => {
// Use previous state provided to update the state
setCalls(calls => ({ ...calls, [key]: value }));
};
const displayIncomingCall = number => {
const callUUID = getCurrentCallId();
addCall(callUUID, number);
...
};
// Use useCallback to keep up with the state
const onAnswerCall = useCallback(() => {
console.log('=== onAnswerCall ::: ===', calls); <--- Will get updated
}, [calls]);
useEffect(() => {
console.log('=== useEffect ::: ===', calls); // getting updated value
}, [calls]);
useEffect(() => {
RNCallKeep.addEventListener('answerCall', onAnswerCall);
return () => {
RNCallKeep.removeEventListener('answerCall', onAnswerCall);
};
}, [onAnswerCall]);
console.log('=== parent ::: ===', calls); // getting updated value
return (...)
}
Комментарии:
1. но
onAnswerCall
запускается несколько раз (прослушиватель вызывается только один раз)2. Прослушиватель отменит регистрацию старого и зарегистрирует новый при каждом
calls
обновлении, это не повлияет на то, сколько раз будет запускатьсяanswerCall
событие и, следовательно, сколько разonAnswerCall
будет вызываться прослушивателем событий.3. как
onAnswerCall
у меня есть похожие прослушиватели событий состояния вызова 4-5, о которых я не упомянул во фрагменте. Как я прочитал kentcdodds.com/blog/usememo-and-usecallback Повлияет ли это на производительность, если мы будем использоватьuseCallback
для всех прослушивателей событий?4. Каждая строка кода, которая выполняется, сопряжена с затратами Снижение производительности, которое вы получите, незначительно — один дополнительный вызов функции за событие для всех слушателей. Вполне возможно, что когда вы вызываете 2 функции вместо 1, предстоит проделать большую работу, не так ли? Если вы просто используете эти функции для прослушивателей, поместите их непосредственно внутри эффекта монтирования, иначе у вас нет выбора, если вы используете
calls
состояние в своем методе рендеринга, которое вам придется использоватьuseCallback
.
Ответ №3:
Ваш обратный вызов прослушивателя событий закрыт по сравнению с начальным состоянием calls
. Добавьте его в качестве зависимости к эффекту, и вы должны регистрировать нового слушателя (и удалять старые) каждый раз, когда изменяется его значение.
РЕДАКТИРОВАТЬ: На самом деле, вы должны переместить всю onAnswerCall
функцию в эффект, поскольку она используется только там:
useEffect(() => {
const onAnswerCall = () => {
console.log('=== onAnswerCall ::: ===', calls);
};
RNCallKeep.addEventListener('answerCall', onAnswerCall);
return () => {
RNCallKeep.removeEventListener('answerCall', onAnswerCall);
};
}, [calls]);
Комментарии:
1. Зачем вам это делать? Вы думаете,
onAnswerCall
что будет по-другому послеcalls
изменения?2. @ChristosLytras это другая функция для каждого рендеринга, так что да! Но возвращаемое значение на самом деле отличается только при
calls
изменениях. Вот ручка, которая иллюстрирует это (посмотрите на консоль): codesandbox.io/s/laughing-lehmann-4xe52?file=/src/App.js3.Мой вопрос был немного ошибочным, и я ссылался на
calls
изменение состояния, а не на рендеринг. Я считаю, что правильный способ — использоватьuseCallback
и получать новую функцию только тогда,calls
когда она изменяется, а затем повторно применять прослушиватели. Проверьте свой пример с этой логикой здесьcodesandbox.io/s/still-fast-x41jt?file=/src/App.js4. Я не думаю, что мы не согласны — оба решения делают одно и то же (в вашем меньше ненужных созданий функций, но в любом случае это действительно быстро в JS). Я пытался указать на непонимание OP того, как работают прослушиватели событий.
5. Тогда соглашайтесь не соглашаться. Определенно есть время и место для
useCallback
, и я абсолютно использую его ежедневно. Я просто не думаю, что вам это нужно здесь, поскольку и эффект, и функция зависят от одного и того же изменения.