记录一次React程序死循环

一、错误复现

开发环境报如下错误。

Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

Call Stack
 checkForNestedUpdates
  website/./node_modules/react-dom/cjs/react-dom.development.js:4013:321
 scheduleUpdateOnFiber
  website/./node_modules/react-dom/cjs/react-dom.development.js:3606:187
 dispatchAction
  website/./node_modules/react-dom/cjs/react-dom.development.js:2689:115
 eval
  website/./src/components/FileUpload.jsx:73:7
 invokePassiveEffectCreate
  website/./node_modules/react-dom/cjs/react-dom.development.js:3960:1047
 HTMLUnknownElement.callCallback
  website/./node_modules/react-dom/cjs/react-dom.development.js:657:119
 Object.invokeGuardedCallbackDev
  website/./node_modules/react-dom/cjs/react-dom.development.js:677:45
 invokeGuardedCallback
  website/./node_modules/react-dom/cjs/react-dom.development.js:696:126
 flushPassiveEffectsImpl
  website/./node_modules/react-dom/cjs/react-dom.development.js:3968:212
 unstable_runWithPriority
  website/./node_modules/scheduler/cjs/scheduler.development.js:465:16

二、错误排查

  1. 通过注释代码的方式,发现出问题的地方,是Assets组件中引用的FileUpload出了问题。正好最近也修改过FileUpload组件。
  2. 通过sourcetree对比git记录,看FileUpload组件被修改了什么?如下图。
    记录一次React程序死循环_第1张图片
  3. 再对比错误提示中的描述,其中componentWillUpdate or componentDidUpdate,推测就是指新增的useEffect代码片断。
  4. 将上述useEffect代码片断注释掉,果然错误消失。

三、原因分析

useEffect的特性表明,只要initFiles发生了改变,46-48行代码就会执行。
既然上述useEffect代码片断事实上造成了死循环,就还说明了一点:

  • setFileList(initFiles)改变了initFiles,才使得useEffect中的函数再次被调用。

那么,initFiles到底是经历了怎样的变化,才使得调用能够循环往复地发生呢?

输出fileListinitFiles

console.log(fileList === initFiles)

可以发现,只有第一次render时输出true,后续全部是false

  • 第一次输出true,表明useState的入参为array时,只是简单的赋值关系,fileListinitFiles指定了同一个内存地址。
  • setFileList函数实际上是做了一次浅拷贝,然后赋值给fileList,改变了fileList的内存指向,也就是改变了最新initFiles的内存指向。同时React保留了之前initFiles的值用来做依赖对比。
  • useEffect在对比引用类型的依赖,比如object/array时,采用的是简单的===操作符,也就是说比较内存地址是否一致。
  • 前后两次initFiles虽然内部数据相同,但内存指向不同,就被useEffect认为【依赖发生了改变】,从而导致了死循环。

四、解决方案

  • 尽量不直接使用object或者array作为依赖项,而是使用值类型来代替引用类型

    useEffect(() => {
    //...
    }, [initFiles.length])

五、拓展

是不是在调用useState时,拷贝initFiles就可以了呢?

const [fileList, setFileList] = useState([...initFiles])

useEffect(() => {
  if (fileList.length === 0) {
    setFileList([...initFiles])
  }
}, [initFiles])

这样依然会报同样的死循环错误,为什么?欢迎大家留言讨论。

你可能感兴趣的:(记录一次React程序死循环)