useState 钩子神秘地重新执行,没有任何痕迹

useState hook mysteriously re-executed without a trace

这个问题可能与钩子无关,而是与闭包有关,但我完全不知道它是如何工作的。

我正在编写自定义挂钩程序,并试图为组件中的每个调用点获取唯一 ID。为此,我使用 useState(() => counter++),其中 counter 每个组件“实例化”一次。

或者这就是理论。实际代码做了一些我一生都无法理解的事情。

以下是此行为的最小可验证示例:

const { useState } = require("react");

let counter = 0;
const useFoo = () => {
  const [id] = useState(() => {
    counter++;
    console.log("indide useState, counter:", counter); // (1)
    return counter;
  });
  console.log("inside useFoo, id:", id); // (2)
  return id;
};

export const Child = () => {
  const id = useFoo();
  return (
    <button onClick={() => console.log("id:", id, "counter:", counter)}>
      Click me!
    </button>
  );
};

useFoo 挂钩执行以下操作:

Child 组件执行以下操作:

在不点击任何东西的情况下,useFoo 中的两个 console.log 都会按预期执行,并在控制台打印以下内容:

indide useState, counter: 1
inside useFoo, id: 1

现在当您按下按钮时,您会期望 Child 内的 id1,对吧?错了。

单击按钮时的输出如下:

id: 2 counter: 2

Here's link 到演示此行为的代码框。

为什么会这样?

特别是,counter 如何在没有执行 (1)(2) 的情况下递增?

谢谢!

有副作用的代码必须放在useEffect中。这样做的原因是 而不是 只是因为直接作为函数体的一部分编写的代码可以在任意时刻执行 - 但 state 甚至可能是这种情况在 useEffect 之外可能看起来不一致。

我不知道如何创建“增量”id,但是通过将所有与副作用相关的代码放入 useEffect,您至少对 [= 返回的值有一个一致的看法16=],而useState返回的值肯定是唯一的。这是一个例子:

const { useState, useEffect } = require("react");

let counter = 0;
const useFoo = () => {
  const [id] = useState(() => {
    counter++;
    return counter;
  });

  useEffect(() => console.log("useState returned:", id), []);
  return id;
};

export const Child = () => {
  const id = useFoo();
  return <button onClick={() => console.log("id:", id)}>Click me!</button>;
};

这将输出

useState returned: 2
id: 2

useFoo的函数体应该是纯的; React 仅保证 useEffect 中的函数以及最终呈现的组件将具有一致的组件状态视图。