在渲染期间而不是在useEffect中更改引用的值是否安全?



我正在使用useRef来保存 prop 的最新值,以便稍后可以在异步调用的回调(例如 onClick 处理程序)中访问它。我使用的是 ref 而不是将value放在 useCallback 依赖项列表中,因为我希望该值会经常更改(当此组件使用新value重新呈现时),但很少调用 onClick 处理程序,因此每次值更改时都不值得为元素分配新的事件侦听器。

function MyComponent({ value }) {
const valueRef = useRef(value);
valueRef.current = value;  // is this ok?
const onClick = useCallback(() => {
console.log("the latest value is", valueRef.current);
}, []);
...
}

React 严格模式的文档让我相信在render()中执行副作用通常是不安全的。

由于上述方法 [包括类组件render()和函数组件主体] 可能会被多次调用,因此它们不包含副作用非常重要。忽略此规则可能会导致各种问题,包括内存泄漏和无效的应用程序状态。

事实上,当我使用ref 访问值时,我在严格模式下遇到了问题。

我的问题是:从渲染函数分配valueRef.current = value的"副作用"是否有任何担忧?例如,是否存在回调会收到过时值(或来自尚未提交的"未来"渲染的值)的情况?

我能想到的一种选择是确保在组件渲染后更新 ref 的useEffect,但从表面上看,这看起来没有必要。

function MyComponent({ value }) {
const valueRef = useRef(value);
useEffect(() => {
valueRef.current = value;  // is this any safer/different?
}, [value]);
const onClick = useCallback(() => {
console.log("the latest value is", valueRef.current);
}, []);
...
}

例如,是否存在回调会收到过时值(或来自尚未提交的"未来"渲染的值)的情况?

括号是主要关注点。

目前,render(和功能组件)调用与实际 DOM 更新之间存在一对一的对应关系。 (即提交)

但是很长一段时间以来,React 团队一直在谈论一种"并发模式",在该模式下,更新可能会开始(render被调用),但随后会被更高优先级的更新打断。

在这种情况下,如果 ref 在被取消的渲染中更新,则 ref 最终可能会与渲染组件的实际状态不同步。

这已经假设了很长时间,但刚刚宣布一些并发模式更改将以选择加入的方式登陆 React 18,使用startTransitionAPI。 (也许还有其他一些)


实际上,这在多大程度上是一个实际问题? 很难说。startTransition是选择加入的,所以如果你不使用它,你可能很安全。 无论如何,许多参考更新都将相当"安全"。

但如果可以的话,

最好谨慎行事。

UPDATE:现在,react.dev 文档还说你不应该这样做:

在渲染过程中不要写入或读取ref.current,初始化除外。这使得组件的行为不可预测。

通过上面的初始化,它们意味着这样的模式:

function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
....

据我所知,这是安全的,但你只需要知道,当 React "感觉像"渲染你的组件时,可能会对 ref-boxed 值进行更改,而不一定是确定性的。

这看起来很像react-useuseLatest钩子(docs),在这里复制,因为它是微不足道的:

import { useRef } from 'react';
const useLatest = <T>(value: T): { readonly current: T } => {
const ref = useRef(value);
ref.current = value;
return ref;
};
export default useLatest;

如果它适用于react-use,我认为这对你也很好。

function MyComponent({ value }) {
const valueRef = useRef(value);
valueRef.current = value;  // is this ok?
const onClick = useCallback(() => {
console.log("the latest value is", valueRef.current);
}, []);
...
}

我在这里并没有真正看到问题,因为每个渲染周期都会发生valueRef.current = value问题。它并不昂贵,但它会在每个渲染周期发生。

如果您使用useEffect钩子,那么您至少将 ref 值设置为仅在prop实际更改时才设置的次数。

function MyComponent({ value }) {
const valueRef = useRef(value);
useEffect(() => {
valueRef.current = value;
}, [value]);
const onClick = useCallback(() => {
console.log("the latest value is", valueRef.current);
}, []);
...
}

由于useEffect在组件生命周期中的工作方式,我建议坚持使用useEffect钩子并保持正常的 React 模式。使用useEffect钩子还可以为每个实际渲染周期提供更确定的值,即"提交阶段"与可以取消、中止、召回等的"渲染阶段"......

不过好奇的是,如果你只想要最新的value道具值,只需直接引用value道具,它将永远是当前的最新值。将其添加到useCallback挂钩的依赖项中。这本质上是您使用更新 refuseEffect完成的工作,但以更清晰的方式。

function MyComponent({ value }) {
...
const onClick = useCallback(() => {
console.log("the latest value is", value);
}, [value]);
...
}

如果你真的总是想要最新的突变值,那么是的,跳过useCallback依赖项,跳过useEffect,并改变你想要/需要的 ref,只需引用当前 ref 值在调用回调时

相关内容

  • 没有找到相关文章

最新更新