首页IT科技react中effect(React18的useEffect会执行两次)

react中effect(React18的useEffect会执行两次)

时间2025-08-03 02:05:08分类IT科技浏览11802
导读:一、执行两次的useEffect。...

一                、执行两次的useEffect                。

前段时间在本地启了一个 React Demo 项目                 ,在编码的过程中遇到一个很奇怪的“Bug                 ”                          。

其中简化版的代码如下所示         。 // 入口文件 import { StrictMode } from react; import * as ReactDOMClient from react-dom/client; import App from ./App; const root = ReactDOMClient.createRoot(document.getElementById(root)); root.render( <StrictMode> <App /> </StrictMode> ); // 组件代码 import React, { useEffect } from react; const App = () => { useEffect(() => { console.log(组件挂载完成!); }, []); return <>Hello world!</>; };

我是万万没想到                         ,就这样几行简单的代码竟然会触发一个“Bug                         ”                。

此“Bug         ”的表现为:在 Chrome 控制台里发现 “Hello world!        ” 被打印了 “两次                         ”                         。

刷新之后依然如此         ,当时就给我整懵了        ,第一感觉就是                         ,这怎么可能?

很是纠结一番之后依然没想明白                 ,于是试着去网上搜了一下        ,发现竟然有人同样遇到过这个问题         。

通过网上指引                         ,同时去官网查了一下                 ,终于得出答案        。

这不是 Bug,这是 React18 新加的特性                         。

二                          、React18 useEffect 新特性

1.这是 React18 才新增的特性                 。 2.仅在开发模式("development")下                         ,且使用了严格模式("Strict Mode")下会触发        。 生产环境("production")模式下和原来一样                         ,仅执行一次                         。 3.之所以执行两次,是为了模拟立即卸载组件和重新挂载组件                 。 为了帮助开发者提前发现重复挂载造成的 Bug 的代码。 同时                 ,也是为了以后 React的新功能做铺垫                         。 未来会给 React 增加一个特性                         ,允许 React 在保留状态的同时         ,能够做到仅仅对UI部分的添加和删除                          。 让开发者能够提前习惯和适应                 ,做到组件的卸载和重新挂载之后                         , 重复执行 useEffect的时候不会影响应用正常运行。

如何应对

看过文档以及了解他们这么做的本意之后         ,我也能够理解他们会这样做了                。

只是        ,对于这种半强迫式操作多少有些不喜欢                         ,感觉是在代码中                 ”被强迫打一针疫苗?        ”                          。

当然                 ,人家就是这么干了        ,作为 React 的普通使用者                         ,能做的就是 适应它                  ,并按照它的规范来做         。

1.首先先了解一下 React 中 useEffect 执行的时机

Every time your component renders, React will update the screen and then run the code inside useEffect.

每次组件渲染时,React 都会更新页面 UI                         ,然后运行 useEffect 中的代码                。

Effects run at the end of the rendering process after the screen updates

Effect 在屏幕更新之后的 rendering 进程结束的时候执行                         。

从上面可以得出结论                         ,React 中的 useEffect 执行时机是在组件渲染之后(类似于 window(component).onload ?)         。

因此,对于某些“副作用                         ”的渲染                 ,比如异步接口请求                         ,事件绑定等操作我们通常都放在 useEffect 中执行        。

当然         ,useEffect 除了在组件渲染的时候执行外                 ,在组件卸载的时候也有相关执行操作                         。

在组件卸载的时候会执行 useEffect 方法的return语句                 。 useEffect(() => { window.a = 100; return (window.a = 0); }, []);

如上代码段                         ,当组件渲染的时候会执行window.a = 100         ,当组件卸载的时候会执行window.a = 0        。

知道了 useEffect 的执行时机        ,也就能明白为什么 React18 中 useEffect 会执行两次了                         。

因为                         , React18 在开发环境中除了必要的挂载之外                 ,还 "额外"模拟执行了一次组件的卸载和挂载                 。

既然知道了原因        ,那么                         ,接下来就是想办法解决了。

2.怎么样才能让 Effect 执行一次?                         。

对于这个问题                 ,官方文档上面有一句原话:The right question isn’t “how to run an Effect once,                 ” but “how to fix my Effect so that it works after remounting”.翻译一下,就是说:正确的问题不是“怎么样让 Effect 执行一次                         ”                         ,而是“怎样修复我的 Effect                         ,让它在(重复)挂载之后正常工作                         ”

也可以理解,毕竟在 React 的未来版本中做离屏渲染的时候 useEffect 肯定会多次执行的                          。

而且                 ,即使是当前版本                         ,在做页面的前进后退也会面临触发多次 useEffect。

所以         ,解决办法其实就是解决 重复挂载卸载之后 应用正常工作了                。

###3.具体的解决方法

我们知道 useEffect 支持返回一个函数                 ,在组件卸载的时候就会执行该函数                          。

因此                         ,通常正确解法就是 实现清理函数         ,并将其在 useEffect 中返回         。

当然        ,不同的 Effect 需要有不同的清理方式                。

在常用 Effect 分类下                         ,大致有如下几类清理                         。

1)清理事件监听 useEffect(() => { function handleScroll(e) { console.log(e.clientX, e.clientY); } window.addEventListener(scroll, handleScroll); return () => window.removeEventListener(scroll, handleScroll); }, []);

对于事件监听类函数                 ,在返回函数内部“取消掉事件监听”即可         。

2-1)重置页面数据        ,清理属性状态 useEffect(() => { const node = ref.current; node.style.opacity = 1; // Trigger the animation return () => { node.style.opacity = 0; // Reset to the initial value }; }, []);

对于一些页面属性的变更                         ,在返回函数内部将其变更的属性进行还原        。

2-2)重置页面数据                 ,还原元素状态 import { useEffect, useRef } from react; function VideoPlayer({ src, isPlaying }) { const ref = useRef(null); useEffect(() => { if (isPlaying) { ref.current.play(); } else { ref.current.pause(); } }); return <video ref={ref} src={src} loop playsInline />; }

涉及到元素状态的,比如播放器之类                         ,需要对(元素)播放器的状态进行重置                         。

2-3)重置页面数据                         ,弹窗类                 。 useEffect(() => { const dialog = dialogRef.current; dialog.showModal(); return () => dialog.close(); }, []);

如果是默认弹窗类,这种也算是元素状态                 ,同样需要对其(弹出)状态进行重置        。

3-1)异步请求页面数据处理                         ,处理异步数据渲染 useEffect(() => { let ignore = false; async function startFetching() { const json = await fetchTodos(userId); // 这里执行是异步的         ,所以第一次执行到此处的时候组件已经被卸载了 // 此时的 ignore 已经被 return 里面的方法置为 true 了 // 所以这里第一次执行的时候不执行 setTodos(json) // setTodos 其实是在第二次执行的时候才触发 if (!ignore) { setTodos(json); } } startFetching(); return () => { ignore = true; }; }, [userId]);

如上代码                 ,对于异步请求数据并渲染这一类                         。

我们可以设置一个 标识位                         ,做到对 请求返回的数据 仅做一次处理与渲染setTodos(json)                 。

codesandbox 测试代码段 3-2)异步请求页面数据处理         ,处理接口请求

上面的方法虽然仅会渲染一次        ,但是请求依然发起了多次。

如果不希望请求多次                         ,也可以使用请求接口数据的缓存方案                 ,对返回数据进行缓存                         。 const cache = useRef(null); useEffect(() => { let ignore = false; async function startFetching() { if (!cache.current) { cache.current = await fetchTodos(userId); } if (!ignore) { setTodos(cache.current); } } startFetching(); return () => { ignore = true; }; }, [userId]);

对于异步请求        ,除了可以处理渲染频率                         ,还可以对接口的请求本身做缓存                          。

在前面3-1的基础上                 ,缓存接口返回的数据,下次请求的时候如果已经有缓存数据了就直接用                         ,无须再次发起请求。 4)无须清理类

并不是所有的 useEffect 函数都需要清理                         ,对于一些没有副作用的函数,我们完全可以不做处理

useEffect(() => { const map = mapRef.current; map.setZoomLevel(zoomLevel); }, [zoomLevel]);

如上代码所示                 ,setZoomLevel 方法仅仅是设置一下 Dom 元素的层级                。

这种操作无论同时执行多少次都不会有太大的影响                         ,所以对于这一类我们就随他去吧         ,毕竟线上也不会执行多次                          。 5)日志 log 上报类 useEffect(() => { reportLog({ name: viewCount }); }, []);

对于日志上报类                 ,其实也可以算是无须清理类                         ,但是又有点特殊         。

因为         ,对于日志类        ,首先在开发环境中我们其实是无须进行上报的                         ,毕竟这种日志打上去也没啥用                。

当然                 ,如果是要对上报日志本身这个进行调试等必须上报的情形        ,这种也有三种应对方式: 方式一                         ,在本地开发环境使用 console.log 来代替 reportLog                         。 方式二                 ,取消掉严格模式(StrictMode) 方式三,构建一个 production 版本启动                         ,或者将其部署到 QA 环境                         ,部署的时候,指定 production 模式         。

借鉴链接:大神地址:epoos

声明:本站所有文章                 ,如无特殊说明或标注                         ,均为本站原创发布        。任何个人或组织         ,在未征得本站同意时                 ,禁止复制         、盗用                、采集                         、发布本站内容到任何网站         、书籍等各类媒体平台                         。如若本站内容侵犯了原著者的合法权益                         ,可联系我们进行处理                 。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
人脸清晰度评价(人脸清晰化神器codeFormer图形界面包GUI) url是什么文件怎么打开(urlproc.exe是什么进程 有什么作用)