react源码学习1(理念)

大纲

1 react的理念

2 react新老架构

3 Fiber架构

理念

react的官网介绍是构建快速响应的大型web应用。
重点是快速响应
制约快速响应主要有两种方式:
1 当遇到大量计算的时候,设备性能不足页面掉帧,导致卡帧(cpu瓶颈)
2 网络响应慢 (io瓶颈)

1 cpu瓶颈

浏览器的的主要刷新频率是60hz,也就是一帧大概16ms
浏览器每一帧完成的动作有:执行js脚本->样式布局->样式绘制
而js线程与gui渲染线程是互斥的,就是执行js脚本不会执行布局绘制,执行布局绘制不会执行js脚本。
而我们有些操作比如同时渲染一个3000长度的数组,此时js脚本的执行时间大大超过16ms,导致gui线程无法工作,会给用户卡的感觉。
react16之前无法解决这个问题,而react16之后架构改变,使用fiber结合requestIdleCallback去执行js脚本,将长任务,分为几个短任务,在每一帧中预留一些时间给js线程,当时间一过,脚本还没执行完毕,react将线程控制权还给浏览器,等待下一帧空闲时间再执行该脚本,浏览器就有时间执行gui线程,就会减少掉帧的出现。
这种将长任务拆分到每一帧中,称为时间切片。

解决cpu的瓶颈在于时间切片,也就是将同步的更新变为可中断的异步更新。

Io瓶颈

IO瓶颈取决于网络问题,前端无法控制,但可以控制交互效果,如react提供的Suspense(一个用于显示加载中界面的组件)以及配套的hooks—useDeferredValue
在react源码内部支持这些特性同样需要讲同步更新变为可中断的异步更新。

总结:为了快速响应主要就是解决cpu瓶颈与io瓶颈,而解决的关键就是将同步更新变为可中断的异步更新。

react架构

react15-16重构了整个架构。
因为react15的架构无法满足于快速响应的原理。
https://editor.csdn.net/md/?articleId=119855711
之前就学过b站简单实现react也了解了一点点,
在这里插入图片描述

react15的架构分为两层,

Reconciler(协调器) 负责找出需要变化的组件
Renderer(渲染器) 负责将变化渲染到组件上。

协调器

在这里插入图片描述
在类组件中,可以通过this.setState,this.forceUpdate,ReactDom.render等api来触发更新,每当有更新变化的时候,reconcilers就会做如下事情:
1 调用render方法,将jsx转为虚拟dom
2 通过diff算法比较新老虚拟dom
3 通过比对找出需要变化的虚拟dom
4 通知Renderer将变化渲染到组件上。

渲染器Renderer

不同的平台有不同的渲染器,前端比较熟悉的是ReactDOM
在这里插入图片描述
在每次更新的时候Renderer接受reconciler的通知将变化的组件渲染到当前宿主环境上。

react15架构的缺点:

在reconciler中,渲染组件会调用mountComponent方法,更改会调用upDateComponent方法,这两个方法都是会递归更新子组件。递归遍历,一旦开始无法中断,层级深的时候,时间远远超过16ms,就会导致卡顿。
而且,reconclier与renderer是交替进行的,什么意思呢?就是比如一个列表需要更换,reconciler发现第一个需要改,就立马交给renderer改,改完之后第二个才会进行reconciler中,这样的交替即使支持中断更新也会导致渲染不完整问题。
而无法将同步的更新变为可中断的异步更新,基于这一点,react决定重写整个架构。

新的react架构

react16的架构分为三层

1 Scheduler(调度器) 调度任务的优先级,高优先级的进入recondiler
2 Reconciler(协调器)负责找出组件的变化,通知Render
3 Renderer(渲染器),负责将变化的组件渲染到组件上。

相比react15,react16新增了Scheduler调度器。
我们以浏览器每帧剩余时间来作为任务中断的标准,所以需要浏览器有空闲的时间通知我们,之前实现的简单react就有用
requestIdleCallback,这个api部分浏览器已经实现,他会在每一帧空闲的时候调用,两个参数可以获取每一帧的剩余时间和够不够时间执行js脚本,但是react并没有采用这个api,第一个是浏览器兼容性问题,第二个是触发频率不稳定,容易受影响。

为此,react实现了功能更为强大的requestIdleCallback的profill,也就是Scheduler调度器,除了空闲时调用回调函数,还有很多调度优先级共任务设置。

react16的Reconclier

在这里插入图片描述

react16的reconciler在每次工作的时候会调用类似于requestIdleCallback的参数询问是否还有时间执行,

** @noinline */
function workLoopConcurrent() {
    
    
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) {
    
    
    workInProgress = performUnitOfWork(workInProgress);
  }
}

若没时间,就中断,等下一个空闲时间再执行,
那这样就会导致渲染dom不完全,react怎么解决的呢?
react16的Rconclier与Renderer的工作方式不再是交替运行,当Schedule将任务交给Reconciler的时候,Reconclier会为需要更改的fiber打上增删改的标记,整个Schedule与Reconciler的工作是在内存里完成的,而且这部分可以中断。只有所有组件都完成Reconciler的工作的时候,才会交给Renderer去渲染。

Renderer

Renderder根据Reconclier打上的标记,做出相对应的操作。
借用学习文章的一个图

总结

在这里插入图片描述

快速响应的关键问题就是cpu瓶颈和io瓶颈,而解决问题的关键就是将同步的更新变为可中断的异步更新。react15的架构是Reconciler和Renderer交替工作,而两者都是使用深度遍历,这就导致其不可中断,满足不了可中断的异步更新的需求,故react重写了整个架构。
react16的架构分为Schedule,Reconclier,Renderer三个,因为是根据浏览器每一帧的空闲时间来做决定,所以react实现了类似requeistIDleCallback方法的功能甚至更强大,可以优先调度,就是Schedule,他会在每一帧的空闲时间告诉react有没有时间,可不可以执行。
而Reconclier与Renderer也不再是交替进行,而是Reconciler每次执行都会通过Sechedule确定档期浏览器的这一帧够不够时间,不够等下一帧再执行。而且Reconciler会为所有需要更改的fiber打上标记。Scheudle与Reconclier是在内存中完成的,可以中断,只有当所有组件的Reconclier完成时,Renderer才会开始工作(解决了如果中断dom渲染不完全的问题),而Renderer就会通过Reconclier的标记将需要更新的组件渲染到ui上

Fiber

react16的Reconclier采用了fiber架构,什么是fiebr架构呢?

Fiber架构的代数效应

将副作用从函数逻辑中抽离,使函数关注点保持纯粹
在react中最明显的就是hooks,像useState我们不需要去知道state在component中如何被保存,react已经帮助我们实现好了,我们要做的只是使用它们完成业务逻辑。

代数效应与generator,

react15-16的Reconclier主要的改变就是从同步更新变为可中断的异步更新,浏览器的generator就是类似的实现,generator在每次有时间的时候就调用下next完成一步,没有时间就返回线权,等下次有时间再执行next。但是generator也有一些缺陷,
在这里插入图片描述
如图,浏览器有空闲时间就执行一个doExpensiveWork,如果只考虑单一优先级任务的中断与继续,generator很好的实现了这个功能,但如果想要实现高优先级任务插队完成,比如上图已经完成了AB,此时B接收到一个高优先级的任务,那么因为generator执行的中间状态是上下文关联的,再计算y时无法复用之前计算好的x,需要重新计算,如果使用全局变量,又会引入新的复杂度。

代数效应与fiber

fiber就是代数效应在js的实现过程。
react fiber是react内部的一套状态更新机制,支持任务优先级,可中断,并可恢复。具体到每个节点比如文本节点,元素节点都是fiber节点。

Fiber架构

起源:

react15使用的是reconclier进行递归遍历更新创建虚拟dom,遍历过程是不可中断的,由于无法满足需求,全新的fiber架构应运而生,代替虚拟dom。

含义:

fiber可以从三个方面来理解:

1 架构:

react 15 的reconclier采用递归的方式进行,数据保存在数组调用栈中,所以reconclier也称为stack reconclier,而react16的reconciler 基于fiber节点实现的,所以也被称为Fiber Reconciler

2 静态结构

作为静态的数据结构,一个fiber节点对应一个react element,react fiber保留着每个节点的信息,比如组建类型,dom的信息等等。

3 动态的工作单元

fiber作为动态的工作单元,自身也保存了比如本次更新的动作删除还是编辑,与其他节点的联系(通过return nextsibling child)三个指针构成树。

Fiber的节点属性定义:

分为四种:

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
    
    
  // 作为静态数据结构的属性,比如对应着组件类型,dom的类型
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  // 该fiber对应的dom
  this.stateNode = null;

  // 用于连接其他Fiber节点形成Fiber树
  //指向父节点,为何是return,因为儿子执行完毕下一个执行完毕的就是父亲。
  this.return = null;
  //永远指向大儿子
  this.child = null;
  //指向自己的兄弟
  this.sibling = null;
  this.index = 0;

  this.ref = null;

  // 作为动态的工作单元的属性
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;

	//变更类型,形成一个单链表
  this.effectTag = NoEffect;
  this.nextEffect = null;

  this.firstEffect = null;
  this.lastEffect = null;

  // 调度优先级相关
  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  // 指向该fiber在另一次更新时对应的fiber
  //指向更新前的节点,新老对比。
  this.alternate = null;
}

作为架构来说:之前实现简单react有写过,每个fiber节点都有三个指针,return,child, sibling三个节点构成fiber树。
为何return不叫parent,这与fiber的实现顺序有关系,fiber树的实现逻辑是1 从大儿子开始,没有儿子就兄弟,没有兄弟就自己父亲。
2 怎么算节点完成了呢?(也就是可以执行completewrok去收集依赖),只要自己的儿子完成即可。
这与执行completework的顺序有关系。第一个最先执行的就是第一个最先完成的,就是没有儿子的大儿子,如果大儿子没有兄弟,那么父亲也就算完成了,也需要执行compltetwork,所以叫做return。因为下一个需要执行的就是父亲了。

Fiber架构的工作原理

  • 双缓存
    我们使用canvas绘画的时候每一帧绘制前都会调用ctx.clearRect清除上一帧的画面,当当前画布计算量较大的时候就会导致白屏出现,这时候我们可以在内存中预先绘制当前帧动画,当清除后立马换上,从而减少白瓶的出现,这种在内存中构建并直接替换的技术成为双缓存
  • react的双缓存
    reacat的双缓存体现在两颗fiber树当中,一颗current fiber树,一颗workInProgress fiber树。当前屏幕显示的fiber树称为currentFiber树,而在内存中构建的fiber树称为workInProgress fiber树。两颗树的fiber节点通过一个指针,alternate链接。
currentFiber.alternate === workInProgressFiber;
workInProgressFiber.alternate === currentFiber;
  • react通过current指针在不同fiber树的rootFiber根结点间进行切换来完成,current fiber树的切换,即当workInProgress树构建完交给Renderer的时候,current指针指向workInProgress,此时workInProgress fiber树就变成current Fiber树
    每次更新都会创建一个新的workInProgress 树,通过current 与 workInProgress的切换,完成dom的更新。
  • mount的操作
    在首次渲染的时候,如渲染< div> 1 < /div>
    首次执行ReactDOM.render的时候会创建两个fiber,一个是整个应用的根结点(fiberRoot),一个是当前组件树的根节点,rootFiber。
    区分这两点很重要
    整个应用只有一个fiberRoot,但是更新的时候不同的组件树会拥有不同的rootFiber。fiberRoot的current会指向当前渲染的fiber树,该属就称为current fiber树。
fiberRoot.current = rootFiber
  • 由于是首次渲染,current fiber树还没有任何的子节点,在render(不是renderer,而是reconclier的render阶段,用来收集依赖,effect list,来看看哪些需要改变)阶段,根据组件返回的jsx在内存中构建fiber节点并且通过指针将其构成一颗fiber树,该树就称为workInProgress fiber树。在构建workInProgress树的时候会通过每个fiber节点的alternate指针,尝试服用current fiber上的一些已有的属性。
  • 构建完commit阶段
    workInprogress fiber树在构建完会在commit阶段渲染到ui上,commit阶段是不可以停止的,此时RootFiber的current指向了workInProgress fiber树,此时workInProgress fiber树就变层了current fiber树

update时

当组件发生更新重新渲染时,会重新进入render阶段收集依赖创建workInProgress fiebr树,与mount一样,workInProgress的每个fiber会根据diff算法判断是否可以复用alternate的数据。在render阶段完成就提交到commit阶段去进行渲染,此时workInProgress fiiber树就会成为current fiber树。

总结:
react16实现了 “Schedule-Reconclier-Renderer” 的架构体系,
Reconclier的工作对应我们上述说的render阶段,也就是收集依赖,关联fier节点。在该阶段会调用组件的render方法。
而Renderer的工作对应着我们上述说的commit阶段,中文意思提交,顾名思义,当你render阶段完收集完依赖后,就需要commit到页面去进行渲染。
render和commit阶段统称为work阶段,只要任务在Schedule调度中,就不属于work阶段。
Schedule与Reconclier是在内存中完成的,因为可以中断。
而Renderer阶段是渲染工作,不能中断。

未完待续。。。

仅供学习笔记使用,学习文章地址:

https://react.iamkasong.com/preparation/idea.html#cpu%E7%9A%84%E7%93%B6%E9%A2%88

おすすめ

転載: blog.csdn.net/lin_fightin/article/details/120074455