学习搭建一个 Mini React(1)

97 阅读3分钟

关于 React 的几个常见问题

Q:为什么用 Virtual DOM

什么是 VDOM

用更简单的 JS 对象描述一个DOM节点,这个对象就是 VDOM

为什么要用VDOM

虽然利用 VDOM最终还是需要操作 DOM,但是可以只操作需要的部分,VDOM 上只有需要的属性,比如事件、CSS等;比如 DOM节点的继承关系就属于不需要的部分。而通过 VDOM 去计算差异,只更新差异部分,更省事的遍历,这样减少 DOM操作。所以VDOM本质上也是服务于 diff 算法。

Q:为什么要用 JSX

什么是 JSX

语法糖,可以通过 HTML 的方式生成 VDOM

为什么需要

这样的书写方式可以复用前端在书写 HTML 经验去写 JS,也增加了格式上书写的便利

Q:Diff 算法的几种策略?

  1. type 相同,属性不同改属性
  2. type 不同,直接重新挂载
  3. 子节点 Key 不同,不可复用
  4. 子节点 Key 相同,可复用

Q:什么是 Fiber

Fiber 的中文译名是纤程,主要是为了解决之前的 Diff 算法的递归遍历不能打断、占用高的问题。通过纤程的方式对 ReactReconciler流程进行调整,这就是 Fiber在架构层面的含义。更具体的说,Fiber 节点是一个 JavaScript 对象,通过 Fiber 节点可以描述一个 VDOM

Fiber 节点的构建

属性

type 节点类型,有原生节点、class 节点、function 节点等几种类型

key 节点的 key 值,用于 diff 比较

props 节点的属性,比如一些常见 DOM 属性

stateNode 原生的 DOM 节点或是 class 组件的实例,function组件在 mount 时用不到

child是第一个子 Fiber

sibling 下一个兄弟Fiber

return是节点的父Fiber

flags标记节点的执行类型,比如 Placement挂载,Update 更新等等

alternate老节点

deletions要删除的子节点,null[]

index当前层级下的下标,从 0 开始

export function createFiber(vNode, returnFiber) {
  const fiber = {
    // 类型,原生 class function
    type: vNode.type,
    key: vNode.key,
    // 属性,dom 属性或者说 fiber 属性
    props: vNode.props,
    // 原生标签,stateNode 指的 DOM
    // class组件,指的是实例,函数组件不用这个
    stateNode: null,
    // 第一个子 fiber
    child: null,
    sibling: null,
    return: returnFiber,
    flags: Placement,
    alternate: null,
    deletions: null,
    // 老节点
    index: null,
  }

  // 函数组件 原生标签 类组件,通过 type 值作区分
  const { type } = vNode
  if(isStr(type)) {
    fiber.tag = HostComponent
  } else if (isFn(type)) {
    // TODO: 如何区分类组件和函数组件
    fiber.tag = FunctionComponent
  }
  return fiber
}

遍历方式

子节点 -> 兄弟节点 -> 父节点 -> 父节点的兄弟节点 -> ...(依次类推遍历),是一种深度优先遍历的方式

wipwork in progress 的意思,表示正在进行工作中的 Fiber 节点,这里的 wip 是一个全局量,最后都会执行一遍 performUnitOfWork 去进行 callback 工作(挂载或更新)。

由于 Fiber 以链表的方式进行深度遍历,所以需要用一个对象去进行循环操作,就是 next 通过对 next 依次向上查找,找到兄弟节点,如果没有其他兄弟节点,当循环结束时,wipnull,表示已经没有节点需要工作

let wip = rootNode // 默认值 null。在 mount 时会被赋值为 rootNode
// 每个 Fiber 节点,最后都会执行 performUnitOfWork
function performUnitOfWork() {
  // callback 里是更新等操作
  callback(wip)
  
  // 子节点 -> 兄弟节点 -> 父节点 -> 父节点的兄弟节点 -> ...(依次类推遍历)
  // 深度优先遍历
  if (wip.child) {
    wip = wip.child
    return
  }

  let next = wip
  while(next) {
    if (wip.sibling) {
      wip = next.sibling
      return
    }
    next = next.return
  }
  wip = null
}

image.png