React Router组件状态更新异常:返回null后子组件为何不更新
这个问题的核心在于组件挂载时机和状态订阅的时机不匹配,咱们一步步拆解清楚:
初始渲染阶段
当Test组件第一次渲染时,useS钩子返回初始状态'aa',触发了if (s === 'aa') { return null }的逻辑,直接返回null——这意味着<Router>和<MyCpt>根本没有被挂载到DOM上,MyCpt组件的代码完全没执行,它内部的useS自然也不会运行,所以MyCpt对应的setState函数根本没被加入到listeners数组里。
这时候listeners里只有Test组件的useS钩子注册的setState函数。
2秒后的状态更新
2秒后,setTimeout的回调执行,遍历listeners数组调用所有回调函数,这时候只会触发Test组件的setState,把它的s改成'bb'。
Test组件重新渲染
Test组件的状态更新后,重新渲染,这时候s是'bb',不再返回null,而是渲染<Router>和<MyCpt>。这时候MyCpt才第一次挂载,执行它内部的useS钩子:
- 初始化状态为
'aa' - 把自己的
setState加入listeners数组
但此时setTimeout的回调已经执行完毕了,不会再触发这个新加入的回调函数,所以MyCpt的状态一直停留在初始的'aa',自然不会显示'bb'。
对比去掉return null的情况
如果去掉if (s === 'aa') { return null }这段代码,Test组件初始就会渲染MyCpt,MyCpt的useS会在初始渲染时把自己的setState加入listeners数组。2秒后setTimeout回调执行时,会同时触发Test和MyCpt的setState,两者的状态都更新为'bb',所以MyCpt能正常显示'bb'。
简单总结:被延迟挂载的组件错过了状态更新的时机,它的订阅是在更新事件发生后才注册的,自然接收不到之前的更新通知。
内容的提问来源于stack exchange,提问作者Guichi

