当前位置: 首页 > 科技观察

React的操作系统梦任重而道远

时间:2023-03-15 01:23:16 科技观察

本文包含以下内容:简述React16年到21年的迭代历史升级到ConcurrentMode难度更大迭代过程回顾ReactCoreTeam开始转型2016年React的核心模块Reconciler(diff算法会在这个模块中执行)。经过一年多的改造,从不可中断的“递归实现”(称为StackReconciler)变为可中断的“遍历实现”(称为FiberReconciler)。之后基于FiberReconciler实现了一套可以区分任务优先级的机制。大体原理如下:不同的交互(用户点击交互/请求数据/用户拖动...)触发状态更新(比如调用this.setState)会有不同的优先级,对应源码中的一个时间戳变量expirationTime。React会根据expirationTime的大小来调度这些更新,最终的效果是:“用户交互”触发的更新优先级更高,优先于“请求数据”触发的更新。高优先级意味着更新对DOM的影响将更快地呈现给用户。之后ReactCoreTeam发现基于expirationTime的调度算法虽然可以满足fiber树整体的优先级调度,但是不够灵活(比如不能满足局部fiber树(如Suspense)的优先级调度).具体原因看这篇文章:启发式更新算法所以去年ReactCoreTeam的AndrewClark将expirationTime模型重构为一个车道模型,用32位二进制位表示优先级。PR见InitialLanesimplementation#18796[1]如果你是React的重度用户,让你说说React这些年来的重大变化,也许你会说:ContextAPIrefactorsHooks但是从我们上面说的,从From2016年到21年,React底层其实做了很多重构工作。有人问:经过这么多次重构,React开发者一点意识都没有?是的,即使是当前稳定版本的React的底层也已经支持时间分片和更智能的更新合并机制(batchedUpdates)。但是React内部有很多类似裹脚的代码,可以让新架构的行为和旧架构保持一致(StackReconciler)。ReactCoreTeam的执念就像开发业务的开发人员需要承担OKR一样,比ReactCoreTeam的成员强,他们也会为OKR操心。在React20年的圣诞特辑中,ReactCore团队的RachelNabors女士在文章InsidetheReactCoreteam[2]中说:Justbecauseyouhavenooutputdoesn'tmeanyouarepointless(一把辛酸的泪).React作为视图层的库,不用开脑洞就可以做到极致。操作系统中的协程、并发等概念被搬进了React,函数式编程的概念也登陆了React(Hooks)。React应该去哪里?React之魂、Hooks作者、TC39成员SebastianMarkb?ge给出的答案是:逆向开发,向BFF层发展。简单的说:在SSR领域,目前的实现方案还是比较粗略的:组件在服务端编译模板串(脱水),前端渲染模板串,完成组件交互(注水),剩下的渲染.SSR解决方案的粒度不够细。如果FiberReconciler可以在组件级别控制时间片的粒度,那么SSR为什么不能在组件级别控制粒度呢?要实现这个目标,至少要支持:一套React组件的流式数据传输协议(区别于字符串模板)前端可以准确控制组件的状态(加载/加载失败/加载成功),即Suspense特性,Suspense特性依赖于ConcurrentMode的时间分片特性。社区中没有大量的库连接到ConcurrentMode,让时间切片成为默认配置,SebastianMarkb?ge的远大理想(OKR)无异于空中楼阁。因此,社区尽快跟上React升级的步伐势在必行。ConcurrentMode升级难点当前社区大量React生态库的逻辑是基于如下React运行流程:stateupdate-->render-->viewrendering如果React的运行流程变为:stateupdate-->render(可暂停)-->Viewrenderingorstatusupdate-->render(中断)-->Re-stateupdate-->render(pausable)-->viewrendering会发生什么?会出现一种叫撕裂的现象,我们举个例子:假设我们有一个变量externalSource,初始值为1。ExternalSource会在1000ms后变为2。letexternalSource=1;setTimeout(()=>{externalSource=2;},1000)我们有一个组件A,其DOM渲染取决于externalSource的值:functionA(){return

{externalSource}

;在当前版本的React中,如果我们在应用程序组件树中的不同地方使用A组件,就会有一些地方的DOM是

1

,一些地方是

2

p>什么?答案是不。因为React当前运行的流程是同步的:stateupdate-->render-->viewrendering,所以让externalSource变为2的setTimeout会在这个流程对应的任务(宏视图)执行完后执行。但是切换到ConcurrentMode时:stateupdate-->render(pausable)-->viewrendering当render暂停时,浏览器获得了JS线程的控制权,会执行setTimeout将externalSource改为2。这样,不同A组件渲染的p标签中的数字可能不同。这种在依赖外部资源时,由于React运行过程发生变化而导致state和view不一致的现象就是撕裂。这里改变externalSource的外力可能来自于各种任务(IO,setTimeout...)目前有一个解决外部资源状态同步的提议useMutableSource[3]这个库will-this-react-global-state-work-in-Concurrent-mode[4]测试主流状态管理库是否会导致撕裂和困难的小步骤。为了让开发者能够更轻松地逐步升级到ConcurrentMode,ReactCoreTeam一直在努力:提供StrictMode(严格模式)组件,规范开发者行为,将componentWilXXX标记为unsafe_提供逐步升级路线,(从遗留模式到阻塞模式到并发模式)很明显,ReactCoreTeam觉得社区的升级速度还是太慢了。最近加入了一个新的PR:Maketime-slicingopt-in[5]这个PR提到:在下一个大版本中,ConcurrentMode将被完全使用,但是这个ConcurrentMode会默认关闭时间分片功能。就是直接给开发者打了个短电话:伙计们,求求你们赶紧升级吧,OKR指着他……这种悲伤、渴望、期待的心情,直接导致了提交这个PR的Ricky逐渐沙雕(狗头救命):React的操作系统梦,任重而道远~~~参考文献[1]初始通道实施#18796:https://github.com/facebook/react/pull/18796[2]React核心团队内部:https://react.christmas/2020/24[3]useMutableSource:https://github.com/reactjs/rfcs/blob/master/text/0147-use-mutable-source.md[4]will-this-react-global-state-work-in-concurrent-mode:https://github.com/dai-shi/will-this-react-global-state-work-in-concurrent-mode[5]选择加入时间片:https://github.com/facebook/react/pull/21072