【发布时间】:2020-12-04 09:52:23
【问题描述】:
以下示例是一个 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 调用中,“秒”值将始终等于最后一次渲染 useEffect 块时(最后一次更改 isActive 时)的值。这将导致setDoubleSeconds(seconds * 2) 语句失败。 React Hooks ESLint 插件给了我一个关于这个问题的警告:
React Hook useEffect 缺少依赖项:'seconds'。包括它或删除依赖数组。你也可以换 如果'setDoubleSeconds',使用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()”?我是否将“秒”和“双秒”合并到一个状态对象中?我使用 refs 吗?
另外,您可能会想“为什么不简单地将 <h3>Seconds x2: {doubleSeconds}</h3> 更改为 <h3>Seconds x2: {seconds * 2}</h3> 并删除 'doubleSeconds' 状态?”。在我的实际应用程序中,doubleSeconds 被传递给子组件,而我没有希望 Child 组件知道如何将秒映射到 doubleSeconds,因为这会降低 Child 的可重用性。
谢谢!
【问题讨论】:
标签: reactjs react-hooks