乐趣区

关于react.js:react

转自 React 技术揭秘

React15

React15架构能够分为 2 层:

  • Reconciler(协调器)————负责找出变动的组件,diff
  • Renderer(渲染器)————负责将变动的组件渲染到页面上

Reconciler(协调器)

react是通过 this.setState,this.forceUpdate,ReactDOM.renderAPI触发更新的。
每当有更新产生时,Reconciler会做如下工作:

  1. 调用函数组件、或 class 组件的 render 办法,将返回的 JSX 转化为虚构DOM
  2. 将虚构 DOM 和上次更时的虚构 DOM 比照
  3. 通过比照找出本次更新中变动的虚构DOM
  4. 告诉 Renderer 将变动的虚构 DOM 渲染到页面上

    Renderer(渲染器)

    因为 react 反对跨平台,所以不同平台有不同的 Renderer,浏览器的是ReactDOM
    因为用递归执行,所以没方法中断,当层级很深时,递归更新工夫超过了16ms,用户交互就会卡顿

state=1;
<li>{state.count}</li>//<li>1</li>
<li>{state.count*2}</li>//<li>2</li>

当点一个 state+1 时更新步骤:

  1. Reconciler发现 1 须要变为 2,告诉 RendererRenderer 更新DOM,1 变为 2。
  2. Reconciler发现 2 须要变为 4,告诉 RendererRenderer 更新DOM,2 变为 4。

能够看到,ReconcilerRenderer 是交替工作的,当第一个 li 在页面上曾经变动后。第二个 li 才进入Reconciler。就是发现扭转渲染扭转,扭转就渲染的模式

React16

react16的架构能够分为三层:

  • Scheduler(调度器)————调度工作的优先级,高优工作优先进入Reconciler
  • Reconciler(协调器)————负责找出变动的组件,diff,又被称为 render 阶段,在此阶段会调用组件的 render 办法。
  • Renderer(渲染器)————负责将变动的组件渲染到页面上,又被称为 commit 阶段,就像 git commit 一样把 render 阶段的信息提交渲染到页面上。
    rendercommit 阶段统称为work

    Scheduler(调度器)

    以浏览器是否有剩余时间作为工作中断的规范,也须要当浏览器有剩余时间时来告诉到咱们,相似 API:requistIdCallback
    就是判断浏览器有无剩余时间,如有按优先级继续执行Reconciler

    Reconciler(协调器)

    react15 是用递归来解决虚构 DOMreact16 的更新工作从递归变成能够中断的循环过程。

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

    React16 中,ReconcilerRenderer 不是交替工作,而是当 Scheduler 将工作交给 Reconciler 后,Reconciler会将变动的虚构 DOM 打上增 / 删 / 改的 tag
    整个 SchedulerReconciler的工作都在内存中进行,只有当所有组件都实现 Reconciler 的工作,才会对立交给 Renderer 渲染

    Renderer(渲染器)

    Renderer依据 Reconciler 为虚构 DOM 打的标记,同步执行对应的 DOM 操作。

state=1;
<li>{state.count}</li>//<li>1</li>
<li>{state.count*2}</li>//<li>2<li>

当点一个 state+1 时更新步骤:

  1. Scheduler接管到更新,看下有没有其它高优先更新要执行,没有的放将 state.count 从 1 变成 2,交给Reconciler
  2. Reconciler接管到更新,找出须要变动的虚构 DOM,发现在 1 要变成 2 打tag:Update, 又发现了 2 要变成 4 再给第二个打上tag:Update。都完了之后将打了标识的虚构DOMRenderer
  3. Renderer接管到告诉,找到打了 Update 标识的 2 个虚构 DOM,对它们执行更新DOM 的操作。
    2,3 步可随时因为有其它高优先级工作先更新或没有剩余时间而中断,但因为 2,3 都是在内存中进行,不会更新页面上的 DOM,所以就算重复中断,用记也不会看到更新一半的 DOM

    Fiber

    react15 及之前,Reconciler采纳递归的形式创立虚构 DOM,递归不能中断,如果组件树层级很深,递归工夫就多,线程开释不进去,就会造成卡顿,因为数据保留在递归栈中被称为stack Reconcilerreact16 将递归的无奈中断更新重构为异步的可中断更新,反对工作不同优先级,可中断与复原,复原后可利用之前的中间状态。每个工作更新单元为 React Element 对应的 Fiber 节点,基于 Fiber 节点实现叫Fiber Reconciler

    Fiber的构造

    function FiberNode(
      tag: WorkTag,
      pendingProps: mixed,
      key: null | string,
      mode: TypeOfMode,
    ) {
      // 作为静态数据构造的属性
      this.tag = tag;
      this.key = key;
      this.elementType = null;
      this.type = null;
      this.stateNode = null;
    
      // 用于连贯其余 Fiber 节点造成 Fiber 树
      this.return = null;// 指向父级 Fiber 节点
      this.child = null;// 指向子 Fiber 节点
      this.sibling = null;// 指向左边第一个兄弟 Fiber 节点
      this.index = 0;
    
      this.ref = null;
    
      // 作为动静的工作单元的属性, 保留本次更新造成的状态扭转相干信息
      this.pendingProps = pendingProps;
      this.memoizedProps = null;
      this.updateQueue = null;
      this.memoizedState = null;
      this.dependencies = null;
    
      this.mode = mode;
      // 保留本次更新会造成的 DOM 操作
      this.effectTag = NoEffect;
      this.nextEffect = null;
    
      this.firstEffect = null;
      this.lastEffect = null;
    
      // 调度优先级相干
      this.lanes = NoLanes;
      this.childLanes = NoLanes;
    
      // 指向该 fiber 在另一次更新时对应的 fiber
      this.alternate = null;
    }

    作为架构来说,每个 Fiber 节点有对应的 React element,多个Fiber 节点是靠 this.return,this.child,this.sibling 3 个属性连接成树的
    如组件构造对应的 Fiber 树,用 return 代指父节点

    function App() {
      return (
        <div>
          i am
          <span>KaSong</span>
        </div>
      )
    }


    render阶段顺次链式执行程序:

  4. rootFiber beginWork
  5. App Fiber beginWork
  6. div Fiber beginWork
  7. "i am" Fiber beginWork
  8. "i am" Fiber completeWork
  9. span Fiber beginWork
  10. span Fiber completeWork
  11. div Fiber completeWork
  12. App Fiber completeWork
  13. rootFiber completeWoek

    双缓存 Fiber

    在内存中构建并间接替换的技术叫双缓存
    react 应用双缓存来实现 Fiber 树的构建与替换 – 对应着 DOM 树的创立与更新。
    react 中最多会同时存在 2 棵 Fiber 树。以后屏幕上显示的 Fiber 树称为 current Fiber,正在内存构建的称为workInProgress Fiber
    当内存的 workInprogress Fiber 树构建实现交给 Renderer 渲染在页面上后,利用的要节点的 current 指针指向 workInProgress Fiber 树,workInProgress Fiber树就变成了 current Fiber 树。
    每次状态更新都会产生新的 workInProgress Fiber 树,通过 currentworkInProgress替换,实现 DOM 更新。在构建 workInProgress Fiber 树时会尝试复用 current Fiber 树中已有的 Fiber 节点内的属性,克隆 current.child 作为workInProgress.child,而不须要新建workInProgres.child

就是在组件 mount 时,Reconciler依据 JSX 形容的组件内容生成组件对应的 Fiber 节点。在组件第一次 mount 的时候只有 rootFiber 上有插入的 tag, 把Reconciler 生成的 DOM 树全副放在 rootFiber 下。
update 时,ReconcilerJSXFiber节点,保留的数据比照,生成组件对应的 Fiber 节点,并依据比照后果为 Fiber 节点打上标记。每个执行完 completeWork 且存在 effectTagFiber节点会被保留在 effectList(只蕴含它的子孙节点)的单向链表中。在commit 阶段只有遍历 effectList 就能执行所有的 effect 了。

diff

为了升高复杂度,reactdiff 预设了 3 个限度:

  1. 只对同级元素进行 diff,如果一个DOM 节点在前后 2 次更新中逾越了层级,那么 react 就不会复用它了。
  2. 2 个不同类型的元素会产生出不同的树,如果元素由 div 变为 preact 会销毁 div 及其子孙节点,并新建 p 及其子孙节点。
  3. 开发者能够通过 key prop 来暗示哪些子元素在不同的渲染下保持稳定,如:

    // 更新前
    <div>
      <p key="ka">ka</p>
      <h3 key="song">song</h3>
    </div>
    // 更新后
    <div>
      <h3 key="song">song</h3>
      <p key="ka">ka</p>
    </div>

    如果没有 keyreact 会认为 div 的第一个子节点由 p 变为 h3,第二个子节点由h3 变为 p。合乎第 2 条的规定,会销毁它并重建。
    然而当咱们用 key 指明了节点前后对应关系后,react晓得 key==='ka'p在更新后还存在,所以 DOM 节点能够复用,只是须要交接下程序。

退出移动版