我的反应代码中有一个奇怪的行为:
编辑:事实证明清除功能正在工作并且成功清除了间隔。现在我面临严格模式双重调用的另一个问题。我在下面附上了完整的应用程序代码,另一个设置函数
setRow()
内的设置函数setSecondPassed()
每个间隔被调用3次,其中一次该值已更新另一次。我假设在双重调用下,如果我的代码写得好,它们应该是幂等的,但在每个间隔内,rows
的状态更新了两次。
import { useState, useEffect, useMemo, useRef } from "react";
function App() {
return (
<>
<Timer />
</>
);
}
function Timer() {
const [secondsPassed, setSecondPassed] = useState(0);
const [rows, setRow] = useState(0);
const thresold = 10;
function onInterval() {
console.log("onInterval timer update"); // once per interval
setSecondPassed((prev) => {
const next = prev + 1;
console.log(`next: ${next}`); // twice per interval
setRow((prevRows) => {
const nextRows = prevRows + 1;
console.log(`setRow: ${nextRows}`); // 3 times per interval
return nextRows;
});
return next;
});
}
useEffect(() => {
const intervalId = setInterval(onInterval, 1000);
console.log(`timer setinterval: ${intervalId}`);
return () => {
console.log(`timer clearInterval: ${intervalId}`);
clearInterval(intervalId);
};
}, []);
const fixedRows = useMemo(() => {
console.log(`fixedRows: ${rows}`);
return Array.from({ length: rows }).map((_, i) => (
<div key={i} className="box-x" style={{ width: `${thresold}px` }}></div>
));
}, [rows]);
return (
<>
{fixedRows}
<div className="box-x" style={{ width: secondsPassed % thresold }}></div>
</>
);
}
export default App;
我什至把代码放在codesandbox.io 中。日志仍然显示设置函数
setRow()
每个时间间隔被调用 3 次。从React文档来看,严格模式下应该有双重调用。我不知道为什么它被调用了 3 次。另外,我希望我的代码尽可能具有幂等性,以便严格模式双重调用的结果不会影响渲染结果。任何建议表示赞赏。
fixedRows: 0
preview-protocol.js:56 fixedRows: 0
App.jsx:30 timer setinterval: 17
App.jsx:33 timer clearInterval: 17
App.jsx:30 timer setinterval: 20
index.js:8 onInterval timer update
index.js:8 next: 1
index.js:8 setRow: 1
index.js:8 fixedRows: 1
index.js:8 fixedRows: 1
index.js:8 onInterval timer update
index.js:8 next: 2
index.js:8 setRow: 2
index.js:8 fixedRows: 2
index.js:8 next: 2
index.js:8 setRow: 2
index.js:8 setRow: 3 <- extra state update here
index.js:8 fixedRows: 3
index.js:8 onInterval timer update
index.js:8 next: 3
index.js:8 setRow: 4
index.js:8 fixedRows: 4
index.js:8 next: 3
index.js:8 setRow: 4
index.js:8 setRow: 5 <- extra state update here
index.js:8 fixedRows: 5
环境:
v22.3.0
5.4.1
18.3.1
1.93.1
127.0.6533.120 arm64
请查看依赖项数组,它现在是空的。这意味着挂钩内的代码将仅在两个事件上运行 - 组件的安装和卸载。挂钩内的代码将在挂载时运行,而该代码返回的代码将在卸载时运行。这就是clearInterval 现在只能工作一次的原因。然而,这段代码运行良好,并给出了正确的结果。
useEffect(() => {
console.log("effect start");
...
}, []);
如果您仍然想了解如何强制执行clearInterval,请参阅下面的其中一种方法。
操作步骤:
a) 请向挂钩添加依赖项。
b) onInterval 函数必须移出钩子。 然后将其设置为其依赖项。
c) 现在会发生的是,在每次渲染时,函数 onInterval 将被重新定义并产生一个新的功能对象。
d) 这将导致钩子中的依赖被识别为 改变。
e) 这将导致重新同步操作。
f) 每个重新同步操作都将从执行 返回的函数,然后继续处理钩子的处理程序。
App.js
import { useEffect, useState } from 'react';
export default function Timer() {
const [secondsPassed, setSecondPassed] = useState(0);
const [rows, setRow] = useState(1);
const thresold = 60;
function onInterval() {
setSecondPassed((prevSec) => {
let currentSecond = prevSec + 1;
console.log(currentSecond);
if (currentSecond % thresold === 0) {
setRow((prevRow) => prevRow + 1);
}
return currentSecond;
});
}
useEffect(() => {
console.log('effect start');
const intervalId = setInterval(onInterval, 1000);
console.log(`setinterval: ${intervalId}`);
return () => {
console.log(`clearInterval: ${intervalId}`);
clearInterval(intervalId);
};
}, [onInterval]);
return (
<>
<div className="box-x" style={{ width: secondsPassed % thresold }}>
seconds passed : {secondsPassed}
</div>
</>
);
}
试运行
与另一个答案一样,由于依赖项数组为空,以下代码中的 useEffect 挂钩也仅适用于两个事件。您可以检查一下,console.log('clearInterval') 语句仅在卸载时执行 - 您可以通过页面刷新来模拟该事件)
import { useState, useEffect } from 'react';
export default function Clock() {
const [now, setNow] = useState(new Date());
useEffect(() => {
const intervalId = setInterval(() => {
setNow(new Date());
}, 1000);
return () => {
console.log('clearInterval');
clearInterval(intervalId);
};
}, []);
return <h2>Time now is {now.getSeconds()}</h2>;
}