我有一个计数器和 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; Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号
更新:回顾一下这篇文章,稍微明智一点,请不要这样做。
使用
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); }; }自 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,正如他们在 不是效果:初始化应用程序 和 不是效果:购买产品,我建议您阅读文章作为一个整体。