在react18之中,引入了transition的概念。而且有一个新的api和两个新的hooks
场景应用:
比如通过输入框输入内容更新列表内容,对于用户来说,输入框输入之后立马反馈的优先级是高过于列表更新的优先级的。但是对于18之前的版本来说,更新没有分优先级,导致如果数据量大,列表更新慢,用户输入在输入框输入内容后很慢才能得到反馈,用户体验差。
而transition可以让列表更新的优先级降低,让用户输入的先处理,然后再处理列表更新。18之后开启了concurrent mode模式,更新也分了优先级的概念。
用法:
setData1(e)
startTransition(()=>{
setData2(e)
})
将需要延迟更新的任务放到startTransition去执行,这样里面的更新任务就会被赋予transition的优先级。
setTimeout也有类似于延迟更新的效果,比如
setData1(e)
setTimeout(()=>{
setData2(e)
}, 200)
transition顾名思义过度,状态更新的任务一般有两种,一种是紧急更新任务,比如用户交互行为,点击,输入等等。
而另一种则是过渡更新任务,比如上面列表的更新,这种任务的优先级低于紧急更新任务。
startTransition(fn)
同步执行,但是fn里面的更新会被标记transition的优先级。也就是优先级更低了。
startTransition只是用来降低优先级的,也就是过渡,但是过渡了多久我们需要一个状态来知道。useTransition就是用来做这个事情的。
const [isPending, startTransition] = useTransition()
startTransition(fn)
useTransition返回两个值,第一个就是是否正在过渡到控制变量,第二个就是跟startTransition用法一样的,用来降低任务优先级的函数。但任务处于过渡阶段的时候,isPending为true,告诉开发者,目前该任务正在过渡。
上述的startTransition和useTransition本质都是将一些任务的优先级降低。
而useDeferredValue可以让状态滞后更新。实现效果类似于transiton,当紧急的任务执行之后,再得到新的状态,而这个新的状态就是deferredValue。
同:内部实现一样,都是将任务标记为过渡更新任务。
别:
startTransition的源码实现
他的本质有点类似于17之前的事件更新,通过开关ReactCurrentBAtchConfig.transition
ReactCurrentBAtchConfig.transition默认值是null,再需要更新过渡任务的时候,将其置为{},这样startTransition里面的函数执行的时候,就会判断ReactCurrentBAtchConfig.transition以此来将其优先级降低。
mount更新阶段,执行的函数是mountTransition,update阶段执行的函数是updateTransition
如图是mountTransition,本质很简单,就是通过调用mountState产生一个状态,然后通过startTransition绑定默认参数,调用moutnWorkInProgressHook创建hooks对象,跟其他hook链接,最后这个hooks.memoizedState保存的就是startTransition这个函数。
如上,则是useTransition返回的第二个参数,本质就是先调用setPending(ture),然后切换transitiion上下文,让setPending(false)的更新也变成了过渡任务,这样setPending(false)和callback的任务优先级相同,他们会同时处理。
所以useTransition可以看成useState+startTransition
useDeferredValue本质也是调用了useState+useEffect,通过监听value值的改变,触发useEffect回调函数的更新,再去切换tranition上下文,更新setValue(value)
所以useDeferredValue可以看成:useState+useEffect+startTransition。
const [data, setData] = useState({})
startTransition(()=>setData({}))
一般startTransition里面函数,存在着触发更新的动作,比如useState的第二个参数setState,他们会创建update,更新优先级,开始调度。具体可以看hooks实现逻辑
所以重点看看产生update时候的优先级的处理,以setState为例子
useState的第二个参数,实际上是调用dispatchSetState
函数
requestUpdateLane就是用来获取当前update的优先级的。来看具体实现
第一个判断就是判断是否是同步模式下,也就是18之前的模式,他不支持优先级的概念。
其次就是判断是否是transition的上下文,判断方式也很简单粗暴,直接判断ReactCurrentBatchConfig.transition是否不等于null,是的话就表示处于transition的更新。
然后返回currentEventTransitionLane,也就是transition的优先级
这也就解释了通过切换transition上下文,产生的update的优先级并不一样。
然后处理这个update的时候
可以看到,setSe 产生的update的确走了这个逻辑,最后返回的优先级就是64。
然后将update插入hooks的update连表上,更新调用scheduleUpdateOnFiber开始新的一轮调度。
然后最后会执行ensureRootIsScheduled函数,该函数根据当前的优先级,调用schedule模块提供的schedule_callback函数调度任务。如下:
对于64的优先级,被分配到了normalPriority的优先级,
然后通过注册任务,开启调度。
自此,整个transition的流程就已经走通了。
总结:
transition过渡,就是将一些不紧要的任务降低其优先级,使其不阻塞高优先级的任务。
具体实现就是通过切换transition上下文,使产生的udpate获取更低的优先级,这样调度的时候过期时间就会更长,从而不会阻塞高优先级的任务。
文章通过学习掘金的《react进阶实践指南》作为笔记产出,文中图片部分来自《react进阶实践指南》