大家好,我是Cason。我的女朋友是一个喜欢做饭和玩耍的硬汉。她问我:“卡卡,你说时间真的可以重来吗?命运真的可以选择吗?”我说:“可以,React18的新特性startTransition就可以了。”当然,startTransition的出现并不是为了扭转命运,而是为了扭转React的更新过程。“在说startTransition的具体应用场景之前,先说说React是如何扬长避短的。”编译时间短,运行时间长如果我们用“重新编译还是运行时间”来区分前端框架。那么Vue和Svelte就是“重新编译时间”的杰出代表。在“编译时”,这两个框架可以将模板语法中“改变”和“未改变”的部分分开,减少运行时的代码逻辑。另一方面,React使用JSX(而不是模板语法)来描述视图,走的是“reruntime”的路线。并不是React不想在“编译时”进行优化,而是JSX做起来太灵活了……所以他的优化策略也偏向于“运行时”。在“运行时”,最大的开销是:状态更新和视图改变之间的计算步骤。这一步是通过“遍历Fiber树”来实现的。常规的“运行时优化策略”,如:React.memoPureComponentshouldComponentUpdate优化方向是:减少遍历时需要遍历的fiber节点数。虽然性能优化的好处可以加起来,但React团队已经不再满足于这种局部的小优化。性能优化的新思路他们的思路是:不同更新触发的视图变化显然是有优先级的。如果能区分更新的优先级,让高质量更新对应的视图变化优先渲染,那么就可以在不改变设备性能的情况下,让用户更快地看到他们想看到的UI。例如:对于这样一个搜索下拉框:用户期望在输入框中输入的内容应该实时反映在视图上(表示不能卡住输入的内容)。结果下拉框的显示可以延迟。基于以上逻辑,React希望提供一个API,让用户自己判断哪些更新是“高质量”,哪些是“低质量”。这样,React就会知道先渲染谁。这个API是startTransition。startTransition的使用接下来我们用一个Demo[1]来演示startTransition的使用。该演示将呈现“毕达哥拉斯树”。拖动左侧滑块将更改树呈现的节点数。拖动顶部滑块将改变树的倾斜角度。顶部有一个掉帧雷达,可以实时显示更新过程中的掉帧情况。不单击“使用开始转换”按钮时,拖动顶部滑块。可以看到拖动不流畅,顶部的帧雷达出现掉帧(出现黄色和红色的扇子)。单击UsestartTransition按钮并拖动顶部的滑块。可以明显看出拖拽变得更顺畅,顶部的帧雷达显示掉帧更少。让我们摘录Demo的代码看看到底发生了什么。演示做了什么?首先,控件滑块、树倾斜角度、要渲染的节点数在不同状态下分开://stateconst[treeSizeInput,setTreeSizeInput]=useState(8);//stateconst[treeSize,setTreeSize]=useState(8)那个控制渲染节点的个数;//stateconst[treeLeanInput,setTreeLeanInput]=useState(0)topslider;//stateconst[treeLean,setTreeLean]=useState那个控制树的倾斜角度(0);//startTransition的钩子版本const[isLeaning,startTransition]=useTransition();拖动顶部滑块时(改变树的倾斜角度)会调用changeTreeLean方法:{startTransition(()=>{setTreeLean(value);});}else{setTreeLean(value);}}这个方法会改变两种状态:通过调用setTreeLeanInput改变与顶部滑块位置相关的状态-treeLeanInputChange通过调用setTreeLean与树倾斜角度相关的状态——treeLean是否点击UsestartTransition按钮的区别在于setTreeLean是否会作为startTransition回调执行://是否启用startTransitionif(enableStartTransition){startTransition(()=>{setTreeLean(value);});}else{setTreeLean(value);}asstartTransition执行setTreeLean的回调时,setTreeLean改变的state(treeLean)对应的视图改变(即:改变树的倾斜角度)即使是在同一个方法中,也会被视为“低优先级更新”作为改变滑块状态的方法(setTreeLeanInput)在上下文中执行,由于其优先级较低,React会优先考虑“改变滑块状态”对应的视图改变。表现为:滑块滑动不卡住。startTransition的原理是铁憨憨:“这么酷的功能,实现起来一定很复杂吧?”“相反,依靠React底层实现的优先级调度模型,startTransition的实现其实非常简单!”以刚才的代码为例,如果在打印中加入console.log:console.log(1);startTransition(()=>{console.log(2);setTreeLean(value);});控制台日志(3);然后会依次输出:123startTransition做的事情很简单,像这样:它的上下文是真实的。如果startTransition的回调函数fn中包含更新状态的方法(如上面Demo中的setTreeLean),那么这个更新会被标记为isTransition,类似这样://将要执行的方法(伪代码)在调用setTreeLean)functionsetState(value){stateQueue.push({nextState:value,isTransition:isInTransition})}之后表明这是一个低优先级的转换更新。接下来就是React内部的调度、批处理和更新过程。批处理的逻辑可以看和闺蜜聊React18的新特性:自动批处理总结今天我们讲了:为了弥补React编译弱的短板,startTransition在运行时的努力本质是让开发者手动标记更新的优先级startTransition的实现原理是铁憨憨:“原来React为了性能优化下了这么大的功夫,这么复杂,我还是用Vue吧!”我:“对了,React现在已经在往实现浏览器的方向发展了。”参考[1]Demo:https://swizec.com/blog/a-better-react-18-starttransition-demo/
