给女朋友讲React18新特性:startTransition

开发 前端
startTransition的出现当然不是为了逆转命运,而是为了逆转React的更新流程。在聊startTransition的具体应用场景前,我先来聊聊React是如何扬长避短的。

[[406706]]

大家好,我是卡颂。

我女友是个铁憨憨,又菜又爱玩。

她问我:“卡卡,你说时光真的可以重来?命运真是可以选择的么?”

我:“可以的,React18的新特性startTransition就行。”

startTransition的出现当然不是为了逆转命运,而是为了逆转React的更新流程。

"在聊startTransition的具体应用场景前,我先来聊聊React是如何扬长避短的。"

编译时的短,运行时的长

如果我们用「重编译时还是运行时」区分前端框架。那么Vue和Svelte就是「重编译时」的杰出代表。

在「编译时」,这两个框架可以分离模版语法中「变」与「不变」的部分,减少运行时的代码逻辑。

而React由于使用JSX(而非模版语法)描述视图,走的是「重运行时」的路线。

  • 不是React不想在「编译时」做优化,奈何JSX实在太灵活,做不到啊......

所以他的优化策略也都是偏「运行时」。

在「运行时」,最大的开销是:状态更新到视图变化中间的计算步骤。

这个步骤是通过「遍历Fiber树」实现的。

常规的「运行时优化策略」,比如:

  • React.memo
  • PureComponent
  • shouldComponentUpdate

优化方向都是:减少遍历时需要遍历的Fiber节点数量。

虽说性能优化的收益可以积少成多,但是React团队早已不满足这种局部的小优化。

性能优化新思

路他们的思路是:

不同更新触发的视图变化显然是有轻重缓急的。

如果能区分更新的优先级,让高优更新对应的视图变化先渲染,那么就能在设备性能不变的情况下,让用户更快看到他们想看到的UI。

比如:对于这样一个搜索下拉框:

用户期望:输入框输入的内容要实时反映在视图上(表现为输入内容不能卡顿)。

而结果下拉框的展示是可以有延迟的。

基于以上逻辑,React希望提供一个API,让用户告诉自己,哪些更新是「高优」的,哪些是「低优」的。

这样,React就能知道优先渲染谁了。

这个API,就是startTransition。

startTransition的使用

接下来,我们用一个Demo[1]演示startTransition的使用。

这个Demo会渲染一棵「毕达哥拉斯树」。

拖动左边滑块会改变树渲染的节点数量。

拖动顶部滑块会改变树的倾斜角度。

最顶上有个帧雷达,可以实时显示更新过程中的掉帧情况。

当不点击Use startTransition按钮,拖动顶上的滑块。

图片

可以看到:拖动并不流畅,顶上的帧雷达显示掉帧(出现黄色、红色扇面)

当点击Use startTransition按钮,拖动顶上的滑块。

图片

可以明显看到:拖动变流畅,顶上的帧雷达显示掉帧的情况变少

让我们节选Demo的代码看看,究竟发生了什么。

Demo都做了什么?

首先,控制滑块、树倾斜角度、要渲染的节点数量是分离在不同state中的:

  1. // 左侧滑块的state 
  2. const [treeSizeInput, setTreeSizeInput] = useState(8); 
  3. // 控制渲染节点数量的state 
  4. const [treeSize, setTreeSize] = useState(8); 
  5.  
  6. // 顶部滑块的state 
  7. const [treeLeanInput, setTreeLeanInput] = useState(0); 
  8. // 控制树倾斜角度的state 
  9. const [treeLean, setTreeLean] = useState(0); 
  10.  
  11. // startTransition的hook版本 
  12. const [isLeaning, startTransition] = useTransition(); 

当拖动顶上的滑块(改变树的倾斜角度)会调用changeTreeLean方法:

  1. function changeTreeLean(event) { 
  2.     const value = Number(event.target.value); 
  3.     setTreeLeanInput(value); // update input 
  4.  
  5.     // update visuals 
  6.     if (enableStartTransition) { 
  7.         startTransition(() => { 
  8.             setTreeLean(value); 
  9.         }); 
  10.     } else { 
  11.         setTreeLean(value); 
  12.     } 

该方法会改变两个state:

  • 通过调用setTreeLeanInput改变顶部滑块位置相关的state —— treeLeanInput
  • 通过调用setTreeLean改变树的倾斜角度相关的state —— treeLean

是否点击Use startTransition按钮的区别,就在于setTreeLean是否会被作为startTransition的回调执行:

  1. // 是否开启startTransition 
  2. if (enableStartTransition) { 
  3.   startTransition(() => { 
  4.     setTreeLean(value); 
  5.   }); 
  6. else { 
  7.   setTreeLean(value); 

当作为startTransition的回调执行时,setTreeLean改变的状态(treeLean)对应的视图变化(即:改变树的倾斜角度)会被视为「低优先级的更新」。

即使其与改变滑块状态的方法(setTreeLeanInput)在同一上下文中执行,

由于其优先级较低,React会优先处理「改变滑块状态」对应的视图变化。

表现为:滑块的滑动不卡顿。

startTransition的原理

铁憨憨:“这么酷炫的功能实现起来一定很复杂吧?”

“恰恰相反,依赖于React底层实现的优先级调度模型,startTransition的实现其实很简单!”

以刚才的代码为例,如果加上console.log打印:

  1. console.log(1); 
  2. startTransition(() => { 
  3.   console.log(2); 
  4.   setTreeLean(value); 
  5. }); 
  6. console.log(3); 

那么会依次输出:123

startTransition做的事情很简单,类似这样:

  1. let isInTransition = false 
  2.  
  3. function startTransition(fn) { 
  4.   isInTransition = true 
  5.   fn() 
  6.   isInTransition = false 

也就是说,当调用startTransition,在其上下文中获取到的全局变量isInTransition为true。

如果startTransition的回调函数fn中包含更新状态的方法(比如上文Demo中的setTreeLean),

那么这次更新就会被标记为isTransition,类似这样:

  1. // 调用setTreeLean后会执行的方法(伪代码) 
  2. function setState(value) { 
  3.   stateQueue.push({ 
  4.     nextState: value, 
  5.     isTransition: isInTransition 
  6.   }) 

代表这是一个低优先级的过渡更新。

接下来,就是React内部的调度、批处理与更新流程了。

  • 批处理的逻辑见给女朋友讲React18新特性:Automatic batching

总结

今天,我们讲了:

  • React为了弥补自身弱编译时的缺点,在运行时作出的努力
  • startTransition本质是让开发者手动标记更新的优先级
  • startTransition的实现原理

铁憨憨:”原来React为了性能优化做了这么多努力,好复杂啊,我还是用Vue吧!“

我:“可不是嘛,React已经在朝着实现一个浏览器的方向发展了。”

参考资料

[1]Demo:

https://swizec.com/blog/a-better-react-18-starttransition-demo/

 

责任编辑:姜华 来源: 魔术师卡颂
相关推荐

2021-06-22 07:30:07

React18Automatic b自动批处理

2021-11-01 19:49:55

React组件模式

2024-04-24 11:00:05

React 18Fiber

2021-06-16 06:05:25

React18React

2020-10-25 08:22:28

V8 引擎JavaScript回调函数

2021-11-29 06:05:31

React组件前端

2021-06-15 14:54:23

ReactReact 18SSR

2019-03-12 09:43:14

反向代理正向代理服务器

2021-10-21 08:31:31

Spring循环依赖面试

2021-03-05 17:06:53

全排列组合子集

2019-04-09 09:40:23

2020-03-16 14:08:59

线程熔断限流

2023-03-21 08:31:13

ReconcilerFiber架构

2022-03-30 14:22:55

ReactReact18并发特性

2022-03-16 17:01:35

React18并发的React组件render

2019-10-09 10:45:16

云计算Web互联网

2021-09-14 12:00:11

VR字节跳动

2019-12-23 10:26:02

3PC分布式2PC

2022-04-27 07:37:42

ReactReact18

2019-04-19 09:48:53

乐观锁悲观锁数据库
点赞
收藏

51CTO技术栈公众号