Skip to main content

读《渲染器》笔记

原文《渲染器》

组件本质

  1. 一个组件就是一个函数,给到什么数据,就渲染出什么样的 html 内容。

  2. 组件产出的是virtual DOM。之所以要产出virtual DOM,是因为virtual DOM带来分层设计

  3. 我自己的理解,vnode 是一个对象,用多个属性描述其映射的DOM节点;通常用 tag 属性表示组件,如果是字符串,就是 html 原生标签;否则就是组件。

  4. 组件可分为函数组件和 有状态组件

它们的区别如下:

  • 函数式组件:

    • 是一个纯函数
    • 没有自身状态,只接收外部数据
    • 产出 VNode 的方式:单纯的函数调用
  • 有状态组件:

    • 是一个类,可实例化
    • 可以有自身状态
    • 产出 VNode 的方式:需要实例化,然后调用其 render 函数

结合 react 自己的理解:

在 react 中,函数组件直接调用,返回组件对象;class 组件实例化后调用 render 方法返回组件对象。其他在函数中或者 class 中的方法,都是为了处理渲染需要的数据。

jsx 被 babel 编译成React.createElement()方法,返回称为 react 元素的对象,根据这个对象生成 fiber 节点,这就是 react 中的 vnode。

VNode 的设计

  1. VNode 的种类

    vnode种类

    VNode 可以描述原生 html 元素,自定义组件,纯文本,Fragment,Portal

  2. VNode 的属性

  • tag,标识 VNode 的标签
  • data,标识 VNode 的属性
  • children,标识 VNode 的子节点
  • flags,标识 VNode 的种类
  • childrenFlags,标识子节点的种类

结合 react 自己的理解:

在 react 中 fiber 的结构与上述类似,通过一些属性来描述 fiber node 对应的元素。

源码中的 fiber 节点构造函数

Fiber 的结构,这篇文章解释了一些属性的含义。

创建 VNode

h 函数要能够创建出所有类型的 VNode。

  1. h 函数传入三个参数:tagdatachildren,其他 VNode 属性都可以根据这三个属性获得。

  2. 根据 tag 可以获得 VNode 的 flags

  3. 根据 children 可以获得 childrenFlags

结合 react,自己理解:

在 react 源码中,有对应的创建 fiber 的函数:createFiber,它其实是调用了构造函数:FiberNode

渲染器

渲染器,就是将Virtual DOM渲染成真实 DOM 的工具(就是一个函数,通常佳作 render)。渲染器主要包含两个工作流程:mountpatch

如果旧的 VNode 存在,就会对比新 VNode 和旧 VNode,然后完成 DOM 更新,这个过程叫 patch,或“打补丁”;如果旧的 VNode 不存在,则直接将新的 VNode 挂载成全新的 VNode,这个过程叫 mount。如果旧 VNode 存在,新 VNode 不存在,则这届将 DOM 移除。

mount

mount 函数的作用就是把一个 VNode 渲染成真实 DOM,根据不同的 VNode 类型,使用不同的挂载方式:

挂载函数

从 js 代码上理解,mount 函数的实现:根据 VNode.flags,调用不同的函数:mountElementmountComponentmountTextmountFragmentmountPortal

  1. 挂载普通元素

    通俗说,就是根据 tag 创建 DOM 元素,将其 append 到 container 中。还需要注意标签属性(style,class 等)、事件、children 等的处理

  2. 挂载纯文本、Fragment 和 Protal

  3. 挂载 class 组件和函数组件

    • 挂载 class 组件:创建组件实例 → 获取组件产出的 VNode → 把 VNode 挂载到 container → 组件实例的$el 属性引用组件的根 DOM 元素

    • 挂载函数组件:调用组件函数获取 VNode → 把 VNode 挂载到 container → vnode.el 属性引用组件的根 DOM 元素

patch

单词字面意思是打补丁,也能形象标识在程序中的意思:更新局部页面。

patch 的核心原则:如果新旧 VNode 的类型不同,则直接调用replaceNode函数,使用新 VNode 替换旧的 VNode,否则根据不同类型的 VNode 调用与之相符的比对函数,如下图:

对比函数

  1. 用新 VNode 替换旧 VNode 好理解,就是把旧的 VNode 渲染的 DOM 移除,再挂载新的 VNode。

  2. 更新 HTML 标签元素

    如果新旧 VNode 的标签不同,就直接调用replaceNode函数用新 VNode 替换;如果相同,那么更新就主要针对 VNodeData 和 children

    更新 VNodeData,简单说,就是用遍历新的 VNodedata 的属性,添加到 el 上;遍历旧的 VNodedata 属性,将新 data 中不存在的属性移除。

    难点是更新 children,涉及到多种情况判断。

  3. 更新文本节点、Fragment、Protal

  4. 更新 class 组件和函数组件