为什么使用Hooks的ReactJS组件会渲染一到两次,这取决于开发人员控制台是否打开



以下代码在codesandbox.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"));
<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>

演示: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"));
<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>

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

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

如何理解这种行为,根据不同的情况打印一两次?我的想法是,如果状态发生变化,那么App将被重新呈现,并使用新字符串一次,因此它将被打印出来一次。奇怪的是,尤其是为什么它打印了两次,但当开发控制台打开时,它是一次?

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

退出状态更新

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

请注意,React可能仍然需要在退出之前再次渲染该特定组件。这不应该是一个问题,因为React不会不必要地"深入"到树中。如果在渲染时进行昂贵的计算,可以使用useMemo对其进行优化。

那里的关键位是"请注意,React可能仍然需要在退出之前再次渲染该特定组件">";。。。React不会不必要地深入到树中">

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

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 ShowTime({timeString}) {
console.log("ShowTime", timeString);
return <div className="App">{timeString}</div>;
}
function App() {
const s = useCurrentTime();
console.log("App", s);

return <ShowTime timeString={s} />;
}
ReactDOM.render(<App />, document.getElementById("root"));
<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>

当我运行它时,我看到:

App 09:57:14
ShowTime 09:57:14
App 09:57:14
App 09:57:15
ShowTime 09:57:15
App 09:57:15
App 09:57:16
ShowTime 09:57:16
App 09:57:16
App 09:57:17
ShowTime 09:57:17
App 09:57:17
App 09:57:18
ShowTime 09:57:18
App 09:57:18
App 09:57:19
ShowTime 09:57:19
App 09:57:19
App 09:57:20
ShowTime 09:57:20
App 09:57:20

注意,尽管对于timeString的每个值App被调用两次,但ShowTime仅被调用一次。

我应该注意到,这比使用class组件时更加自动化。如果不实现shouldComponentUpdate,等效的class组件将每秒更新10次。:-)

您在CodeSandbox上看到的一些内容很可能是由于StrictMode,更多信息请点击此处。但对于每个timeString值,对App的两个调用只是React在做它的事情。

最新更新