reactjs React Hooks如何使用回调“冻结”闭包?

xcitsw88  于 2022-11-22  发布在  React
关注(0)|答案(2)|浏览(180)

我想知道React如何在使用useCallback钩子(以及其他钩子)时“冻结”闭包,然后在将钩子内使用的变量传递给inputs参数时只更新它们。
我明白“冻结”可能不是很清楚,所以我创建了一个REPL.it来说明我的意思:https://repl.it/repls/RudeMintcreamShoutcast。打开代码后,打开Web浏览器控制台,开始单击count按钮。
为什么外部的值和内部的值是不同的,对于同一个变量,如果它们在相同的闭包下,引用相同的东西?我不熟悉React代码库,所以我想我在这里遗漏了一个底层实现细节,但是我试着思考了几分钟,但是不能很好地理解React是如何实现的。

n9vozmp4

n9vozmp41#

第一次呈现组件时,useCallback钩子会把传递过来的函数作为它的参数,并把它存储在后台。当你调用回调时,它会调用你的函数。到目前为止,一切都很好。
第二次呈现组件时,useCallback钩子将检查您传入的依赖项。如果它们没有改变,* 您传入的函数将被完全忽略 *!当您调用回调时,它将调用您在第一次呈现时传入的函数,这与作为依赖项传入的值无关--这只是普通的JavaScript闭包!
当依赖关系发生变化时,useCallback钩子会接受你传入的函数,并替换它已经存储的函数。当你调用回调函数时,它会调用 * 新 * 版本的函数。
因此,换句话说,没有“冻结”/条件更新的变量-它只是存储一个函数,然后重新使用它,没有比这更花哨的了:)

**EDIT:**下面的例子演示了纯JavaScript中的情况:

// React has some component-local storage that it tracks behind the scenes.
// useState and useCallback both hook into this.
//
// Imagine there's a 'storage' variable for every instance of your
// component.
const storage = {};

function useState(init) {
  if (storage.data === undefined) {
    storage.data = init;
  }
  
  return [storage.data, (value) => storage.data = value];
}

function useCallback(fn) {
  // The real version would check dependencies here, but since our callback
  // should only update on the first render, this will suffice.
  if (storage.callback === undefined) {
    storage.callback = fn;
  }

  return storage.callback;
}

function MyComponent() {
  const [data, setData] = useState(0);
  const callback = useCallback(() => data);

  // Rather than outputting DOM, we'll just log.
  console.log("data:", data);
  console.log("callback:", callback());

  return {
    increase: () => setData(data + 1)
  }
}

let instance = MyComponent(); // Let's 'render' our component...

instance.increase(); // This would trigger a re-render, so we call our component again...
instance = MyComponent();

instance.increase(); // and again...
instance = MyComponent();
hvvq6cgz

hvvq6cgz2#

我来到这里,也带着一个类似的、相当模糊的不确定性,即关于useCallback的工作方式,以及它与闭包的交互作用(我也直观地理解你的措辞,关于useCallback应该如何“冻结”闭包),但我对公认的答案感到相当困惑,它似乎与(我所理解的)你的问题并不完全一致。
对我来说,理解发生了什么的方法是看两个非常简单的闭包(都在定义了单个状态变量a的同一个环境中),有和没有useCallback,看看会发生什么。在使用Reactlinter的情况下,必须忽略它的警告(将a作为依赖项包括在内),以便真正说明差异:

function App() {

  const [a, setA] = useState(0)

  const incrementWithUseCallback = useCallback(() => {
    // As it closes on the first time `App` is called, the closure is "frozen" in an environment where a=0, forever
    console.log(a)
    setA(a + 1)
  }, []) // but.. the linter should complain about this, saying that `a` should be included!

  const incrementWithoutUseCallback = () => {
    // This will see every value of a, as a new closure is created at every render (i.e. every time `App` is called)
    console.log(a)
    setA(a + 1)
  }

  return (
    <div>
      <button onClick={incrementWithUseCallback}>Increment with useCallback</button>
      <button onClick={incrementWithoutUseCallback}>Increment without useCallback</button>
    </div>
  )
}

所以我们清楚地看到useCallback在某个时刻有效地“冻结”了它的闭包,这是一个必须清楚理解的概念,以避免混淆问题。本文可能比我更好地解释了它:https://tkdodo.eu/blog/hooks-dependencies-and-stale-closures

相关问题