VNode:虚拟DOM
大概结构如下:

  1. export interface VNode {
  2. _isVNode: true
  3. el: Element | null
  4. flags: VNodeFlags
  5. tag: string | FunctionalComponent | ComponentClass | null
  6. data: VNodeData | null
  7. children: VNodeChildren
  8. childFlags: ChildrenFlags
  9. }

demo

一个简单的例子:

  1. const elementVnode = {
  2. tag: 'div'
  3. }
  4. // 把 elementVnode 渲染到 id 为 app 的元素下
  5. render(elementVnode, document.getElementById('app'))
  6. function render(vnode, container) {
  7. mountElement(vnode, container)
  8. }
  9. function mountElement(vnode, container) {
  10. // 创建元素
  11. const el = document.createElement(vnode.tag)
  12. // 将元素添加到容器
  13. container.appendChild(el)
  14. }

组件用VNode表示:

  1. // MyComponent 组件
  2. class MyComponent {
  3. render() {
  4. // render 函数产出 VNode
  5. return {
  6. tag: 'div'
  7. }
  8. }
  9. }
  10. // VNode
  11. const componentVnode = {
  12. tag: MyComponent
  13. }
  14. // 渲染
  15. render(componentVnode, document.getElementById('app'))
  16. function render(vnode, container) {
  17. if (typeof vnode.tag === 'string') {
  18. // html 标签
  19. mountElement(vnode, container)
  20. } else {
  21. // 组件
  22. mountComponent(vnode, container)
  23. }
  24. }
  25. function mountComponent(vnode, container) {
  26. // 创建组件实例
  27. const instance = new vnode.tag()
  28. // 渲染
  29. instance.$vnode = instance.render()
  30. // 挂载
  31. mountElement(instance.$vnode, container)
  32. }
  33. function mountElement(vnode, container) {
  34. // 创建元素
  35. const el = document.createElement(vnode.tag)
  36. // 将元素添加到容器
  37. container.appendChild(el)
  38. }

用VNode描述抽象内容

  1. <div>
  2. <MyComponent />
  3. </div>
  4. const elementVNode = {
  5. tag: 'div',
  6. data: null,
  7. children: {
  8. tag: MyComponent,
  9. data: null
  10. }
  11. }

可以通过检查 tag 属性值是否是字符串来确定一个 VNode 是否是普通标签

tag

特殊的tag

  • Fragment:抽象的,不是真实存在的 ```javascript

const fragmentVNode = { // tag 属性值是一个唯一标识 tag: Fragment, data: null, children: [ { tag: ‘td’, data: null }, { tag: ‘td’, data: null }, { tag: ‘td’, data: null } ] }

  1. - Portal
  2. ```javascript
  3. <template>
  4. <Portal target="#app-root">
  5. <div class="overlay"></div>
  6. </Portal>
  7. </template>
  8. //无论你在何处使用该组件,它都会把内容渲染到 id="app-root" 的元素下

VNode的种类:
探究VNode - 图1

flag

在 VNode 创建的时候就把该 VNode 的类型通过 flags 标明,这样在挂载或 patch 阶段通过 flags 可以直接避免掉很多消耗性能的判断,我们先提前感受一下渲染器的代码:

  1. if (flags & VNodeFlags.ELEMENT) {
  2. // VNode 是普通标签
  3. mountElement(/* ... */)
  4. } else if (flags & VNodeFlags.COMPONENT) {
  5. // VNode 是组件
  6. mountComponent(/* ... */)
  7. } else if (flags & VNodeFlags.TEXT) {
  8. // VNode 是纯文本
  9. mountText(/* ... */)
  10. }
  1. const VNodeFlags = {
  2. // html 标签
  3. ELEMENT_HTML: 1,
  4. // SVG 标签
  5. ELEMENT_SVG: 1 << 1,
  6. // 普通有状态组件
  7. COMPONENT_STATEFUL_NORMAL: 1 << 2,
  8. // 需要被keepAlive的有状态组件
  9. COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE: 1 << 3,
  10. // 已经被keepAlive的有状态组件
  11. COMPONENT_STATEFUL_KEPT_ALIVE: 1 << 4,
  12. // 函数式组件
  13. COMPONENT_FUNCTIONAL: 1 << 5,
  14. // 纯文本
  15. TEXT: 1 << 6,
  16. // Fragment
  17. FRAGMENT: 1 << 7,
  18. // Portal
  19. PORTAL: 1 << 8
  20. }
  21. // html 和 svg 都是标签元素,可以用 ELEMENT 表示
  22. VNodeFlags.ELEMENT = VNodeFlags.ELEMENT_HTML | VNodeFlags.ELEMENT_SVG
  23. // 普通有状态组件、需要被keepAlive的有状态组件、已经被keepAlice的有状态组件 都是“有状态组件”,统一用 COMPONENT_STATEFUL 表示
  24. VNodeFlags.COMPONENT_STATEFUL = VNodeFlags.COMPONENT_STATEFUL_NORMAL
  25. | VNodeFlags.COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE
  26. | VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE
  27. // 有状态组件 和 函数式组件都是“组件”,用 COMPONENT 表示
  28. VNodeFlags.COMPONENT = VNodeFlags.COMPONENT_STATEFUL | VNodeFlags.COMPONENT_FUNCTIONAL

VNode可以加上flag表示:

  1. // html 元素节点
  2. const htmlVnode = {
  3. flags: VNodeFlags.ELEMENT_HTML,
  4. tag: 'div',
  5. data: null
  6. }
  7. // 函数式组件
  8. const functionalComponentVnode = {
  9. flags: VNodeFlags.COMPONENT_FUNCTIONAL,
  10. tag: MyFunctionalComponent
  11. }
VNodeFlags 左移运算 32 位的 bit 序列(出于简略,只用 9 位表示)
ELEMENT_HTML 000000001
ELEMENT_SVG 1 << 1 000000010
COMPONENT_STATEFUL_NORMAL 1 << 2 000000100
COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE 1 << 3 000001000
COMPONENT_STATEFUL_KEPT_ALIVE 1 << 4 000010000
COMPONENT_FUNCTIONAL 1 << 5 000100000
TEXT 1 << 6 001000000
FRAGMENT 1 << 7 010000000
PORTAL 1 << 8 100000000

根据上表展示的基本 flags 值可以很容易地得出下表:

VNodeFlags 32 位的比特序列(出于简略,只用 9 位表示)
ELEMENT 00000001 1
COMPONENT_STATEFUL 00001 1 100
COMPONENT 0001 1 1 100

一个标签的子节点会有以下几种情况:

  • 没有子节点
  • 只有一个子节点
  • 多个子节点
    • 有 key
    • 无 key
  • 不知道子节点的情况 ```javascript const ChildrenFlags = { // 未知的 children 类型 UNKNOWN_CHILDREN: 0, // 没有 children NO_CHILDREN: 1, // children 是单个 VNode SINGLE_VNODE: 1 << 1,

    // children 是多个拥有 key 的 VNode KEYED_VNODES: 1 << 2, // children 是多个没有 key 的 VNode NONE_KEYED_VNODES: 1 << 3 }

//children是多个 VNode ChildrenFlags.MULTIPLE_VNODES = ChildrenFlags.KEYED_VNODES | ChildrenFlags.NONE_KEYED_VNODES

  1. VNode表示:
  2. ```javascript
  3. // 没有子节点的 div 标签
  4. const elementVNode = {
  5. flags: VNodeFlags.ELEMENT_HTML,
  6. tag: 'div',
  7. data: null,
  8. children: null,
  9. childFlags: ChildrenFlags.NO_CHILDREN
  10. }
  11. // 文本节点的 childFlags 始终都是 NO_CHILDREN
  12. const textVNode = {
  13. tag: null,
  14. data: null,
  15. children: '我是文本',
  16. childFlags: ChildrenFlags.NO_CHILDREN
  17. }
  18. // 只有一个子节点的 Fragment
  19. const elementVNode = {
  20. flags: VNodeFlags.FRAGMENT,
  21. tag: null,
  22. data: null,
  23. childFlags: ChildrenFlags.SINGLE_VNODE,
  24. children: {
  25. tag: 'p',
  26. data: null
  27. }
  28. }

data

  1. {
  2. flags: VNodeFlags.ELEMENT_HTML,
  3. tag: 'div',
  4. data: {
  5. class: ['class-a', 'active'],
  6. style: {
  7. background: 'red',
  8. color: 'green'
  9. },
  10. // 其他数据...
  11. }
  12. }
  13. <MyComponent @some-event="handler" prop-a="1" />
  14. {
  15. flags: VNodeFlags.COMPONENT_STATEFUL,
  16. tag: 'div',
  17. data: {
  18. on: {
  19. 'some-event': handler
  20. },
  21. propA: '1'
  22. // 其他数据...
  23. }
  24. }

那么当前的VNode对象为:

  1. export interface VNode {
  2. // _isVNode 属性在上文中没有提到,它是一个始终为 true 的值,有了它,我们就可以判断一个对象是否是 VNode 对象
  3. _isVNode: true
  4. // el 属性在上文中也没有提到,当一个 VNode 被渲染为真实 DOM 之后,el 属性的值会引用该真实DOM
  5. el: Element | null
  6. flags: VNodeFlags
  7. tag: string | FunctionalComponent | ComponentClass | null
  8. data: VNodeData | null
  9. children: VNodeChildren
  10. childFlags: ChildrenFlags
  11. }