React 中 state render 到 html dom 的流程分析

原文链接:https://github.com/xieyu/blog/blob/master/React/from-jsx-to-dom.md

作者:xieyu


React 中 state render 到 html dom 的流程分析

Questions

  1. React 的 component的 lifecycle 在 react 中是怎么被调到的.

  2. 分析 jsx => element tree => fiber tree => html dom 在 react 中的流程.

  3. react 中的 fiber tree 的建立和执行, 以及异步的 schedule.

研究工具和方法

  • chrome debug 打断点

  • ag the silver searcher, 源代码全局搜索.

  • 猜测它的实现原理,打 log, call trace 验证, console.log, console.trace;

准备工作

代码下载,编译

  
  
  1. $ git clone git@github.com:facebook/react.git

  2. $ cd react

  3. $ yarn install

  4. $ gulp react:extract-errors

  5. $ yarn build

Component lifeCycle callback

准备最简单的组件 HelloWorld

  
  
  1. import React from "react"

  2. import ReactDom from "react-dom"

  3. class HelloWorld extends React.Component{

  4.    constructor(props){

  5.        super(props);

  6.        this.state = {

  7.            message: "hello, world"

  8.        }

  9.    }

  10.    componentWillMount(){

  11.        console.log("component will mount");

  12.    }

  13.    componentWillUpdate(){

  14.        console.log("component will update");

  15.    }

  16.    componentDidUpdate(){

  17.        console.log("component did update");

  18.    }

  19.    componentDidMount(){

  20.        console.log("componentDidMount");

  21.    }

  22.    render(){

  23.        return <span className={this.state.message}>

  24.            {this.state.message}

  25.        </span>;

  26.    }

  27. }

  28. ReactDom.render(<HelloWorld/>, document.getElementById("app"));

在 componentWillMountcomponentDidMountcomponentWillUpdatecomponentDidUpdate 中打个断点

创建 html dom 的 callstack

react中最后一定会去调用 document.createElement 去创建 html 的 dom 节点,所以把 document.createElement 这个方法覆盖了,加了一层 log.

  
  
  1. var originCreateElement = document.createElement;

  2. document.createElement = function() {

  3.    if (arguments[0] === 'span'){

  4.        console.log('create span');

  5.    }

  6.   return originCreateElement.apply(document, arguments);

  7. }

然后打断点,得到的 callstack 如下:

call flow 整理

函数间的 callflow 整理如下

函数所属模块之间的 call flow 整理如下

Fiber

fiber 的设计思想

在 react-fiber-artchitecture 中作者描述了 fiber 的设计思想,简单来说,每个 fiber 就是一个执行单元,可以任意的修改它的优先级,可以 pause 它,之后再继续执行(感觉很像进程线程的概念)。

实际中执行一个 fiber 可以生成下一步要执行的 fiber,然后 fiber 执行之前可以检查时候 js 跑的时间时候用完了,如果用完了,就挂起来,等待下次 requestIdleCallback/requestAnimationFrame 的 callback, schedule 开始接着上次结束的地方继续执行 js code.

相当于把以前的 js function 的 call stack 改成 fiber chain 了。

workLoop 函数主要逻辑如下(注,删除了错误处理和其他不相干的 ifelse 分支) performWork

  
  
  1. // ReactScheduler.js workLoop

  2. if (deadline !== null && priorityLevel > TaskPriority) {

  3.      // The deferred work loop will run until there's no time left in

  4.      // the current frame.

  5.      while (nextUnitOfWork !== null && !deadlineHasExpired) {

  6.        if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {

  7.          nextUnitOfWork = performUnitOfWork(nextUnitOfWork);

  8.          if (nextUnitOfWork === null && pendingCommit !== null) {

  9.           // If we have time, we should commit the work now.

  10.           if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {

  11.             commitAllWork(pendingCommit);

  12.             nextUnitOfWork = findNextUnitOfWork();

  13.             // Clear any errors that were scheduled during the commit phase.

  14.           }

  15.         }

  16.       }

  17.   }

  18.  }

schedule

schedule 有同步和异步的,同步的会一直执行,直到 fiber tree 被执行结束,不会去检查 time 限制和 priorityLevel 的问题,异步的有两类权限,一个是 animation 的,一类是 HighPriority, OffScreen Priority 这个会有个 deadline.

在 preformwork 的末尾会去检查 nextLevelPriority 的优先权,然后根据优先权异步的 schedule.

  
  
  1. switch (nextPriorityLevel) {

  2.      case SynchronousPriority:

  3.      case TaskPriority:

  4.        // Perform work immediately by switching the priority level

  5.        // and continuing the loop.

  6.        priorityLevel = nextPriorityLevel;

  7.        break;

  8.      case AnimationPriority:

  9.        scheduleAnimationCallback(performAnimationWork);

  10.        // Even though the next unit of work has animation priority, there

  11.        // may still be deferred work left over as well. I think this is

  12.        // only important for unit tests. In a real app, a deferred callback

  13.        // would be scheduled during the next animation frame.

  14.        scheduleDeferredCallback(performDeferredWork);

  15.        break;

  16.      case HighPriority:

  17.      case LowPriority:

  18.      case OffscreenPriority:

  19.        scheduleDeferredCallback(performDeferredWork);

  20.        break;

  21.    }

fiber类型

FunctionalComponent, ClassComponent 对应着用户创建的 Component, HostRoot, HostComponent, HostPortal, HostText 这些是和平台相关的组件。对于 web 来说就是 div, span 这些 dom 元素了。

  
  
  1. // ReactTypeOfWork.js

  2. module.exports = {

  3.  IndeterminateComponent: 0, // Before we know whether it is functional or class

  4.  FunctionalComponent: 1,

  5.  ClassComponent: 2,

  6.  HostRoot: 3, // Root of a host tree. Could be nested inside another node.

  7.  HostPortal: 4, // A subtree. Could be an entry point to a different renderer.

  8.  HostComponent: 5,

  9.  HostText: 6,

  10.  CoroutineComponent: 7,

  11.  CoroutineHandlerPhase: 8,

  12.  YieldComponent: 9,

  13.  Fragment: 10,

  14. };

fiber 执行的三个阶段

react 中的 fiber 执行的执行主要分为三个阶段

  1. beginWork: fiber 展开(把ClassComponent render 开来,最后展开到 fiber tree 的叶子节点都是 hostComponent)


  2. completeWork: 计算 fiber 之间的 diff, 底层的 dom 元素的创建,以及 dom tree 的建立,还有事件绑定。


  3. commitWork: 调用 host 接口,把 fiber 的 diff 更新到 host 上去


begin work: fiber tree 的展开

每次的 beginWork(fiber), 会把 fiber 的所有直接子节点展开(这里只展开一层, 不会递归的去展开子节点的子节点)

  
  
  1. function performUnitOfWork(workInProgress: Fiber): Fiber | null {

  2.   const current = workInProgress.alternate;

  3.   let next = beginWork(current, workInProgress, nextPriorityLevel);

  4.   if (next === null) {

  5.     next = completeUnitOfWork(workInProgress);

  6.   }

  7.   return next;

  8. }

在 workloop 里面会把 beginWork 创建的子节点接着传给 beginWork,继续展开 fiber tree

  
  
  1. //workLoop

  2. while (nextUnitOfWork !== null && !deadlineHasExpired) {

  3.       if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {

  4.         nextUnitOfWork = performUnitOfWork(nextUnitOfWork);

`

completeWork 创建 dom 元素,计算 diff

创建的 instance(对于 html 来说,就是 dom 节点), 存储在 workInProgress.stateNode 里面, 计算好的 props diff 存放在了 workInProgress.updateQueue,在下一个阶段 commitWork 会把这个 updateQueue 里面的 patch 提交到 host。

commitWork 提交 diff

在 commitUpdate 中取 WorkInprogress.updateQueue, 然后调用 Dom 操作把 diff apply 上去


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值