下面的示例是一个 Timer 组件,它有一个按钮(用于启动计时器)和两个显示经过秒数和经过秒数乘以 2 的标签。
但是,它不起作用(CodeSandbox Demo)
代码
import React, { useState, useEffect } from "react";
const Timer = () => {
const [doubleSeconds, setDoubleSeconds] = useState(0);
const [seconds, setSeconds] = useState(0);
const [isActive, setIsActive] = useState(false);
useEffect(() => {
let interval = null;
if (isActive) {
interval = setInterval(() => {
console.log("Creating Interval");
setSeconds((prev) => prev + 1);
setDoubleSeconds(seconds * 2);
}, 1000);
} else {
clearInterval(interval);
}
return () => {
console.log("Destroying Interval");
clearInterval(interval);
};
}, [isActive]);
return (
<div className="app">
<button onClick={() => setIsActive((prev) => !prev)} type="button">
{isActive ? "Pause Timer" : "Play Timer"}
</button>
<h3>Seconds: {seconds}</h3>
<h3>Seconds x2: {doubleSeconds}</h3>
</div>
);
};
export { Timer as default };
问题
在 useEffect 调用中,“seconds”值将始终等于上次渲染 useEffect 块时(上次更改 isActive 时)的值。这将导致setDoubleSeconds(seconds * 2)语句失败。React Hooks ESLint 插件给了我一个关于这个问题的警告,内容如下:
React Hook useEffect 缺少依赖项:'seconds'。包括它或删除依赖项数组。如果“setDoubleSeconds”需要“seconds”的当前值,您还可以用 useReducer 替换多个 useState 变量。(react-hooks/exhaustive-deps)eslint
正确的是,将“秒”添加到依赖项数组(并更改setDoubleSeconds(seconds * 2)为setDoubleSeconds((seconds + 1) * )将呈现正确的结果。然而,这有一个令人讨厌的副作用,会导致在每次渲染时创建和销毁间隔(每次渲染时console.log("Destroying Interval")触发)。
所以现在我正在查看 ESLint 警告中的另一个建议“如果‘setDoubleSeconds’需要‘seconds’的当前值,您也可以用 useReducer 替换多个 useState 变量”。
我不明白这个建议。如果我创建一个减速器并像这样使用它:
import React, { useState, useEffect, useReducer } from "react";
const reducer = (state, action) => {
switch (action.type) {
case "SET": {
return action.seconds;
}
default: {
return state;
}
}
};
const Timer = () => {
const [doubleSeconds, dispatch] = useReducer(reducer, 0);
const [seconds, setSeconds] = useState(0);
const [isActive, setIsActive] = useState(false);
useEffect(() => {
let interval = null;
if (isActive) {
interval = setInterval(() => {
console.log("Creating Interval");
setSeconds((prev) => prev + 1);
dispatch({ type: "SET", seconds });
}, 1000);
} else {
clearInterval(interval);
}
return () => {
console.log("Destroying Interval");
clearInterval(interval);
};
}, [isActive]);
return (
<div className="app">
<button onClick={() => setIsActive((prev) => !prev)} type="button">
{isActive ? "Pause Timer" : "Play Timer"}
</button>
<h3>Seconds: {seconds}</h3>
<h3>Seconds x2: {doubleSeconds}</h3>
</div>
);
};
export { Timer as default };
过时值的问题仍然存在(CodeSandbox Demo (using Reducers))。
问题
那么对于这个场景有什么建议呢?我是否会降低性能并简单地将“秒”添加到依赖项数组中?我是否创建另一个依赖于“秒”的 useEffect 块并在其中调用“setDoubleSeconds()”?我是否将“seconds”和“doubleSeconds”合并为一个状态对象?我使用 refs 吗?
此外,您可能会想“为什么不简单地将<h3>Seconds x2: {doubleSeconds}</h3>“更改为<h3>Seconds x2: {seconds * 2}</h3>并删除 'doubleSeconds' 状态?”。在我的实际应用程序中,doubleSeconds 被传递给子组件,我不希望子组件知道秒是多少映射到 doubleSeconds,因为它使 Child 的可重用性降低。
谢谢!