人人都能读懂的react源码解析(大厂高薪必备)

2.react心智模型(来来来,让大脑有react思维吧)

视频解说

视频课程的目标是为了疾速把握react源码运行的过程和react中的scheduler、reconciler、renderer、fiber等,并且具体debug源码和剖析,过程更清晰。

视频解说(高效学习):点击学习

课程构造:

  1. <u>开篇(据说你还在艰巨的啃react源码)</u>
  2. <u>react心智模型(来来来,让大脑有react思维吧)</u>
  3. <u>Fiber(我是在内存中的dom)</u>
  4. <u>从legacy或concurrent开始(从入口开始,而后让咱们奔向将来)</u>
  5. <u>state更新流程(setState里到底产生了什么)</u>
  6. <u>render阶段(厉害了,我有创立Fiber的技能)</u>
  7. <u>commit阶段(据说renderer帮咱们打好标记了,映射实在节点吧)</u>
  8. <u>diff算法(妈妈再也不放心我的diff面试了)</u>
  9. <u>hooks源码(想晓得Function Component是怎么保留状态的嘛)</u>
  10. <u>scheduler&lane模型(来看看工作是暂停、持续和插队的)</u>
  11. <u>concurrent mode(并发模式是什么样的)</u>
  12. <u>手写迷你react(短小精悍就是我)</u>

在正式开始之前须要理解一下前置常识,当初不太分明没关系,这些内容会在前面的章节中呈现并且具体介绍,这一章的指标是理解react源码中存在的模型(数据结构或者思维)

react架构

react的外围能够用ui=fn(state)来示意,更具体能够用

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

react源码能够分为如下几个模块:

  • Scheduler(调度器): 排序优先级,让优先级高的工作先进行reconcile
  • Reconciler(协调器): 找出哪些节点产生了扭转,并打上不同的Tag
  • Renderer(渲染器): 将Reconciler中打好标签的节点渲染到视图上

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

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

    Renderer产生在commit阶段,commit阶段遍历effectList执行对应的dom操作或局部生命周期

    <img src="https://gitee.com/xiaochen1024/assets/raw/master/assets/_10.png" style="

    max-width: 100%;margin: 0 auto;display: block;

    ">

<img src="https://gitee.com/xiaochen1024/assets/raw/master/assets/_25.png" style="

max-width: 100%;margin: 0 auto;display: block;

">

react17的呈现是为了解决什么

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

那么react17怎么实现异步可中断的更新呢,咱们晓得个别浏览器的fps是60Hz,也就是每16.6ms会刷新一次,而js执行线程和GUI也就是浏览器的绘制是互斥的,因为js能够操作dom,影响最初出现的后果,所以如果js执行的工夫过长,会导致浏览器没工夫绘制dom,造成卡顿。react17会在每一帧调配一个工夫(工夫片)给js执行,如果在这个工夫内js还没执行完,那就要暂停它的执行,等下一帧继续执行,把执行权交回给浏览器去绘制。

<img src="https://gitee.com/xiaochen1024/assets/raw/master/assets/_24.png" style="

max-width: 100%;margin: 0 auto;display: block;

">

比照下开启和未开启concurrent mode的区别,开启之后,构建Fiber的工作的执行不会始终处于阻塞状态,而是分成了一个个的task

未开启concurrent

<img src="https://gitee.com/xiaochen1024/assets/raw/master/assets/_0.png" style="

max-width: 100%;margin: 0 auto;display: block;

">

开启concurrent

<img src="https://gitee.com/xiaochen1024/assets/raw/master/assets/_29.png" style="

max-width: 100%;margin: 0 auto;display: block;

">

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好性能的操作。

例如上面代码的Fiber双缓存构造如下,在第5章会具体解说

function App() {  return (    <div>      xiao      <p>chen</p>    </div>  )}ReactDOM.render(<App />, document.getElementById("root"));

<img src="https://gitee.com/xiaochen1024/assets/raw/master/assets/_17.png" style="

max-width: 100%;margin: 0 auto;display: block;

">

Reconciler(render阶段中)

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

export const Placement = /*             */ 0b0000000000010;export const Update = /*                */ 0b0000000000100;export const PlacementAndUpdate = /*    */ 0b0000000000110;export const Deletion = /*              */ 0b0000000001000;

render阶段遍历Fiber树相似dfs的过程,‘捕捉’阶段产生在beginWork函数中,该函数做的次要工作是创立Fiber节点,计算state和diff算法,‘冒泡’阶段产生在completeWork中,该函数次要是做一些收尾工作,例如解决节点的props、和造成一条effectList的链表,该链表是被标记了更新的节点造成的链表

深度优先遍历过程如下,图中的数字是程序,return指向父节点,第8章具体解说

<img src="https://gitee.com/xiaochen1024/assets/raw/master/assets/_5.png" style="

max-width: 100%;margin: 0 auto;display: block;

">

如果p和h1节点更新了则effectList如下,从rootFiber->h1->p,,顺便说下fiberRoot是整个我的项目的根节点,只存在一个,rootFiber是利用的根节点,可能存在多个,例如多个ReactDOM.render(<App />, document.getElementById("root"));创立多个利用节点

<img src="https://gitee.com/xiaochen1024/assets/raw/master/assets/_12.png" style="

max-width: 100%;margin: 0 auto;display: block;

">

Renderer(commit阶段中)

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

commit阶段产生在commitRoot函数中,该函数次要遍历effectList,别离用三个函数来解决effectList上的节点,这三个函数是commitBeforeMutationEffects、commitMutationEffects、commitLayoutEffects,他们次要做的事件如下,前面会具体解说,当初在大脑里有一个构造就行

<img src="https://gitee.com/xiaochen1024/assets/raw/master/assets/_13.png" style="

max-width: 100%;margin: 0 auto;display: block;

">

diff算法

diff算法产生在render阶段的reconcileChildFibers函数中,diff算法分为单节点的diff和多节点的diff(例如一个节点中蕴含多个子节点就属于多节点的diff),单节点会依据节点的key和type,props等来判断节点是复用还是间接新创建节点,多节点diff会波及节点的增删和节点地位的变动,具体见第10章。

Scheduler

咱们晓得了要实现异步可中断的更新,须要浏览器指定一个工夫,如果没有工夫残余了就须要暂停工作,requestIdleCallback貌似是个不错的抉择,然而它存在兼容和触发不稳固的起因,react17中采纳MessageChannel来实现。

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

在Scheduler中的每的每个工作的优先级应用过期工夫示意的,如果一个工作的过期工夫离当初很近,阐明它马上就要过期了,优先级很高,如果过期工夫很长,那它的优先级就低,没有过期的工作寄存在timerQueue中,过期的工作寄存在taskQueue中,timerQueue和timerQueue都是小顶堆,所以peek取出来的都是离当初工夫最近也就是优先级最高的那个工作,而后优先执行它。

<img src="https://gitee.com/xiaochen1024/assets/raw/master/assets/_23.png" style="

max-width: 100%;margin: 0 auto;display: block;

">

Lane

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

这好较量车较量,在较量开始的时候会调配一个赛道,较量开始之后大家都会抢内圈的赛道(react中就是抢优先级高的Lane),较量的序幕,最初一名赛车如果落后了很多,它也会跑到内圈的赛道,最初达到目的地(对应react中就是饥饿问题,低优先级的工作如果被高优先级的工作始终打断,到了它的过期工夫,它也会变成高优先级)

Lane的二进制位如下,从上往下,优先级递加

export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;export const SyncBatchedLane: Lane = /*                 */ 0b0000000000000000000000000000010;export const InputDiscreteHydrationLane: Lane = /*      */ 0b0000000000000000000000000000100;const InputDiscreteLanes: Lanes = /*                    */ 0b0000000000000000000000000011000;const InputContinuousHydrationLane: Lane = /*           */ 0b0000000000000000000000000100000;const InputContinuousLanes: Lanes = /*                  */ 0b0000000000000000000000011000000;export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000100000000;export const DefaultLanes: Lanes = /*                   */ 0b0000000000000000000111000000000;const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000001000000000000;const TransitionLanes: Lanes = /*                       */ 0b0000000001111111110000000000000;const RetryLanes: Lanes = /*                            */ 0b0000011110000000000000000000000;export const SomeRetryLane: Lanes = /*                  */ 0b0000010000000000000000000000000;export const SelectiveHydrationLane: Lane = /*          */ 0b0000100000000000000000000000000;const NonIdleLanes = /*                                 */ 0b0000111111111111111111111111111;export const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;const IdleLanes: Lanes = /*                             */ 0b0110000000000000000000000000000;export const OffscreenLane: Lane = /*                   */ 0b1000000000000000000000000000000;

jsx

jsx是ClassComponent的render函数或者FunctionComponent的返回值,能够用来示意组件的内容,在通过babel编译之后,最初会被编译成React.createElement,这就是为什么jsx文件要申明import React from 'react'的起因,你能够在 babel编译jsx 站点查看jsx被编译后的后果

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

  • 解决config,把除了保留属性外的其余config赋值给props
  • 把children解决后赋值给props.children
  • 解决defaultProps
  • 调用ReactElement返回一个jsx对象
export function createElement(type, config, children) {  let propName;  const props = {};  let key = null;  let ref = null;  let self = null;  let source = null;  if (config != null) {    //解决config,把除了保留属性外的其余config赋值给props    //...  }  const childrenLength = arguments.length - 2;  //把children解决后赋值给props.children  //...  //解决defaultProps  //...  return ReactElement(    type,    key,    ref,    self,    source,    ReactCurrentOwner.current,    props,  );}const ReactElement = function(type, key, ref, self, source, owner, props) {  const element = {    $$typeof: REACT_ELEMENT_TYPE,//示意是ReactElement类型    type: type,//class或function    key: key,//key    ref: ref,//useRef的ref对象    props: props,//props    _owner: owner,  };  return element;};

$$typeof示意的是组件的类型,例如在源码中有一个查看是否是非法Element的函数,就是根object.$$typeof === REACT_ELEMENT_TYPE来判断的

export function isValidElement(object) {  return (    typeof object === 'object' &&    object !== null &&    object.$$typeof === REACT_ELEMENT_TYPE  );}

如果组件是ClassComponent则type是class自身,如果组件是FunctionComponent创立的,则type是这个function,源码中用ClassComponent.prototype.isReactComponent来区别二者。留神class或者function创立的组件肯定要首字母大写,不而后被当成一般节点,type就是字符串。

jsx对象上没有优先级、状态、effectTag等标记,这些标记在Fiber对象上,在mount时Fiber依据jsx对象来构建,在update是依据最新状态的jsx和current Fiber比照,造成新的workInProgress Fiber,最初workInProgress Fiber切换成current Fiber

源码目录构造

源码中次要包含如下局部

  • fixtures:为代码贡献者提供的测试React
  • packages:次要局部,蕴含Scheduler,reconciler等
  • scripts:react构建相干

上面来看下packages次要蕴含的模块

  • react:外围Api如:React.createElement、React.Component都在这
  • 和平台相干render相干的文件夹:

    react-art:如canvas svg的渲染
    react-dom:浏览器环境
    react-native-renderer:原生相干
    react-noop-renderer:调试或者fiber用

  • 试验性的包

    react-server: ssr相干

    react-fetch: 申请相干

    react-interactions: 和事件如点击事件相干

    react-reconciler: 构建节点

  • shared:蕴含公共办法和变量
  • 辅助包:

    react-is : 判断类型

    react-client: 流相干

    react-fetch: 数据申请相干

    react-refresh: 热加载相干

  • scheduler:调度器相干
  • React-reconciler:在render阶段用它来构建fiber节点

怎么调试源码

本课程应用的react版本是17.0.1,通过上面几步就能够调试源码了,当然你能够用现成的蕴含本课程所有demo的我的项目来调试,倡议应用曾经构建好的我的项目,地址:https://github.com/xiaochen10...

  • clone源码:git clone https://github.com/facebook/react.git
  • 依赖装置:npm install or yarn
  • build源码:npm build react,react-dom,scheduler --type=NODE
  • 为源码建设软链:

    cd build/node_modules/reactnpm linkcd build/node_modules/react-domnpm link
  • create-react-app创立我的项目

    npx create-react-app demonpm link react react-dom

<img src="https://gitee.com/xiaochen1024/assets/raw/master/assets/_9.jpg" style="

  max-width: 100%;  margin: 0 auto;  display: block;

">