useEffect 中的嵌套 set 函数在严格模式下调用了 3 次

问题描述 投票:0回答:2

我的反应代码中有一个奇怪的行为:

编辑:事实证明清除功能正在工作并且成功清除了间隔。现在我面临严格模式双重调用的另一个问题。我在下面附上了完整的应用程序代码,另一个设置函数

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
  • vscode:
    1.93.1
  • 铬:
    127.0.6533.120 arm64
javascript reactjs react-hooks
2个回答
0
投票

请查看依赖项数组,它现在是空的。这意味着挂钩内的代码将仅在两个事件上运行 - 组件的安装和卸载。挂钩内的代码将在挂载时运行,而该代码返回的代码将在卸载时运行。这就是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>
    </>
  );
}

试运行

Browser display


0
投票

与另一个答案一样,由于依赖项数组为空,以下代码中的 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>;
}
© www.soinside.com 2019 - 2024. All rights reserved.