hos*_*fti 105 javascript reactjs next.js
我有一个计数器和一个console.log()来useEffect记录我状态中的每个变化,但是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;
Run Code Online (Sandbox Code Playgroud)
You*_*mar 186
useEffect从 React 版本 18 开始,当您使用StrictMode. 以下是文档中原因的概述:
\n\n将来,我们\xe2\x80\x99d 希望添加一个功能,允许 React 在保留状态的同时添加和删除 UI 部分。例如,当用户离开屏幕并返回时,React 应该能够立即显示前一个屏幕。为此,React 将支持使用卸载前使用的相同组件状态重新挂载树。
\n
\n\n此功能将为 React 提供更好的开箱即用性能,但要求组件能够适应多次安装和销毁的效果。大多数效果无需任何更改即可工作,但某些效果无法在销毁回调中正确清理订阅,或者隐式假设它们仅安装或销毁一次。
\n
\n\n为了帮助解决这些问题,React 18 引入了针对严格模式的新的仅开发检查。每当第一次安装组件时,这项新检查都会自动卸载并重新安装每个组件,并在第二次安装时恢复之前的状态。
\n
\n\n这仅适用于开发模式,生产行为不变。
\n
这看起来很奇怪,但最终,我们通过缓存 HTTP 请求,并在有两个调用时使用清理函数来编写更好的 React 代码,无错误,符合当前指南,并与未来版本兼容。一个问题。这是一个例子:
\n/* Having a setInterval inside an useEffect: */\n\nimport { useEffect, useState } from "react";\n\nconst Counter = () => {\n const [count, setCount] = useState(0);\n\n useEffect(() => {\n const id = setInterval(() => setCount((count) => count + 1), 1000);\n\n /* \n Make sure I clear the interval when the component is unmounted,\n otherwise, I get weird behavior with StrictMode, \n helps prevent memory leak issues.\n */\n return () => clearInterval(id);\n }, []);\n\n return <div>{count}</div>;\n};\n\nexport default Counter;\nRun Code Online (Sandbox Code Playgroud)\n在这篇非常详细的文章中,React 团队useEffect以前所未有的方式进行了解释并讲述了一个示例:
\n\n这说明如果重新安装破坏了应用程序的逻辑,通常会发现现有的错误。从用户\xe2\x80\x99s 的角度来看,访问页面应该与访问页面、单击链接然后按“返回”不同。React 通过在开发过程中重新安装组件来验证您的组件是否不会违反此原则。
\n
对于您的特定用例,您可以保持原样,无需担心。并且您不应该尝试将这些技术与useRefandif语句一起使用useEffect以使其触发一次或删除StrictMode,因为正如您可以在文档中阅读的那样:
\n\nReact 会有意地重新安装开发中的组件,以帮助您发现错误。正确的问题是\xe2\x80\x99t \xe2\x80\x9chow 运行一次效果\xe2\x80\x9d,但是\xe2\x80\x9chow 修复我的效果,以便它在重新安装\xe2\x80\ 后工作。 x9d。
\n通常,答案是实现清理功能。清理函数应该停止或撤消 Effect 正在执行的任何操作。经验法则是,用户应该\xe2\x80\x99t能够区分运行一次的效果(如在生产中)和设置\xe2\x86\x92清理\xe2\x86\x92设置序列(如你\xe2\x80\x99d 参见开发中)。
\n
/* As a second example, an API call inside an useEffect with fetch: */\n\nuseEffect(() => {\n const abortController = new AbortController();\n\n const fetchUser = async () => {\n try {\n const res = await fetch("/api/user/", {\n signal: abortController.signal,\n });\n const data = await res.json();\n } catch (error) {\n // \xe2\x84\xb9\xef\xb8\x8f: The error name is "CanceledError" for Axios.\n if (error.name !== "AbortError") {\n /* Logic for non-aborted error handling goes here. */\n }\n }\n };\n\n fetchUser();\n\n /* \n Abort the request as it isn\'t needed anymore, the component being \n unmounted. It helps avoid, among other things, the well-known "can\'t\n perform a React state update on an unmounted component" warning.\n */\n return () => abortController.abort();\n}, []);\nRun Code Online (Sandbox Code Playgroud)\n\n\n您可以\xe2\x80\x99t \xe2\x80\x9cundo\xe2\x80\x9d 已经发生的网络请求,但是您的清理函数应该确保\xe2\x80\x99s 不再相关的提取不会继续影响你的申请。
\n
\n\n开发中,您将在“网络”选项卡中看到两次提取。没有什么不妥。通过上述方法,第一个 Effect 将立即被清理...因此,即使有额外的请求,由于中止,它也不会\xe2\x80\x99 影响状态。
\n
\n\n生产中,只会有一个请求。如果开发中的第二个请求困扰您,最好的方法是使用一种解决方案,该解决方案可以删除重复的请求并在组件之间缓存其响应:
\n
function TodoList() {\n const todos = useSomeDataFetchingLibraryWithCache(`/api/user/${userId}/todos`);\n // ...\nRun Code Online (Sandbox Code Playgroud)\n如果您仍然遇到问题,也许您正在使用useEffect不应该使用的地方,正如他们在“不是效果:初始化应用程序”和“不是效果:购买产品”中所说的那样。我建议你阅读这篇文章整篇
dan*_*dan 19
While I agree with the points raised in the accepted answer,
If one still needs to use useEffect
Then, using the useRef() to control the flow is an option.
To apply the effect ONLY on the FIRST mount:
const effectRan = useRef(false);
useEffect(() => {
if (!effectRan.current) {
console.log("effect applied - only on the FIRST mount");
}
return () => effectRan.current = true;
}, []);
Run Code Online (Sandbox Code Playgroud)
To apply the effect on the REmount:
const effectRan = useRef(false);
useEffect(() => {
if (effectRan.current || process.env.NODE_ENV !== "development") {
console.log("effect applied - on the REmount");
}
return () => effectRan.current = true;
}, []);
Run Code Online (Sandbox Code Playgroud)
When is this useful?
One application is where the useEffect contains a server request that can potentially change the state of the backend (e.g. DB change). In that case, unintended (duplicate) server requests due to StrictMode can lead to unforeseen results.
Can't we cancel the request via AbortController?
Yes, we can cancel a request. However, by the time the cancel gets invoked, the request might have already run to completion, if not altered number of backend states. Abort does not guarantee a sound rest, at least not out of the box. Therefore, (I think) a request that is NOT intended; should not be attempted in the very first place.