我有一个计数器和 useEffect
中的 console.log()
来记录我的状态中的每个更改,但是 useEffect
在挂载时被调用两次。我正在使用 React 18。这是我的项目的 CodeSandbox 和下面的代码:
import { useState, useEffect } from "react"; const Counter = () => { const [count, setCount] = useState(5); useEffect(() => { console.log("rendered", count); }, [count]); return ( <div> <h1> Counter </h1> <div> {count} </div> <button onClick={() => setCount(count + 1)}> click to increase </button> </div> ); }; export default Counter;
P粉1565327062023-10-20 11:55:03
更新:回顾一下这篇文章,稍微明智一点,请不要这样做。
使用ref
或创建一个没有的自定义hook
。
export const useClassicEffect = createClassicEffectHook(); function createClassicEffectHook() { if (import.meta.env.PROD) return React.useEffect; return (effect: React.EffectCallback, deps?: React.DependencyList) => { React.useEffect(() => { let isMounted = true; let unmount: void | (() => void); queueMicrotask(() => { if (isMounted) unmount = effect(); }); return () => { isMounted = false; unmount?.(); }; }, deps); }; }
P粉4594409912023-10-20 09:39:10
自 React 18 起,当您使用 StrictMode
进行开发
时,
useEffect
在挂载时被调用两次是正常的。以下是他们在 文档:
这看起来很奇怪,但最终,我们通过缓存 HTTP 请求并在有两个调用时使用清理函数来编写更好的 React 代码,无错误,符合当前指南,并与未来版本兼容一个问题。这是一个例子:
/* Having a setInterval inside an useEffect: */
import { useEffect, useState } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount((count) => count + 1), 1000);
/*
Make sure I clear the interval when the component is unmounted,
otherwise, I get weird behavior with StrictMode,
helps prevent memory leak issues.
*/
return () => clearInterval(id);
}, []);
return {count};
};
export default Counter;
在这篇非常详细的文章中,React 团队解释了 useEffect
前所未有并举例说明:
对于您的特定用例,您可以保持原样,无需担心。并且您不应该尝试将这些技术与 useEffect
中的 useRef
和 if
语句一起使用以使其触发一次,或删除 StrictMode
,因为正如您可以在 文档:
/* As a second example, an API call inside an useEffect with fetch: */
useEffect(() => {
const abortController = new AbortController();
const fetchUser = async () => {
try {
const res = await fetch("/api/user/", {
signal: abortController.signal,
});
const data = await res.json();
} catch (error) {
// ℹ️: The error name is "CanceledError" for Axios.
if (error.name !== "AbortError") {
/* Logic for non-aborted error handling goes here. */
}
}
};
fetchUser();
/*
Abort the request as it isn't needed anymore, the component being
unmounted. It helps avoid, among other things, the well-known "can't
perform a React state update on an unmounted component" warning.
*/
return () => abortController.abort();
}, []);
function TodoList() { const todos = useSomeDataFetchingLibraryWithCache(`/api/user/${userId}/todos`); // ...
如果您仍然遇到问题,也许您正在使用 useEffect
,正如他们在 useEffect /learn/synchronizing-with-effects#not-an-effect-initializing-the-application" rel="noreferrer">不是效果:初始化应用程序 和 不是效果:购买产品,我建议您阅读文章作为一个整体。