为什么useRef可以解决陈旧状态的问题?

Sea*_*ean 1 javascript reactjs react-hooks

我知道 React 组件内的嵌套函数或回调(例如 setTimeout)存在陈旧状态问题。例如,在下面的代码(取自 React 文档)中,我们看到了陈旧状态的问题:

\n
\n

如果您首先单击 \xe2\x80\x9cShow Alert\xe2\x80\x9d,然后递增计数器,\nalert 将显示您单击 \xe2\x80\x9cShow\nalert\xe2\x80 时的计数变量\x9d 按钮。”

\n
\n
function Example() {\n  const [count, setCount] = useState(0);\n\n  function handleAlertClick() {\n    setTimeout(() => {\n      alert('You clicked on: ' + count);\n    }, 3000);\n  }\n\n  return (\n    <div>\n      <p>You clicked {count} times</p>\n      <button onClick={() => setCount(count + 1)}>\n        Click me\n      </button>\n      <button onClick={handleAlertClick}>\n        Show alert\n      </button>\n    </div>\n  );\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我知道我们可以使用以下方法创建对最新/当前状态值的可变引用useRef

\n
function Example() {\n  const [count, setCount] = useState(0);\n  const stateRef = useRef()\n  stateRef.current = count\n  function handleAlertClick() {\n    setTimeout(() => {\n      alert('You clicked on: ' + stateRef.current);\n    }, 3000);\n  }\n\n  return (\n    <div>\n      <p>You clicked {count} times</p>\n      <button onClick={() => setCount(count + 1)}>\n        Click me\n      </button>\n      <button onClick={handleAlertClick}>\n        Show alert\n      </button>\n    </div>\n  );\n}\n
Run Code Online (Sandbox Code Playgroud)\n

useRef我的问题是,为什么创建对变量 ( )的引用stateRef可以解决陈旧状态的问题,以及为什么这可以解决闭包问题?count当然,通过直接在内部引用setTimeout,一旦组件重新渲染,count值就会更新,因此我们对它的引用将返回最新值。

\n

我很难理解为什么引用stateRef而不是count绕过陈旧状态的问题,因为从setTimeouts 的角度来看它们都是在相同的词法范围中声明的。

\n

hac*_*ape 7

尝试想象一个时间轴,组件的每次重新渲染都会创建一个“快照”并在此时间轴上留下标记。

所谓的“陈旧状态”更常被称为“陈旧闭包”,它更准确地描述了真正的问题。

关闭究竟如何造成麻烦?

每次组件重新渲染时,组件函数本身都会从头到尾重新执行,在执行过程中:

  1. 沿途所有的钩子调用都会被调用
  2. 所有局部变量都被重新初始化
  3. 并且组件函数内的所有嵌套函数都被重新声明
  4. 最重要的是,如果这些嵌套函数中的任何一个引用组件函数内的局部变量,则此类引用实际上指向“闭包”或捕获这些局部变量的当前状态的“快照”,在创建时飞并存储在内存中的某个地方。

如果您调用setTimeout并向其发送回调函数,并且该函数引用局部变量,则它实际上存储在渲染时间轴上的这些快照之一内。

当组件重新渲染时,新的快照会添加到时间线中,并且只有最新的快照是最新的,而回调函数仍然引用过时的快照/闭包。

setTimeout决定执行回调时,回调会查看自己的快照/闭包版本,并说“好吧,'count'变量为 0”,但不知道它已经过时了。

useRef问题如何解决?

Cusconst ref = useRef()在重新渲染时始终返回相同的对象引用。因此,即使回调查看过时的快照/闭包,它仍然会看到ref与最新快照中相同的对象。并且由于组件函数的每次重新执行总是将属性设置ref.value = someValue为最新值,因此回调有一种访问最新值的方法。