就 React 变更检测机制问题的建议
转载自前端备忘录-江湖术士
二话不多说,先上代码:
1 | import { |
大家可以新建一个项目试一下,这里为了图方便写了高阶组件,不过还是建议大家不要用高阶组件,IOC
本身就是为了消灭高阶组件,继承,而出现的
这是一个查看 React
深度变更性能的代码,在千层深度组件中:
结果是,在我的电脑上,统计一下时间:
初始化各个 Fiber 节点
local: 1.89 ms app: 53 ms
更新
local: 0ms app: 2ms
多花费了 50ms 2ms 左右的时间,20ms 之后生效
注意,这个部分,不可被优化,大家可以试试,使劲浑身解数,包括但不限于 React.memo,useMemo,或者按照官方最佳实践,来只用上下文传递 setter
是的,很多人说,这个变更检测毫无意义,因为我只在最底层修改了数据,你只需要定位依赖,然后检测最底层就好
也就是说,这极端情况下的 20 ms,避免不了
会在性能监控上留下这样的痕迹:
然后,熟悉 ng platform-webworker 的同学首先坐不住了,不是说提倡 120 hz 高刷么?提交变更必须控制在 4ms 以内么?不然为啥上多线程?
Vue 同学也坐不住了,为啥不把变更检测控制在组件级别?我还能手动 markDirty,手动 changeDetect ,别说千层,万层我也不怕
淡定,这个是协调 m-vm(p) 的消耗,视图提交时是完整的,也就是说,这个操作会在 20 Ms 2ms 后,提交视图渲染,在 4ms 左右之后(一般的 patch 生效时间,嵌套太多可能会久一点),被用户看到
用户看到时,并不会觉得卡顿
ng 同学说不对!我 ng 一个 requestAnimationFrame 调度,来组织个动画,你的视图也需要同步的话,这 20ms ,简直要了我的命!而且 React 干嘛侵占本该我 大 rx 用的调度频道?
先别急,首先,这 20 ms 是极端情况,初始化,一般也就 几毫秒 的样子
ng 同学说:不行!几毫秒也不行,我是高刷屏+safari,react 调度不行的,还是让 rx 来吧!
急吼吼干啥?首先,你既然是流调度,加个 debounceTime 再和 react 同步不行么?只要保证这个 time 盖过了 react 的 调度时间,不就可以么?
ng 同学说:是吼!但是这样不丝滑!
而且,你就算是用 rx 调度,你就以为逃得过 react 的调度魔爪么?你难道就可以完全不用 context 么?
ng 同学说:还是不丝滑!
额,加个 transition?css 解决如何?
然后 ng 同学的处女座毛病就犯了,觉得这样不纯粹,没有真正解决问题云云
这些场景都太极端了!
追求这些极端情况的思想本身就需要调节,这样是不对的
我们写代码,遇到这样极端的情况很罕见,不需要为了极端情况而改变架构和降低使用体验
比起这几十毫秒的时间,你还是多想想如何保证代码质量吧
而且,如果你用 Context 真的出现了肉眼可见的性能问题,建议还是先查看代码中是不是没有贯彻惰性初始化,这也是为数不多的强制要求
惰性初始化才能让你感受到肉眼可见的延迟,1000 层已经是极限,再多框架可就撂挑子了
框架实现得怎么样,那是框架的事情,我们写代码写得怎么样,才是我们能控制的事情
你说 fiber 不行,源代码乱得一匹,调度太烂,还是 ng 大多线程好
是是是,反正我啥都用,也不用跟你生气
但是看着我 ——
你对 React DDD 这些简洁的写法,难道真的不动心么?
几毫秒而已,让它去吧
对了,可能会有人说这个例子还不够极限,那我们再改一下:
1 | function TestDeepCompo(props) { |
这下够极限了么?
1000 层级,每一层级都有响应式变量,有 5 个
变更检测多花 91 ms
91ms,还是没有到体感程度
但是如果我们加个其他逻辑
可以看到,一个惰性初始化,把 调度 时间 从 740 水平,直接降到了 94
所以,还是希望大家搞清楚,最大消耗的源头,在哪里
不要死抓着那些地方不放
有惰性初始化,是,你的 context 还是会有意外,但是这些意外能干啥?就算千层下,你做好惰性初始化,也能把时间压到百毫秒内
他只是新建 workInProgress,你 memo 没变化,他不会动