react源码学习(一)

react源码学习



前言

工作不忙,啃一啃react源码

提示:以下是本篇文章正文内容,下面案例可供参考

react的核心可以用**ui=fn(state)**来表示,更详细可以用

const state = reconcile(update);
const UI = commit(state);

react源码可以分为以下几个部分

  1. Scheduler(调度器): 排序优先级,让优先级高的任务先进行reconcile(调度)
  2. Reconciler(协调器): 找出哪些节点发生了改变,并打上不同的Tag
  3. Renderer(渲染器): 将Reconciler中打好标签的节点渲染到视图上

Scheduler的作用是调度任务,react15没有调度器这部分,所以所有任务没有优先级,也不能中断,只能同步执行。

​ Reconciler发生在render阶段,render阶段会分别为节点执行beginWork和completeWork(后面会讲),或者计算state,对比节点的差异,为节点赋值相应的effectTag(对应dom节点的增删改)

​ Renderer发生在commit阶段,commit阶段遍历effectList执行对应的dom操作或部分生命周期
在这里插入图片描述
在这里插入图片描述

1.scheduler调度算法

  • scheduler是用来做任务调度

  • 所有任务在一个调度生命周期内都有一个过期时间与调度优先级,但是调度优先级最终还是会转换为过期时间,只是过期时间长短的问题,过期时间越短代表越饥饿,优先级也就越高,但已经过期了的任务也会被视为饥饿任务

  • requestAnimationFrameWithTimeout,这是React scheduler的一个超强的函数,它是解决网页选项卡如果在未激活状态下requestAnimationFrame不会被触发的问题,这样的话,调度器是可以在后台继续做调度的,一方面也能提升用户体验,同时后台执行的时间间隔是以100ms为步长,这个是一个最佳实践,100ms是不会影响用户体验同时也不影响CPU能耗的一个折中时间间隔

  • 调度优先级分为:

  • 立即执行优先级,立即过期

  • 用户阻塞型优先级,250毫秒后过期

  • 空闲优先级,永不过期,可以在任意空闲时间内执行

  • 普通优先级,5秒后过期

我们知道了要实现异步可中断的更新,需要浏览器指定一个时间,如果没有时间剩余了就需要暂停任务,requestIdleCallback貌似是个不错的选择,但是它存在兼容和触发不稳定的原因,react17中采用MessageChannel来实现。

​ 在Scheduler中的每的每个任务的优先级使用过期时间表示的,如果一个任务的过期时间离现在很近,说明它马上就要过期了,优先级很高,如果过期时间很长,那它的优先级就低,没有过期的任务存放在timerQueue中过期的任务存放在taskQueue中,timerQueue和timerQueue都是小顶堆,所以peek取出来的都是离现在时间最近也就是优先级最高的那个任务,然后优先执行它。

function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {//shouldYield判断是否暂停任务
    workInProgress = performUnitOfWork(workInProgress); 
  }
}

在这里插入图片描述

Lane

​ react之前的版本用expirationTime属性代表优先级,该优先级和IO不能很好的搭配工作(io的优先级高于cpu的优先级),现在有了更加细粒度的优先级表示方法Lane,Lane用二进制位表示优先级,二进制中的1表示位置,同一个二进制数可以有多个相同优先级的位,这就可以表示‘批’的概念,而且二进制方便计算。

​ 这好比赛车比赛,在比赛开始的时候会分配一个赛道,比赛开始之后大家都会抢内圈的赛道(react中就是抢优先级高的Lane),比赛的尾声,最后一名赛车如果落后了很多,它也会跑到内圈的赛道,最后到达目的地(对应react中就是饥饿问题,低优先级的任务如果被高优先级的任务一直打断,到了它的过期时间,它也会变成高优先级)

React.createElement的源码中做了如下几件事

  1. 处理config,把除了保留属性外的其他config赋值给props
  2. 把children处理后赋值给props.children
  3. 处理defaultProps
  4. 调用ReactElement返回一个jsx对象

一个调度生命周期分为几个阶段

  1. 调度前:注册任务队列(环状链表(链表结构就是为了空间换时间,对于插入删除操作性能非常好),头接尾,尾接头),按照过期时间从小到大排列,如果当前任务是最饥饿的任务,则排到最前面,并立即开始调度,如果并不是最饥饿的任务,则放到队列中间或者最后面,不做任何操作,等待被调度
  2. 调度准备:通过requestAnimationFrame在下一次屏幕刚开始刷新的帧起点时计算当前帧的截止时间(33毫秒内)
    如果不超过当前帧的截止时间且当前任务没有过期,进入任务调度
    如果已经超过当前帧的截止时间,但没有过期,进入下一帧,并更新计算帧截止时间,重新判断时间(轮询判断),直到没有任何过期超时或者超时才进入任务调度
    如果已经超过当前帧的截止时间,同时已经过期,进入过期调度
  3. 正式调度:
    执行调度:在当前帧的截止时间前批量调用所有任务,不管是否过期
    过期调度:批量调用饥饿任务或超时任务的回调,删除任务节点
    调度完成:检查任务队列是否还有任务,先执行最饥饿的任务,如果存在任务,则进入下一帧,进入下一个调度生命周期

react17的出现

​ react之前的版本在reconcile的过程中是同步执行的,而计算复杂组件的差异可能是一个耗时操作,加之js的执行是单线程的,设备性能不同,页面就可能会出现卡顿的现象。此外应用所处的网络状况也不同,也需要应对不同网络状态下获取数据的响应,所以为了解决这两类(cpu、io)问题,react17带了全新的concurrent mode,它是一类功能的合集(如fiberschduler、lane、suspense),其目的是为了提高应用的响应速度,使应用不在那么卡顿,其核心是实现了一套异步可中断带优先级的更新。

​ 那么react17怎么实现异步可中断的更新呢,我们知道一般浏览器的fps是60Hz,也就是每16.6ms会刷新一次,而js执行线程和GUI也就是浏览器的绘制是互斥的,因为js可以操作dom,影响最后呈现的结果,所以如果js执行的时间过长,会导致浏览器没时间绘制dom,造成卡顿。react17会在每一帧分配一个时间(时间片)给js执行如果在这个时间内js还没执行完,那就要暂停它的执行,等下一帧继续执行,把执行权交回给浏览器去绘制
对比下开启和未开启concurrent mode的区别,开启之后,构建Fiber的任务的执行不会一直处于阻塞状态,而是分成了一个个的task
未开启concurrent
在这里插入图片描述
开启concurrent
在这里插入图片描述

Fiber双缓存

​ Fiber(Virtual dom)是内存中用来描述dom阶段的对象

​ 在它上面保存了包括这个节点的属性、类型、dom等,Fiber通过child、sibling、return(指向父节点)来形成Fiber树,

​ 还保存了更新状态时用于计算state的updateQueue,updateQueue是一种链表结构,上面可能存在多个未计算的update,update也是一种数据结构,上面包含了更新的数据、优先级等,

​ 除了这些之外,上面还有和副作用有关的信息。

​ 双缓存是指存在两颗Fiber树,current Fiber树描述了当前呈现的dom树,workInProgress Fiber是正在更新的Fiber树,这两颗Fiber树都是在内存中运行的,在workInProgress Fiber构建完成之后会将它作为current Fiber应用到dom上

​ 在mount时(首次渲染),会根据jsx对象(Class Component或的render函数者Function Component的返回值),构建Fiber对象,形成Fiber树,然后这颗Fiber树会作为current Fiber应用到真实dom上,在update(状态更新时如setState)的时候,会根据状态变更后的jsx对象和current Fiber做对比形成新的workInProgress Fiber,然后workInProgress Fiber切换成current Fiber应用到真实dom就达到了更新的目的,而这一切都是在内存中发生的,从而减少了对dom好性能的操作。

function App() {
  return (
    <div>
      xiao
      <p>chen</p>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById("root"));

在这里插入图片描述

2.Reconciler(在render阶段中执行):协调器

协调器是在render阶段工作的,简单一句话概括就是Reconciler会创建或者更新Fiber节点。在mount的时候会根据jsx生成Fiber对象,在update的时候会根据最新的state形成的jsx对象和current Fiber树对比构建workInProgress Fiber树,这个对比的过程就是diff算法。reconcile时会在这些Fiber上打上Tag标签,在commit阶段把这些标签应用到真实dom上,这些标签代表节点的增删改

​ render阶段遍历Fiber树类似dfs的过程,‘捕获’阶段发生在beginWork函数中,该函数做的主要工作是创建Fiber节点,计算state和diff算法,‘冒泡’阶段发生在completeWork中,该函数主要是做一些收尾工作,例如处理节点的props、和形成一条effectList的链表,该链表是被标记了更新的节点形成的链表
在这里插入图片描述

Reconciler中的diff算法

​ diff算法发生在render阶段的reconcileChildFibers函数中,diff算法分为单节点的diff和多节点的diff(例如一个节点中包含多个子节点就属于多节点的diff),单节点会根据节点的key和type,props等来判断节点是复用还是直接新创建节点,多节点diff会涉及节点的增删和节点位置的变化

3.Renderer(commit阶段中:渲染器)

​ Renderer是在commit阶段工作的,commit阶段会遍历render阶段形成的effectList,并执行真实dom节点的操作和一些生命周期,不同平台对应的Renderer不同,例如浏览器对应的就是react-dom。

​ commit阶段发生在commitRoot函数中,该函数主要遍历effectList,分别用三个函数来处理effectList上的节点,这三个函数是commitBeforeMutationEffectscommitMutationEffectscommitLayoutEffects
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值