问题描述
以下代码在 codeandBox.io 网站的控制台(该版本使用 StrictMode
)以及下面的代码段(不使用 {{1} }):
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"));
演示:https://codesandbox.io/s/gallant-bas-3lq5w?file=/src/App.js(使用 <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>
)
这是一个使用生产库的片段;它仍然记录两次:
StrictMode
const { useState,document.getElementById("root"));
然而,当我打开开发者控制台时,我看到每次只打印一次,而且在 codeandBox.io 的控制台中,我看到它打印一次。
然后如果我使用 create-react-app 创建一个独立的 React 应用程序,并使用上面的代码,并且每次都打印两次。
如何理解这种行为,根据不同情况打印一次或两次?我的想法是,如果状态发生变化,那么 <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>
将使用新字符串重新呈现,一次,因此它会被打印一次。特别奇怪的是为什么打印了两次,打开Dev Console就打印一次?
解决方法
据我所知,我们看到对 App
的双重调用是预期行为。来自useState
documentation:
退出状态更新
如果您将 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
请注意,尽管 App
的每个值都被调用了两次,但 timeString
只被调用了一次。
我应该注意到,这比使用 ShowTime
组件时更自动化。如果您没有实现 shouldComponentUpdate
,等效的 class
组件将每秒更新 10 次。 :-)
您在 CodeSandbox 上看到的一些内容很可能是由于 class
,更多关于 here。但是对于每个 StrictMode
值对 App
的两次调用只是 React 做的事情。