为什么使用 Hooks 的 ReactJS 组件会根据开发者控制台是否打开而呈现一次或两次?

nop*_*ole 6 javascript reactjs codesandbox react-hooks

以下代码在 codeandbox.io 网站的控制台(该版本使用StrictMode)以及下面的代码段(使用StrictMode)中打印出相同的时间两次:

const { useState, useEffect } = React;

function useCurrentTime() {
  const [timeString, setTimeString] = useState("");

  useEffect(() => {
    const intervalID = setInterval(() => {
      setTimeString(new Date().toLocaleTimeString());
    }, 100);
    return () => clearInterval(intervalID);
  }, []);

  return timeString;
}

function App() {
  const s = useCurrentTime();
  console.log(s);

  return <div className="App">{s}</div>;
}

ReactDOM.render(<App />, document.getElementById("root"));
Run Code Online (Sandbox Code Playgroud)
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.development.js"></script>
Run Code Online (Sandbox Code Playgroud)

演示:https : //codesandbox.io/s/gallant-bas-3lq5w?file=/ src/ App.js(使用StrictMode

这是使用生产库的片段;它仍然记录两次:

const { useState, useEffect } = React;

function useCurrentTime() {
  const [timeString, setTimeString] = useState("");

  useEffect(() => {
    const intervalID = setInterval(() => {
      setTimeString(new Date().toLocaleTimeString());
    }, 100);
    return () => clearInterval(intervalID);
  }, []);

  return timeString;
}

function App() {
  const s = useCurrentTime();
  console.log(s);

  return <div className="App">{s}</div>;
}

ReactDOM.render(<App />, document.getElementById("root"));
Run Code Online (Sandbox Code Playgroud)
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

然而,当我打开开发者控制台时,我看到每次只打印一次,而且在 codeandbox.io 的控制台中,我看到它打印了一次。

然后如果我使用 create-react-app 创建一个独立的 React 应用程序,并使用上面的代码,并且每次都打印两次。

如何理解这种行为,根据不同情况打印一次或两次?我的想法是,如果状态发生变化,则App使用新字符串重新渲染一次,因此它会打印一次。特别奇怪的是为什么它打印了两次,但是当 Dev Console 打开时,它是一次?

T.J*_*der 3

据我所知,我们看到的双重调用App是预期的行为。从useState文档中:

\n
\n

摆脱状态更新

\n

如果将 State Hook 更新为与当前状态相同的值,React 将退出而不渲染子项或触发效果。(React 使用Object.is比较算法。)

\n

请注意,React 可能仍需要在退出之前再次渲染该特定组件。这不应该成为一个问题,因为 React 不会不必要地将 \xe2\x80\x9cdeeper\xe2\x80\x9d 放入树中。如果您\xe2\x80\x99在渲染时进行昂贵的计算,您可以使用useMemo.

\n
\n

其中的关键位是“请注意,React 可能仍需要在退出之前再次渲染该特定组件...”“...React won\xe2\x80\x99t 不必要地转到 \xe2\x80\x9cdeeper\xe2\x80 \x9d 进入树中...”

\n

事实上,如果我更新您的示例,以便它使用子组件来显示时间字符串,我们只会看到每个值调用子组件一次timeString,而不是按原样调用两次App,即使子组件没有包装在React.memo

\n

\r\n
\r\n
const { useState, useEffect } = React;\n\nfunction useCurrentTime() {\n    const [timeString, setTimeString] = useState("");\n  \n    useEffect(() => {\n        const intervalID = setInterval(() => {\n            setTimeString(new Date().toLocaleTimeString());\n        }, 100);\n        return () => clearInterval(intervalID);\n    }, []);\n  \n    return timeString;\n}\n\nfunction ShowTime({timeString}) {\n    console.log("ShowTime", timeString);\n    return <div className="App">{timeString}</div>;\n}\n\nfunction App() {\n    const s = useCurrentTime();\n    console.log("App", s);\n  \n    return <ShowTime timeString={s} />;\n}\n\nReactDOM.render(<App />, document.getElementById("root"));
Run Code Online (Sandbox Code Playgroud)\r\n
<div id="root"></div>\n\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

当我运行它时,我看到:

\n
App 09:57:14\nShowTime 09:57:14\nApp 09:57:14\nApp 09:57:15\nShowTime 09:57:15\nApp 09:57:15\nApp 09:57:16\nShowTime 09:57:16\nApp 09:57:16\nApp 09:57:17\nShowTime 09:57:17\nApp 09:57:17\nApp 09:57:18\nShowTime 09:57:18\nApp 09:57:18\nApp 09:57:19\nShowTime 09:57:19\nApp 09:57:19\nApp 09:57:20\nShowTime 09:57:20\nApp 09:57:20\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,虽然App的每个值被调用两次timeStringShowTime但仅被调用一次。

\n

我应该指出,这比class组件更自动化。class如果您没有实现,等效组件将每秒更新 10 次shouldComponentUpdate。:-)

\n

您在 CodeSandbox 上看到的一些内容很可能是由于此处StrictMode的原因造成的。但是对每个值的两次调用只是 React 在做它的事情。ApptimeString

\n