使用useRef存储先前的状态值

17

我对使用useRef存储上一个状态值感到困惑。实际上,它如何能够正确显示先前的值?由于useEffect依赖于"value",我的理解是每次"value"更改时(即当用户更新文本框时),它会将“prevValue.current”更新为新键入的值。

但这不是正在发生的事情。在这种情况下,步骤的顺序是什么?

function App() {
  const [value, setValue] =  useState("");
  const prevValue = useRef('')
  useEffect(() => {
    prevValue.current = value;
  }, [value]);
  return (
    <div>
      <input
        value={value}
        onChange={e => setValue(e.target.value)}
      />
      <div>
        Curr Value: {value}
      </div>
      <div>
        Prev Value: {prevValue.current}
      </div>
    </div>
  );
}
3个回答

14

虽然这种方法在技术上可行,但是实际操作中容易混淆,并且随着添加更多的内容可能会导致 bug。它能够工作的原因是因为 useEffect 在状态改变之后运行,而改变引用值不会导致重新渲染。更好的方法是在 onChange 处理程序中更新引用值,而不是在 effect 中进行更新。但是你发布的代码的工作方式如下:

  1. 最初,两者都为空
  2. 用户输入内容,通过 setValue 触发状态改变
  3. 这会触发重新渲染,所以 {value} 是新值,但由于引用还没有被更新,所以 {prevValue.current} 仍然呈现为
  4. 接下来,在渲染后,effect 运行,因为它依赖于 value。因此,此 effect 更新引用以包含当前状态值
  5. 然而,由于改变引用值并不会触发重新渲染,所以新值不会反映在呈现的内容中

因此,在上述步骤完成后,状态值和引用实际上是相同的值。但由于引用更改没有触发重新渲染,所以在呈现的内容中仍然显示旧引用值。

显然这并不是太好的,因为如果其他什么东西触发了重新渲染,比如说你有另一个与状态值连接的输入框,那么是的{prevValue.current}会重新渲染成当前的{value},从技术上讲它就是错误的,因为它显示的是当前值而不是先前的值。

因此,虽然在这种情况下它技术上是可行的,但随着您添加更多代码,它将容易出现错误,并且会让人感到困惑。


2
哦,你是指那个页面上的这个语句吗? useEffect 是做什么用的?使用这个 Hook,你告诉 React 在渲染后你的组件需要做一些事情。React 会记住你传递的函数(我们称之为“effect”),并在执行 DOM 更新后稍后调用它。 - copenndthagen
2
是的,useEffect 的基本意思是:“在每次重新渲染后,在 DOM 更新完成并且所有操作都完成之后,如果我的任何依赖项已更改,则运行此代码”。 - Jayce444
1
是的,没错。在 React 内部,当改变状态时,状态值会先被改变,然后组件会重新渲染。因此,这个变化会立即反映出来。 - Jayce444
1
@Jayce444 - 非常感谢...最后一个相关的问题...如果定义了return/cleanup函数,useEffect内部的该函数何时被触发? - copenndthagen
1
@testndtv 在组件卸载后,基本上重新创建了 componentDidUnmount。具体来说,我认为它是在从 DOM 中移除并在组件对象被 JS 引擎标记为垃圾回收之前。 - Jayce444
显示剩余5条评论

3

https://reactjs.org/docs/hooks-reference.html#useref useRef返回一个可变的ref对象,其.current属性初始化为传递的参数(initialValue)。返回的对象将在组件的整个生命周期中持续存在

https://reactjs.org/docs/hooks-effect.html useEffect会在每次渲染后运行吗?是的!默认情况下,它会在第一次渲染后和每次更新后都运行

因此,它按顺序执行以下步骤:

1 - Input change (example: "1")
2 - Component re-render
3 - useEffect run and set value ("1") to prevValue.current. This does not make component re-render. At this time prevValue.current is "1".
4 - Input change (example: "12")
5 - Component re-render => show prevValue.current was set before in step 3 ("1")
6 - useEffect run and set value ("12") to prevValue.current. This does not make component re-render. At this time prevValue.current is "12".
... 

谢谢...但是对于步骤(3),当 useEffect 运行时,"value" 不应该等于更新/新值,因此 prevValue.current 也应该是更新/新值吗? 这是我的具体问题。 - copenndthagen
1
例如,在第1步中,您输入“1”,然后在第3步中,当useEffect运行时,“value”将为“1”,并且它将被设置为prevValue.current,因此prevValue.current将具有值“1”。在useEffect中,“value”始终是输入的新/更新值。 - Thanh
1
请注意,useEffect会在第一次渲染之后以及每次更新之后运行。 - Thanh

2

useRef() 可用于在连续的渲染中保留值。如果你想要保留过去的值,则将其放在 onChange 中:

<input
    value={value}
    onChange={e => {
       prevValue.current = value;
       setValue(e.target.value)
    }}
   />

在改变它之前,这将把它分配给当前状态值value,您将不需要使用useEffect钩子。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接