当前位置: 移动技术网 > IT编程>开发语言>JavaScript > React笔记-首次渲染

React笔记-首次渲染

2019年04月15日  | 移动技术网IT编程  | 我要评论

渲染机制

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

首次渲染

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

<!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: 
      ...
  }
}

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

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如下:

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;
  }
}

总结

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

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网