В React и useEffect
, и useMemo
имеют аргумент зависимости.
Я наивно полагал, что они работают одинаково: всякий раз, когда значения в этом аргументе зависимостей изменялись, я думал, что будет выполняться обратный вызов useEffect
или useMemo
, и что единственная разница заключалась во времени (useMemo
выполняется до рендеринга, а useEffect
— после).
В целом они идентичны. Но когда у меня есть ссылка в качестве зависимости, это не так:
const Foo = ()=> {
const ref = useRef();
useEffect(() => console.info('effect ref', ref), [ref]);
useMemo(() => console.info('memo ref', ref), [ref]);
return <div ref = {ref}>Foo</div>
}
Ссылка на песочницу (нажмите значок консоли в правом верхнем углу, чтобы просмотреть журналы)
Насколько я понимаю, Foo
должен рендериться дважды. В первый раз ref.current
будет undefined
, а во второй раз будет установлено <div>
. Таким образом, я ожидаю увидеть два useEffect
журнала effect ref div
, потому что ref
будет установлен как после рендеринга №1, так и после рендеринга №2. И я делаю ...
ссылка на эффект {текущий: div}
ссылка на эффект {текущий: div}
Однако в журналах заметок я никогда не вижу зарегистрированных div
. Вместо этого я просто вижу повторение одного и того же:
ссылка на заметку не определена
ссылка на заметку не определена
Это ожидаемо для первого вызова, поскольку компонент еще не отрендерился... но перед вторым рендерингом, после первого, разве ref
не должно быть установлено на <div>
, и поэтому не следует useMemo
регистрировать его? Вместо этого, похоже, useMemo
никогда не запускается.
Короче говоря, хотя я в основном понимаю, как работает useMemo
, похоже, мне не хватает некоторых ключевых деталей, объясняющих, почему он никогда не регистрирует значение ref
, и я был бы признателен за любую помощь, объясняющую почему.
P.S. Я понимаю, что, вероятно, мог бы решить все это, сделав useRef
зависимым от переменной состояния, а затем создав useEffect
, который обновляет эту переменную состояния при изменении ref
... но я больше сосредоточен на том, чтобы сначала попытаться понять проблему, прежде чем решить ее. это.
Но независимо от того, является ли ref
стабильным объектом или нет, я делаю два рендеринга, верно? На рендере №1 установлен ref.current
undefined
, а на рендере №2 (и 3, и...) установлен ref.current
. Если я правильно понимаю, то если я вижу два журнала обратных вызовов useMemo
, это должно означать, что useMemo
запускался один раз перед первым рендерингом и один раз перед вторым. Разве этот второй не должен был зарегистрировать ref
, поскольку к тому времени он уже должен был быть установлен? Я знаю, что что-то здесь не понимаю, но не знаю, что именно.
Я предполагаю, что он не выполняет два рендеринга или делает два рендеринга только из-за React.StrictMode, который может вести себя иначе, чем обычный повторный рендеринг.
Я считаю, что ваш эффект и заметка в основном настроены на запуск только один раз, но вы видите, что они выполняются дважды. Единственное объяснение, которое я видел в мире React, — это строгий режим. И сложно понять нормальный цикл событий в строгом режиме, поэтому я хотел сначала обсудить это, чтобы исключить
О, кажется, теперь я понимаю. Если я вас поймаю, вы говорите, что журналы, которые я вижу, отражают несколько рендеров до DOM (т. е. предварительной возможности установки ссылки)? Я думал, что компонент визуализируется только один раз без ref
, а ref
всегда устанавливался рендером №2... но вы предполагаете, что я вижу журналы нескольких рендерингов до того, как был установлен ref
(потому что «React Strict Mode» вызывает второй рендеринг с предварительной настройкой)?
Честно говоря, сложно уследить за всеми странностями, которые может вызвать StrictMode, но пока мы это обсуждаем, я считаю, что в React 18 добавили новую функцию, которая вызывает двойное монтирование компонентов, что определенно может объяснить такое поведение. Позвольте мне посмотреть, смогу ли я найти ссылку на документацию по этому поведению.
Если бы вы могли добавить эту ссылку в ответ, это определенно показалось бы лучшим объяснением, которое кто-либо представил, поэтому я с радостью проголосую за него (и приму его через 24 часа, если ничего больше не появится).
К сожалению, я не смог найти эту информацию в документации, только в объявлении о выпуске React 18 - act.dev/blog/2022/03/29/react-v18#new-strict-mode-behaviors - " Эта новая проверка автоматически размонтирует и перемонтирует каждый компонент при первом монтировании компонента, восстанавливая предыдущее состояние при втором монтировании».
В документации есть упоминание об этом здесь — act.dev/reference/react/… — «Когда включен строгий режим, React также запускает один дополнительный цикл настройки + очистки в разработке для каждого эффекта. Это может показаться удивительным, но это помогает выявить тонкие ошибки, которые трудно обнаружить вручную».
Эффект использования вызовет действие, основанное на измененной ссылке, где has memo будет переменной, и вам нужно будет что-то вернуть.
Например:
const Foo = ()=> {
const user = authHook(); // whatever here
useEffect(() => console.info('user full name', `${user.firstname} ${user.lastname}` ), [user]); // will trigger when the user changes and outputs to console the user fill name
const fullname = useMemo(() => `${user.firstname} ${user.lastname}`, [user]); // returns a variable that can be used in your code and will auto update when the user changes but wont reivaluate when anything else changes
return <div>{fullname}</div>
}
Я думаю, что в вашем вопросе, в частности, происходит то, что вы передаете ссылку, хотя должны пройти ref.current
Спасибо, но опять же я не ищу решения, я ищу объяснение того, как useMemo
работает, в частности, как он обрабатывает ref
зависимости иначе, чем useEffect
.
тогда это может ответить на ваш вопрос, stackoverflow.com/a/67906087/10037470 кажется, что есть проблемы с useMemo и useEffect с ссылками, поэтому рекомендуется использовать useCallback.
Эта ссылка интересна, но она все еще не отвечает на мой вопрос. В этой ссылке говорится, что когда вы используете useEffect
с зависимостью ref
, изменения ref
не вызывают повторный рендеринг/обратный вызов useEffect
. Это верно, но мой вопрос не в запуске обратных вызовов: как вы можете видеть из песочницы, обратные вызовы запускаются. Мой вопрос о том, что они регистрируют при запуске: кажется, что второе сообщение обратного вызова/журнала useMemo
должно появиться после установки ref
, поэтому я не понимаю, почему оно не регистрирует ref
.
Такое поведение связано с React.StrictMode, а не с использованием ссылки в качестве зависимости.
Ссылка — это стабильная ссылка на объект при рендеринге, а useEffect
и useMemo
используют проверку ссылочного равенства, чтобы определить, изменились ли их зависимости. Следовательно, при нормальных обстоятельствах (например, при сборке продукта) эти перехватчики будут выполняться только один раз.
Поскольку вы видите свидетельства того, что они запускаются более одного раза, ответ — строгий режим.
Объяснение этому можно найти в нескольких различных разделах документации.
https://react.dev/reference/react/StrictMode
Ваши компоненты будут перезапускать эффекты еще раз, чтобы найти ошибки, вызванные отсутствием очистки эффектов.
https://react.dev/reference/react/useMemo#caveats
В строгом режиме React дважды вызовет вашу вычислительную функцию, чтобы помочь вам найти случайные примеси. Это поведение предназначено только для разработки и не влияет на производство. Если ваша вычислительная функция чистая (как и должно быть), это не должно повлиять на вашу логику. Результат одного из вызовов будет проигнорирован.
https://react.dev/blog/2022/03/29/react-v18#new-strict-mode-behaviors
Также возможно (но невозможно сказать), что это может быть вызвано тем, что React 18 StrictMode перемонтирует каждый компонент.
Однако ссылка является стабильной ссылкой на объект, поэтому даже если значение
current
внутри изменится, внешняя ссылка останется прежней. Поэтому я бы не ожидал, что заметка или эффект сработают дважды. Ссылка на песочницу, похоже, не работает, но возможно ли, что StrictMode мешает, запуская их дважды на одном и том же рендеринге?