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
挂钩执行以下操作:
- 它使用
useState(()=>counter++)
为调用站点“生成”一个唯一的 ID。它还在增量后打印出 counter
的值。
- 它 returns 生成的 id,在将
id
打印到控制台后
Child
组件执行以下操作:
- 它调用
const id = useFoo()
- 它 returns 一个按钮,单击该按钮将打印出
id
以及 counter
的值
在不点击任何东西的情况下,useFoo
中的两个 console.log
都会按预期执行,并在控制台打印以下内容:
indide useState, counter: 1
inside useFoo, id: 1
现在当您按下按钮时,您会期望 Child
内的 id
为 1
,对吧?错了。
单击按钮时的输出如下:
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
中的函数以及最终呈现的组件将具有一致的组件状态视图。
这个问题可能与钩子无关,而是与闭包有关,但我完全不知道它是如何工作的。
我正在编写自定义挂钩程序,并试图为组件中的每个调用点获取唯一 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
挂钩执行以下操作:
- 它使用
useState(()=>counter++)
为调用站点“生成”一个唯一的 ID。它还在增量后打印出counter
的值。 - 它 returns 生成的 id,在将
id
打印到控制台后
Child
组件执行以下操作:
- 它调用
const id = useFoo()
- 它 returns 一个按钮,单击该按钮将打印出
id
以及counter
的值
在不点击任何东西的情况下,useFoo
中的两个 console.log
都会按预期执行,并在控制台打印以下内容:
indide useState, counter: 1
inside useFoo, id: 1
现在当您按下按钮时,您会期望 Child
内的 id
为 1
,对吧?错了。
单击按钮时的输出如下:
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
中的函数以及最终呈现的组件将具有一致的组件状态视图。