「React 16」为 Luy 实现 React Fiber 架构

前言

Facebook 的研发能力真是惊人, Fiber 架构给 React 带来了新视野的同时,将调度一词介绍给了前端,然而这个架构实在很差懂,比起之前的 Vdom 树,新的 Fiber 树就麻烦太多。前端

能够说,React 16 和 React 15 已是技巧上的分水岭,可是得益于 React 16 的 Fiber 架构,使得 React 即便在没有开启异步的状况下,性能依旧是获得了提升。node

通过两个星期的痛苦研究,终于将 React 16 的渲染脉络摸得比较清晰,能够写文章来记录、回顾一下。react

若是你已经稍微理解了 Fiber 架构,能够直接看代码:仓库地址git

什么是 React Fiber ?

React Fiber 并非所谓的纤程(微线程、协程),而是一种基于浏览器的单线程调度算法,背后的支持 API 是大名鼎鼎的: requestIdleCallback ,获得了这个 API 的支持,咱们即可以将 React 中最耗时的部分放入其中。github

回顾 React 历年来的算法都知道,reconcilation 算法其实是一个大递归,大递归一旦进行,想要中断仍是比较很差操做的,加上头大尾大的 React 15 代码已经膨胀到了难以想象的地步,在重重压力之下,React 使用了大循环来代替以前的大递归,虽然代码变得比递归难懂了几个梯度,可是实际上,代码量比原来少了很是多(开发版本 3W 行压缩到了 1.3W 行)算法

那问题就来了,什么是 Fiber :一种将 recocilation (递归 diff ),拆分红无数个小任务的算法;它随时可以中止,恢复。中止恢复的时机取决于当前的一帧( 16ms )内,还有没有足够的时间容许计算。npm

React 16 先后的大小图

React 异步渲染流程图

  1. 用户调用 ReactDOM.render 方法,传入例如<App />组件,React 开始运做<App />
  2. <App /> 在内部会被转换成 RootFiber 节点,一个特殊的节点,并记录在一个全局变量中,TopTree
  3. 拿到 <App />RootFiber ,首先建立一个 <App /> 对应的 Fiber ,而后加上 Fiber 信息,以便以后回溯。随后,赋值给以前的全局变量 TopTree
  4. 使用 requestIdleCallback 重复第三个步骤,直到循环到树的全部节点
  5. 最后完成了 diff 阶段,一次性将变化更新到真实 DOM 中,以防止 UI 展现的不连续性

其中,重点就是 34 阶段,这两个阶段将建立真实 DOM 和组件渲染 ( render )拆分为无数的小碎块,使用 requestIdleCallback 连续进行。在 React 15 的时候,渲染、建立、插入、删除等操做是最费时的,在 React 16 中将渲染、建立抽离出来分片,这样性能就获得了极大的提高。数组

那为何更新到真实 DOM 中不能拆分呢?理论上来讲,是能够拆分的,可是这会形成 UI 的不连续性,极大的影响体验。浏览器

递归变成了循环

以简单的组件为例子:bash

  1. 从顶端的 div#root 向下走,先走左子树
  2. div 有两个孩子 span ,继续走左边的
  3. 来到 span ,之下只有一个 hello ,到此,再也不继续往下,而是往上回到 span
  4. 由于 span 有一个兄弟,所以往兄弟 span 走去
  5. 兄弟 span 有孩子 luy ,到此,不继续往下,而是回到 luy 的老爹 span
  6. luy 的老爹 span 右边没有兄弟了,所以回到其老爹 div
  7. div 没有任何的兄弟,所以回到顶端的 div#root

每通过一个 Fiber 节点,执行 render 或者 document.createElement (或者更新 DOM )的操做

Fiber 数据结构

一个 Fiber 数据结构比较复杂

const Fiber = {
  tag: HOST_COMPONENT,
  type: 'div',
  return: parentFiber,
  child: childFiber,
  sibling: null,
  alternate: currentFiber,
  stateNode: document.createElement('div') | instance,
  props: { children: [], className: 'foo' },
  partialState: null,
  effectTag: PLACEMENT,
  effects: []
}

这是一个比较完整的 Fiber object,他复杂的缘由是由于一个 Fiber 就表明了一个「正在执行或者执行完毕」的操做单元。这个概念不是那么好理解,若是要说得简单一点就是:之前的 VDOM 树节点的升级版。让咱们介绍几个关键属性:

  • 由「 递归改循环 」咱们能够得知,当咱们循环的遍历树到达底部时,须要回到其父节点,那么对应的就是 Fiber 中的 return 属性(之前叫 parent )。 childsibling 相似,表明这个 Fiber 的子 Fiber 和兄弟 Fiber
  • stateNode 这个属性比较特殊,用于记录当前 Fiber 所对应的真实 DOM 节点 或者 当前虚拟组件的实例,这么作的缘由第一是为了实现 Ref ,第二是为了实现 DOM 的跟踪
  • tag 属性在新版的 React 中一共有 14 种值,分别表明了不一样的 JSX 类型。
  • effectTageffects 这两个属性为的是记录每一个节点 Diff 后须要变动的状态,好比删除,移动,插入,替换,更新等...

alternate 属性我想拿出来单独说一下,这个属性是 Fiber 架构新加入的属性。咱们都知道,VDOM 算法是在更新的时候生成一颗新的 VDOM 树,去和旧的进行对比。在 Fiber 架构中,当咱们调用 ReactDOM.render 或者 setState 以后,会生成一颗树叫作:work-in-progress tree,这一颗树就是咱们所谓的新树用来与咱们的旧树进行对比,新的树和旧的树的 Fiber 是彻底不同的,此时,咱们就须要 alternate 属性去连接新树和旧树。

司徒正美的研究中,一个 Fiber 和它的 alternate 属性构成了一个联婴体,他们有共同的 tagtypestateNode 属性,这些属性在错误边界自爆时,用于恢复当前节点。

开始写代码:Component 构造函数

讲了那么多的理论,你们必定是晕了,可是没办法,Fiber 架构已经比以前的简单 React 要复杂太多了,所以不可能期望一次性把 Fiber 的内容所有理解,须要反复多看。

固然,结合代码来梳理,思路旧更加清晰了。咱们在构建新的架构时,老的 Luy 代码大部分都要进行重构了,先来看看几个主要重构的地方:

export class Component {
  constructor(props, context) {
    this.props = props
    this.context = context
    this.state = this.state || {}
    this.refs = {}
    this.updater = {}
  }

  setState(updater) {
    scheduleWork(this, updater)
  }

  render() {
    throw 'should implement `render()` function'
  }
}

Component.prototype.isReactComponent = true
  • 这就是 React.Component 的代码
  • 构造函数中,咱们都进两个参数,一个是外部的 props ,一个是 context
  • 内部有 staterefsupdaterupdater 用于收集 setState 的信息,便于以后更新用。固然,在这个版本之中,我并无使用。
  • setState 函数也并无作队列处理,只是调用了 scheduleWork 这个函数
  • Component.prototype.isReactComponent = true ,这段代码表饰着,若是一个组件的类型为 function 且拥有 isReactComponent ,那么他就是一个有状态组件,在建立实例时须要用 new ,而无状态组件只须要 fn(props,context) 调用
const tag = {
  HostComponent: 'host',
  ClassComponent: 'class',
  HostRoot: 'root',
  HostText: 6,
  FunctionalComponent: 1
}

const updateQueue = []

export function render(Vnode, Container, callback) {
  updateQueue.push({
    fromTag: tag.HostRoot,
    stateNode: Container,
    props: { children: Vnode }
  })

  requestIdleCallback(performWork) //开始干活
}

export function scheduleWork(instance, partialState) {
  updateQueue.push({
    fromTag: tag.ClassComponent,
    stateNode: instance,
    partialState: partialState
  })
  requestIdleCallback(performWork) //开始干活
}

咱们定义了一个全局变量 updateQueue 来记录咱们全部的更新操做,每当 renderscheduleWork (setState) 触发时,咱们都会往 updateQueuepush 一个状态,而后,进而调用大名鼎鼎的 requestIdleCallback 进行更新。在这里与以前的 react 15 最大不一样是,更新阶段和首次渲染阶段获得了统一,都是使用了 updateQueue 进行更新。

实际上这里还有优化的空间,就是屡次 setState 的时候,应该合并成一次再进行 requestIdleCallback 的调用,不过这并非咱们的目标,咱们的目标是搞懂 Fiber 架构。requestIdleCallback 调用的是 performWork 函数,咱们接下来看看

performWork 函数

const EXPIRATION_TIME = 1 // ms async 逾期时间
let nextUnitOfWork = null
let pendingCommit = null

function performWork(deadline) {
  workLoop(deadline)
  if (nextUnitOfWork || updateQueue.length > 0) {
    requestIdleCallback(performWork) //继续干
  }
}

function workLoop(deadline) {
  if (!nextUnitOfWork) {
    //一个周期内只建立一次
    nextUnitOfWork = createWorkInProgress(updateQueue)
  }

  while (nextUnitOfWork && deadline.timeRemaining() > EXPIRATION_TIME) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
  }

  if (pendingCommit) {
    //当全局 pendingCommit 变量被负值
    commitAllwork(pendingCommit)
  }
}

熟悉 requestIdleCallback 的同窗必定对这两个函数并不陌生,这两个函数其实作的就是所谓的异步调度。

requestIdleCallback 用法

performWork 函数主要作了两件事,第一件事就是拿到 deadline 进入咱们以前所谓的大循环,也就是正式进入处理新旧 FiberDiff 阶段,这个阶段比较的奇妙,咱们叫他 workLoop 阶段。workLoop 会一次处理 1 个或者多个 Fiber ,具体处理多少个,要看每一帧具体还剩下多少时间,若是一个 Fiber 消耗太多时间,那么就会等到下一帧再处理下一个 Fiber ,如此循环,遍历整个 VDOM 树。

在这里咱们注意到,若是一个 Fiber 消耗太多时间,可能会致使一帧时间的逾期,不过其实没什么问题啦,也仅仅是一帧逾期而已,对于咱们视觉上并无多大的影响。

workLoop 函数主要是三部曲:

  1. createWorkInProgress 这个函数会构建一颗树的顶端,赋值给全局变量 nextUnitOfWork ,经过迭代的方式,不断更新 nextUnitOfWork 直到遍历完全部树的节点。
  2. performUnitOfWork 函数是第二步,不断的检测当前帧是否还剩余时间,进行 WorkInProgress tree 的迭代
  3. WorkInProgress tree 迭代完毕之后,调用 commitAllWork ,将全部的变动所有一次性的更新到 DOM 中,以保证 UI 的连续性

全部的 Diff 和建立真实 DOM 的操做,都在 performUnitOfWork 之中,可是插入和删除是在 commitAllWork 之中。接下来,咱们逐一分析三部曲的内部操做。

第一步:createWorkInProgress

export function createWorkInProgress(updateQueue) {
  const updateTask = updateQueue.shift()
  if (!updateTask) return

  if (updateTask.partialState) {
    // 证实这是一个setState操做
    updateTask.stateNode._internalfiber.partialState = updateTask.partialState
  }

  const rootFiber =
    updateTask.fromTag === tag.HostRoot
      ? updateTask.stateNode._rootContainerFiber
      : getRoot(updateTask.stateNode._internalfiber)

  return {
    tag: tag.HostRoot,
    stateNode: updateTask.stateNode,
    props: updateTask.props || rootFiber.props,
    alternate: rootFiber // 用于连接新旧的 VDOM
  }
}

function getRoot(fiber) {
  let _fiber = fiber
  while (_fiber.return) {
    _fiber = _fiber.return
  }
  return _fiber

这个函数的主要做用就是构建 workInProgress 树的顶端并赋值给全局变量 nextUnitOfWork。

首先,咱们先从 updateQueue 中获取一个任务对象 updateTask 。随后,进行判断是不是更新阶段。而后获取 workInProgress 树的顶端。若是是第一次渲染, RootFiber 的值是空的,由于咱们并无构建任何的树。

最后,咱们将返回一个 Fiber 对象,这个 Fiber 对象的标识符( tag )是 HostRoot

第二步:performUnitOfWork

// 开始遍历
function performUnitOfWork(workInProgress) {
  const nextChild = beginWork(workInProgress)
  if (nextChild) return nextChild

  // 没有 nextChild, 咱们看看这个节点有没有 sibling
  let current = workInProgress
  while (current) {
    //收集当前节点的effect,而后向上传递
    completeWork(current)
    if (current.sibling) return current.sibling
    //没有 sibling,回到这个节点的父亲,看看有没有sibling
    current = current.return
  }
}

咱们调用 performUnitOfWork 处理咱们的 workInProgress

整个函数作的事情其实就是一个左遍历树的过程。首先,咱们调用 beginWork ,得到一个当前 Fiber 下的第一个孩子,若是有直接返回出去给 nextUnitOfWork ,看成下一个处理的节点;若是没有找到任何孩子,证实咱们已经到达了树的底部,经过下面的 while 循环,回到当前节点的父节点,将当前 Fiber 下拥有 Effect 的孩子所有记录下来,以便于以后更新 DOM

而后查找当前节点的父亲节点,是否有兄弟,有就返回,当成下一个处理的节点,若是没有,就继续回溯。

整个过程用图来表示,就是:

在讨论第三部以前,咱们仍然有两个迷惑的地方:

  1. beginWork 是如何建立孩子的
  2. completeWork 是如何收集 effect 的接下来,咱们就来一块儿看看

beginWork

function beginWork(currentFiber) {
  switch (currentFiber.tag) {
    case tag.ClassComponent: {
      return updateClassComponent(currentFiber)
    }
    case tag.FunctionalComponent: {
      return updateFunctionalComponent(currentFiber)
    }
    default: {
      return updateHostComponent(currentFiber)
    }
  }
}

function updateHostComponent(currentFiber) {
  // 当一个 fiber 对应的 stateNode 是原生节点,那么他的 children 就放在 props 里
  if (!currentFiber.stateNode) {
    if (currentFiber.type === null) {
      //表明这是文字节点
      currentFiber.stateNode = document.createTextNode(currentFiber.props)
    } else {
      //表明这是真实原生 DOM 节点
      currentFiber.stateNode = document.createElement(currentFiber.type)
    }
  }
  const newChildren = currentFiber.props.children
  return reconcileChildrenArray(currentFiber, newChildren)
}

function updateFunctionalComponent(currentFiber) {
  let type = currentFiber.type
  let props = currentFiber.props
  const newChildren = currentFiber.type(props)

  return reconcileChildrenArray(currentFiber, newChildren)
}

function updateClassComponent(currentFiber) {
  let instance = currentFiber.stateNode
  if (!instance) {
    // 若是是 mount 阶段,构建一个 instance
    instance = currentFiber.stateNode = createInstance(currentFiber)
  }

  // 将新的state,props刷给当前的instance
  instance.props = currentFiber.props
  instance.state = { ...instance.state, ...currentFiber.partialState }

  // 清空 partialState
  currentFiber.partialState = null
  const newChildren = currentFiber.stateNode.render()

  // currentFiber 表明老的,newChildren表明新的
  // 这个函数会返回孩子队列的第一个
  return reconcileChildrenArray(currentFiber, newChildren)
}

beginWork 实际上是一个判断分支的函数,整个函数的意思是:

  • 判断当前的 Fiber 是什么类型,是 class 的走 class 分支,是 stateless 的走 stateless,是原生节点的走原生分支
  • 若是没有 stateNode ,则建立一个 stateNode
  • 若是是 class ,则建立实例,调用 render 函数,渲染其儿子;若是是原生节点,调用 DOM API 建立原生节点;若是是 stateless ,就执行它,渲染出 VDOM 节点
  • 最后,走到最重要的函数, recocileChildrenArray 函数,将其每个孩子进行链表的连接,进行 diff ,而后返回当前 Fiber 之下的第一个孩子

咱们来看看比较重要的 classComponent 的构建流程

function updateClassComponent(currentFiber) {
  let instance = currentFiber.stateNode
  if (!instance) {
    // 若是是 mount 阶段,构建一个 instance
    instance = currentFiber.stateNode = createInstance(currentFiber)
  }

  // 将新的state,props刷给当前的instance
  instance.props = currentFiber.props
  instance.state = { ...instance.state, ...currentFiber.partialState }

  // 清空 partialState
  currentFiber.partialState = null
  const newChildren = currentFiber.stateNode.render()

  // currentFiber 表明老的,newChildren表明新的
  // 这个函数会返回孩子队列的第一个
  return reconcileChildrenArray(currentFiber, newChildren)
}

function createInstance(fiber) {
  const instance = new fiber.type(fiber.props)
  instance._internalfiber = fiber
  return instance
}

若是是首次渲染,那么组件并无被实例话,此时咱们调用 createInstance 实例化组件,而后将当前的 propsstate 赋值给 props 、state ,随后咱们调用 render 函数,得到了新儿子 newChildren

渲染出新儿子以后,来到了新架构下最重要的核心函数 reconcileChildrenArray .

reconcileChildrenArray

const PLACEMENT = 1
const DELETION = 2
const UPDATE = 3

function placeChild(currentFiber, newChild) {
  const type = newChild.type

  if (typeof newChild === 'string' || typeof newChild === 'number') {
    // 若是这个节点没有 type ,这个节点就多是 number 或者 string
    return createFiber(tag.HostText, null, newChild, currentFiber, PLACEMENT)
  }

  if (typeof type === 'string') {
    // 原生节点
    return createFiber(tag.HOST_COMPONENT, newChild.type, newChild.props, currentFiber, PLACEMENT)
  }

  if (typeof type === 'function') {
    const _tag = type.prototype.isReactComponent ? tag.CLASS_COMPONENT : tag.FunctionalComponent

    return {
      type: newChild.type,
      tag: _tag,
      props: newChild.props,
      return: currentFiber,
      effectTag: PLACEMENT
    }
  }
}

function reconcileChildrenArray(currentFiber, newChildren) {
  // 对比节点,相同的标记更新
  // 不一样的标记 替换
  // 多余的标记删除,而且记录下来
  const arrayfiyChildren = arrayfiy(newChildren)

  let index = 0
  let oldFiber = currentFiber.alternate ? currentFiber.alternate.child : null
  let newFiber = null

  while (index < arrayfiyChildren.length || oldFiber !== null) {
    const prevFiber = newFiber
    const newChild = arrayfiyChildren[index]
    const isSameFiber = oldFiber && newChild && newChild.type === oldFiber.type

    if (isSameFiber) {
      newFiber = {
        type: oldFiber.type,
        tag: oldFiber.tag,
        stateNode: oldFiber.stateNode,
        props: newChild.props,
        return: currentFiber,
        alternate: oldFiber,
        partialState: oldFiber.partialState,
        effectTag: UPDATE
      }
    }

    if (!isSameFiber && newChild) {
      newFiber = placeChild(currentFiber, newChild)
    }

    if (!isSameFiber && oldFiber) {
      // 这个状况的意思是新的节点比旧的节点少
      // 这时候,咱们要将变动的 effect 放在本节点的 list 里
      oldFiber.effectTag = DELETION
      currentFiber.effects = currentFiber.effects || []
      currentFiber.effects.push(oldFiber)
    }

    if (oldFiber) {
      oldFiber = oldFiber.sibling || null
    }

    if (index === 0) {
      currentFiber.child = newFiber
    } else if (prevFiber && newChild) {
      // 这里不懂是干吗的
      prevFiber.sibling = newFiber
    }

    index++
  }
  return currentFiber.child
}

这个函数作了几件事

  • 将孩子 array 化,这么作可以使得 reactrender 函数返回数组
  • currentFiber 是新的 workInProgress 上的一个节点,是属于新的 VDOM 树 ,而此时,咱们必需要找到旧的 VDOM 树来进行比对。那么在这里, Alternate 属性就起到了关键性做用,这个属性连接了旧的 VDOM ,使得咱们可以获取原来的 VDOM
  • 接下来咱们进行对比,若是新的节点的 type 与原来的相同,那么咱们将新建一个 Fiber ,标记这个 FiberUPDATE
  • 若是新的节点的 type 与原来的不相同,那咱们使用 PALCEMENT 来标记他
  • 若是旧的节点数量比新的节点少,那就证实,咱们要删除旧的节点,咱们把旧节点标记为 DELETION ,并构建一个 effect list 记录下来
  • 当前遍历的是组件的第一个孩子,那么咱们将他记录在 currentFiberchild 字段中
  • 当遍历的不是第一个孩子,咱们将 新建的 newFiber 用链表的形式将他们一块儿推入到 currentFiber
  • 返回当前 currentFiber 下的第一个孩子

看着比较啰嗦,可是实际上作的就是构建链表和 diff 孩子的过程,这个函数有不少优化的空间,使用 key 之后,在这里能提升不少的性能,为了简单,我并无对 key 进行操做,以后的 Luy 版本必定会的。

completeWork: 收集 effectTag

// 开始遍历
function performUnitOfWork(workInProgress) {
  const nextChild = beginWork(workInProgress)
  if (nextChild) return nextChild

  // 没有 nextChild, 咱们看看这个节点有没有 sibling
  let current = workInProgress
  while (current) {
    //收集当前节点的effect,而后向上传递
    completeWork(current)
    if (current.sibling) return current.sibling
    //没有 sibling,回到这个节点的父亲,看看有没有sibling
    current = current.return
  }
}

//收集有 effecttag 的 fiber
function completeWork(currentFiber) {
  if (currentFiber.tag === tag.classComponent) {
    // 用于回溯最高点的 root
    currentFiber.stateNode._internalfiber = currentFiber
  }

  if (currentFiber.return) {
    const currentEffect = currentFiber.effects || [] //收集当前节点的 effect list
    const currentEffectTag = currentFiber.effectTag ? [currentFiber] : []
    const parentEffects = currentFiber.return.effects || []
    currentFiber.return.effects = parentEffects.concat(currentEffect, currentEffectTag)
  } else {
    // 到达最顶端了
    pendingCommit = currentFiber
  }
}

这个函数作了两件事,第一件事情就是收集当前 currentFibereffectTag ,将其 append 到父 Fibereffectlist 中去,经过循环一层一层往上,最终到达顶端 currentFiber.return === void 666 的时候,证实咱们到达了 root ,此时咱们已经把全部的 effect 收集到了顶端的 currentFiber.effect 上,并把它赋值给 pendingCommit ,进入 commitAllWork 阶段。

第三步:commitAllWork

终于,咱们已经经过不断不断的调用 requestIdleCallback 和 大循环,将咱们的全部变动都找出来放在了 workInProgress tree 里,咱们接下来就要作最后一步:将全部的变动一次性的变动到真实 DOM 中,注意,这个阶段里咱们再也不运行建立 DOMrender ,所以,虽然咱们一次性变动全部的 DOM ,可是性能来讲并非太差。

function commitAllwork(topFiber) {
  topFiber.effects.forEach(f => {
    commitWork(f)
  })

  topFiber.stateNode._rootContainerFiber = topFiber
  topFiber.effects = []
  nextUnitOfWork = null
  pendingCommit = null
}

咱们直接拿到 TopFiber 中的 effects list ,遍历,将变动所有打到 DOM 中去,而后咱们将全局变量清理干净。

function commitWork(effectFiber) {
  if (effectFiber.tag === tag.HostRoot) {
    // 表明 root 节点没什么必要操做
    return
  }

  // 拿到parent的缘由是,咱们要将元素插入的点,插在父亲的下面
  let domParentFiber = effectFiber.return
  while (domParentFiber.tag === tag.classComponent || domParentFiber.tag === tag.FunctionalComponent) {
    // 若是是 class 就直接跳过,由于 class 类型的fiber.stateNode 是其自己实例
    domParentFiber = domParentFiber.return
  }

  //拿到父亲的真实 DOM
  const domParent = domParentFiber.stateNode
  if (effectFiber.effectTag === PLACEMENT) {
    if (effectFiber.tag === tag.HostComponent || effectFiber.tag === tag.HostText) {
      //经过 tag 检查是否是真实的节点
      domParent.appendChild(effectFiber.stateNode)
    }
    // 其余状况
  } else if (effectFiber.effectTag == UPDATE) {
    // 更新逻辑 只能是没实现
  } else if (effectFiber.effectTag == DELETION) {
    //删除多余的旧节点
    commitDeletion(effectFiber, domParent)
  }
}

function commitDeletion(fiber, domParent) {
  let node = fiber
  while (true) {
    if (node.tag == tag.classComponent) {
      node = node.child
      continue
    }
    domParent.removeChild(node.stateNode)
    while (node != fiber && !node.sibling) {
      node = node.return
    }
    if (node == fiber) {
      return
    }
    node = node.sibling
  }
}

这一部分代码是最好理解的了,就是作的是删除和插入或者更新 DOM 的操做,值得注意的是,删除操做依旧使用的链表操做。

最后来一段测试代码:

import React from './Luy/index'
import { Component } from './component'
import { render } from './vdom'

class App extends Component {
  state = {
    info: true
  }
  constructor(props) {
    super(props)

    setTimeout(() => {
      this.setState({
        info: !this.state.info
      })
    }, 1000)
  }

  render() {
    return (
      <div>
        <span>hello</span>
        <span>luy</span>
        <div>{this.state.info ? 'imasync' : 'iminfo'}</div>
      </div>
    )
  }
}
render(<App />, document.getElementById('root'))

咱们来看看动图吧!当节点 mount 之后,过了 1 秒,就会更新,咱们简单的更新就到此结束了


再看如下调用栈,咱们的 requestIdleCallback 函数已经正确的运行了。

若是你想下载代码亲自体验,能够到 Luy 仓库中:

git clone https://github.com/Foveluy/Luy.git
cd Luy
npm i --save-dev
npm run start

目前我能找到的全部资料都放在仓库中:资料

回顾本文几个重要的点

一开始咱们就使用了一个数组来记录 update 的信息,经过调用 requestIdleCallback 来将更新一个一个的取出来,大部分时间队列里只有一个。

取出来之后,使用从左向右遍历的方式,用链表连接一个一个的 Fiber ,并作 diff 和建立,最后一次性的 patch 到真实 DOM 中去。

如今 react 的架构已经变得极其复杂,而本文也只是将 React 的总体架构通篇流程描述了一遍,里面的细节依旧值得咱们的深究,好比,如何传递 context ,如何实现 ref ,如何实现错误边界处理,声明周期的处理,这些都是很大的话题,在接下去的文章里,我会一步一步的将这些关系讲清楚。

最后,感谢支持个人迷你框架项目:Luy ,如今正在向 Fiber 晋级!若是你喜欢,请给我一点 star🌟 表示鼓励!谢谢

若是有什么问题,能够加入咱们的学习 QQ 群: 370262116 ,群里几乎全部的迷你 React 做者都在了,包括 anu 做者司徒正美, omi 做者,我等,一块儿来学习吧!

相关文章
相关标签/搜索