当前位置: 首页 > Web前端 > HTML

记录一个React程序的死循环

时间:2023-03-28 16:26:47 HTML

1.错误重现开发环境报如下错误。超过最大更新深度。当组件在componentWillUpdate或componentDidUpdate中重复调用setState时,就会发生这种情况。React限制嵌套更新的数量以防止无限循环。CallStackcheckForNestedUpdateswebsite/./node_modules/react-dom/cjs/react-dom.development.js:4013:321scheduleUpdateOnFiberwebsite/./node_modules/react-dom/cjs/react-dom.development.js:3606:187dispatchActionwebsite/./node_modules/react-dom/cjs/react-dom.development.js:2689:115evalwebsite/./src/components/FileUpload。jsx:73:7invokePassiveEffectCreatewebsite/./node_modules/react-dom/cjs/react-dom.development.js:3960:1047HTMLUnknownElement.callCallbackwebsite/./node_modules/react-dom/cjs/react-dom.development.js:657:119Object.invokeGuardedCallbackDev网站/./node_modules/react-dom/cjs/react-dom.development.js:677:45invokeGuardedCallback网站/./node_modules/react-dom/cjs/react-dom.development。js:696:126flushPassiveEffectsImplwebsite/./node_modules/react-dom/cjs/react-dom.development.js:3968:212unstable_runWithPrioritywebsite/./node_modules/scheduler/cjs/scheduler.development.js:465:162.通过注释排错在代码的方式上,我发现问题是Assets组件中引用的FileUpload有问题,最近也修改了FileUpload组件。通过sourcetree对比git记录,看FileUpload组件修改了什么?如下所示。然后对比报错信息中的描述,其中componentWillUpdate或者componentDidUpdate,推测是指新添加的useEffect代码片段。将上面的useEffect代码段注释掉,果然错误消失了。3、原因分析useEffect的特性表明,只要initFiles发生变化,就会执行46-48行代码。由于上面的useEffect代码片段实际上造成了死循环,所以也说明了一点:setFileList(initFiles)改变了initFiles,使得useEffect中的函数又被调用了。那么,initFiles到底经历了怎样的变化,才使得调用反复发生呢?输出fileList和initFiles:console.log(fileList===initFiles)可以发现只有第一个render会输出true,后面的都是false。第一次输出true,说明useState的入参为array时,只是简单的赋值关系,fileList和initFiles指定了相同的内存地址。setFileList函数实际上是做一个浅拷贝,然后赋值给fileList,改变fileList的内存指针,也就是改变最新的initFiles的内存指针。同时,React保留了initFiles之前的值,用于依赖比较。useEffect在比较对象/数组等引用类型的依赖关系时,使用了一个简单的===运算符,也就是比较内存地址是否一致。前后两个initFile虽然内部数据相同,但是内存点不同,被useEffect认为是【依赖发生了变化】,从而导致死循环。4.方案一尽量不要直接使用对象或者数组作为依赖,而是使用值类型代替引用类型useEffect(()=>{//...},[initFiles.length])5.方案二够不够用调用useState时复制initFiles?const[fileList,setFileList]=useState([...initFiles])useEffect(()=>{if(fileList.length===0){setFileList([...initFiles])}},[initFiles])这样还是会报同样的死循环错误,这是为什么呢?initFiles是从父组件传入的。会不会是在重新渲染FileUpload组件的时候,initFiles被重新赋值了?接下来的两个演示证明了这一推测。Demo1-小心打开。打开后,浏览器选项卡会卡住:初始化initFiles时,默认使用[],导致死循环。Demo1-放心打开它。打开后不会执行JS,不会卡浏览器,可以放心查看代码。Demo2:初始化initFiles时,没有使用默认值,也没有更新父组件,导致没有出现死循环五。在Demo1中,initFiles作为一个prop,每次渲染时,都会分配一个新的空数组,改变它的内存指针。使useEffect连续执行。constFileUpload=({initFiles=[]})=>{}在Demo2中,initFiles的值完全由父组件传入。当父组件的变量不改变时,initFiles不改变。constFileUpload=({initFiles=[]})=>{}constApp=()=>{return}也就是说只要不循环赋值initFiles,它可以避免死循环。6.结论不建议使用array/object等引用类型作为useEffect的依赖,因为极有可能触发bug,而且错误排查难度大。建议使用一种或多种值类型作为useEffect依赖。跟随公众号学习更多关于React/Vue的实战知识。