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

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

时间2025-06-19 00:23:51分类IT科技浏览10689
导读:一、执行两次的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
bootstrap开发工具(PbootCMS:开源易用的内容管理系统) 网站点击量与排名,网站点击量统计不一样(网站点击量与排名,网站点击量统计)