欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

React笔记-首次渲染

程序员文章站 2022-06-24 14:34:47
渲染机制 渲染机制主要分为两部分: 首次渲染和更新渲染。 首次渲染 首先通过一个小例子,来讲解首次渲染过程。 程序运行到 时,其中的 babel React.createElement(ClickCounter, null) element`如下: 接下来执行 函数,生成 节点。首先了解下 的部分数 ......

渲染机制

渲染机制主要分为两部分: 首次渲染和更新渲染。

首次渲染

首先通过一个小例子,来讲解首次渲染过程。

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>react app</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
import react from 'react';
import reactdom from 'react-dom';

class clickcounter extends react.component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  handleclick = () => {
    this.setstate((state) => {
      return {count: state.count + 1};
    });
  }
  render() {
    return [
      <button key="1" onclick={this.handleclick}>update counter</button>,
      <span key="2">{this.state.count}</span>,
    ]
  }
}
reactdom.hydrate(<clickcounter />, document.getelementbyid('root'));

程序运行到reactdom.hydrate时,其中的<clickcounter />已被babel转换为react.createelement(clickcounter, null),生成的element如下:

{
    $$typeof: symbol(react.element),
    key: null,
    props: {},
    ref: null,
    type: clickcounter
}

接下来执行hydrate函数,生成root节点。首先了解下fiber的部分数据结构。

  • alternate(对应的workinprogressfiber
  • statenode(关联的fiber,组件实例或者dom节点)
  • type(组件或html tag,如divspan等)
  • tag(类型,详见worktags
  • effecttag(操作类型,详见sideeffecttag
  • updatequeue(更新队列)
  • memoizedstate(state
  • memoizedprops(props
  • pendingprops(vdom
  • return(父fiber
  • sibling(兄弟fiber
  • child(孩子fiber
  • firsteffect(第一个待处理的effect fiber
  • lasteffect(最后一个待处理的effect fiber

首次渲染会以同步渲染的方式进行渲染,首先创建一个update,将element装载到其payload属性中,然后合并到root.current.updatequeue,如果没有updatequeue会创建一个。我们暂且将root.current看成hostroot

接着根据hostroot克隆一棵workinprogress更新树。将hostroot.alternate指向workinprogressworkinprogress.alternate指向hostroot。然后进入workloop进行更新树操作部分。workloop的任务也很简单,就是将所有节点的更新挂载到更新树上。下面详细看看reconciliation阶段。

reconciliation阶段

reconciliation的核心在于workloopworkloop会以workinprogress为起点,即克隆的hostroot,不断向下寻找。如果workinprogress.child不为空,会进行diff;如果为空会创建workinprogress.child`。

// 第一次循环nextunitofwork为workinprogress
function workloop(isyieldy) {
  if (!isyieldy) {
    // flush work without yielding
    while (nextunitofwork !== null) {
      nextunitofwork = performunitofwork(nextunitofwork);
    }
  } else {
    // flush asynchronous work until there's a higher priority event
    while (nextunitofwork !== null && !shouldyieldtorenderer()) {
      nextunitofwork = performunitofwork(nextunitofwork);
    }
  }
}

因为只涉及首次渲染,所以这里将performunitofwork简单化。beginwork根据workinprogress.tag选择不同的处理方式。先暂且看看如何处理hostroot。进入updatehostroot方法,先进行workinprogress.updatequeue的更新,计算新的state,将update.basestateworkinprogress.memoizedstate指向新的state。这里新的state装载的是element

接下来调用createfiberfromelement创建fiber,将workinprogress.child指向该fiberfiber.return指向workinprogress

function performunitofwork(workinprogress) {
  let next = beginwork(workinprogress); // 创建workinprogress.child并返回
  if (next === null) { // 没有孩子,收集effect list,返回兄弟或者父fiber
      next = completeunitofwork(workinprogress);
  }
  return next;
}

function beginwork(workinprogress) {
  switch(workinprogress.tag) {
    case hostroot:
      return updatehostroot(current, workinprogress, renderexpirationtime);
    case classcomponent: 
      ...
  }
}

用一张图体现更新树创建完成后的样子:

React笔记-首次渲染

workinprogress没有孩子时,即创建的孩子为空。说明已经到达底部,开始收集effect

function completeunitofwork(workinprogress) {
  while (true) {
    let returnfiber = workinprogress.return;
    let siblingfiber = workinprogress.sibling;
    nextunitofwork = completework(workinprogress);
   ...// 省略收集effect list过程
    if (siblingfiber !== null) {
      // if there is a sibling, return it
      // to perform work for this sibling
      return siblingfiber;
    } else if (returnfiber !== null) {
      // if there's no more work in this returnfiber,
      // continue the loop to complete the parent.
      workinprogress = returnfiber;
      continue;
    } else {
      // we've reached the root.
      return null;
    }
  }
}
function completework(workinprogress) {
  //根据workinprogress.tag创建、更新或删除dom
  switch(workinprogress.tag) {
    case hostcomponent: 
      ...
  }
  return null;
}

协调算法过程结束后,workinprogress更新树更新完毕,收集的effect list如下:

React笔记-首次渲染

commit阶段

effect list(链表)是reconciliation阶段的结果,决定了哪些节点需要插入、更新和删除,以及哪些组件需要调用生命周期函数。firsteffect记录第一个更新操作,firsteffect.nexteffect(fiber)记录下一个,然后继续通过其nexteffect不断往下寻找直至为null。下面是commit阶段的主要流程:

// finishedwork为更新树
function commitroot(root, finishedwork) {
    commitbeforemutationlifecycles();
    commitallhosteffects();
    root.current = finishedwork;
    commitalllifecycles();
}

变量nexteffect每次执行完上面一个函数会被重置为finishedwork

  • commitbeforemutationlifecycles

检查effect list中每个fiber是否有snapshot effect,如果有则执行getsnapshotbeforeupdate

// 触发getsnapshotbeforeupdate
function commitbeforemutationlifecycles() {
  while (nexteffect !== null) {
    const effecttag = nexteffect.effecttag;
    if (effecttag & snapshot) {
      const current = nexteffect.alternate;
      commitbeforemutationlifecycles(current, nexteffect);
    }
    nexteffect = nexteffect.nexteffect;
  }
}
  • commitallhosteffects

提交所有effect,实现dom的替换、更新和删除。

function commitallhosteffects() {
  while(nexteffect !== null) {
    var effecttag = nexteffect.effecttag;
    var primaryeffecttag = effecttag & (placement | update | deletion);
    switch (primaryeffecttag) {
      case placement: {
        commitplacement(nexteffect);
        ...
      }
      case placementandupdate: {
        commitplacement(nexteffect);
        var _current = nexteffect.alternate;
        commitwork(_current, nexteffect);
        ...
      }
      case update: {
        var _current2 = nexteffect.alternate;
        commitwork(_current2, nexteffect); 
        ...
      }
      case deletion: {// 触发componentwillunmout
        commitdeletion(nexteffect);
        ...
      }
    }
    nexteffect = nexteffect.nexteffect;
  }
}
  • commitalllifecycles

触发componentdidmountcomponentdidupdate

function commitalllifecycles(finishedroot, committedexpirationtime) {
  while (nexteffect !== null) {
    var effecttag = nexteffect.effecttag;

    if (effecttag & (update | callback)) {
      var current$$1 = nexteffect.alternate;
      commitlifecycles(finishedroot, current$$1, nexteffect, committedexpirationtime);
    }
    if (effecttag & ref) {
      commitattachref(nexteffect);
    }
    if (effecttag & passive) {
      rootwithpendingpassiveeffects = finishedroot;
    }

    nexteffect = nexteffect.nexteffect;
  }
}

总结

这里并未逐一细说,不想读起来直犯困,更多讲述了大概流程。如果觉得有疑惑的地方,也知道该在什么地方找到对应的源码,解答疑惑。

更好的阅读体验在我的,欢迎