VNode:虚拟DOM
大概结构如下:
export interface VNode {_isVNode: trueel: Element | nullflags: VNodeFlagstag: string | FunctionalComponent | ComponentClass | nulldata: VNodeData | nullchildren: VNodeChildrenchildFlags: ChildrenFlags}
demo
一个简单的例子:
const elementVnode = {tag: 'div'}// 把 elementVnode 渲染到 id 为 app 的元素下render(elementVnode, document.getElementById('app'))function render(vnode, container) {mountElement(vnode, container)}function mountElement(vnode, container) {// 创建元素const el = document.createElement(vnode.tag)// 将元素添加到容器container.appendChild(el)}
组件用VNode表示:
// MyComponent 组件class MyComponent {render() {// render 函数产出 VNodereturn {tag: 'div'}}}// VNodeconst componentVnode = {tag: MyComponent}// 渲染render(componentVnode, document.getElementById('app'))function render(vnode, container) {if (typeof vnode.tag === 'string') {// html 标签mountElement(vnode, container)} else {// 组件mountComponent(vnode, container)}}function mountComponent(vnode, container) {// 创建组件实例const instance = new vnode.tag()// 渲染instance.$vnode = instance.render()// 挂载mountElement(instance.$vnode, container)}function mountElement(vnode, container) {// 创建元素const el = document.createElement(vnode.tag)// 将元素添加到容器container.appendChild(el)}
用VNode描述抽象内容
<div><MyComponent /></div>const elementVNode = {tag: 'div',data: null,children: {tag: MyComponent,data: null}}
可以通过检查 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 } ] }
- Portal```javascript<template><Portal target="#app-root"><div class="overlay"></div></Portal></template>//无论你在何处使用该组件,它都会把内容渲染到 id="app-root" 的元素下
VNode的种类:
flag
在 VNode 创建的时候就把该 VNode 的类型通过 flags 标明,这样在挂载或 patch 阶段通过 flags 可以直接避免掉很多消耗性能的判断,我们先提前感受一下渲染器的代码:
if (flags & VNodeFlags.ELEMENT) {// VNode 是普通标签mountElement(/* ... */)} else if (flags & VNodeFlags.COMPONENT) {// VNode 是组件mountComponent(/* ... */)} else if (flags & VNodeFlags.TEXT) {// VNode 是纯文本mountText(/* ... */)}
const VNodeFlags = {// html 标签ELEMENT_HTML: 1,// SVG 标签ELEMENT_SVG: 1 << 1,// 普通有状态组件COMPONENT_STATEFUL_NORMAL: 1 << 2,// 需要被keepAlive的有状态组件COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE: 1 << 3,// 已经被keepAlive的有状态组件COMPONENT_STATEFUL_KEPT_ALIVE: 1 << 4,// 函数式组件COMPONENT_FUNCTIONAL: 1 << 5,// 纯文本TEXT: 1 << 6,// FragmentFRAGMENT: 1 << 7,// PortalPORTAL: 1 << 8}// html 和 svg 都是标签元素,可以用 ELEMENT 表示VNodeFlags.ELEMENT = VNodeFlags.ELEMENT_HTML | VNodeFlags.ELEMENT_SVG// 普通有状态组件、需要被keepAlive的有状态组件、已经被keepAlice的有状态组件 都是“有状态组件”,统一用 COMPONENT_STATEFUL 表示VNodeFlags.COMPONENT_STATEFUL = VNodeFlags.COMPONENT_STATEFUL_NORMAL| VNodeFlags.COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE| VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE// 有状态组件 和 函数式组件都是“组件”,用 COMPONENT 表示VNodeFlags.COMPONENT = VNodeFlags.COMPONENT_STATEFUL | VNodeFlags.COMPONENT_FUNCTIONAL
VNode可以加上flag表示:
// html 元素节点const htmlVnode = {flags: VNodeFlags.ELEMENT_HTML,tag: 'div',data: null}// 函数式组件const functionalComponentVnode = {flags: VNodeFlags.COMPONENT_FUNCTIONAL,tag: MyFunctionalComponent}
| 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
VNode表示:```javascript// 没有子节点的 div 标签const elementVNode = {flags: VNodeFlags.ELEMENT_HTML,tag: 'div',data: null,children: null,childFlags: ChildrenFlags.NO_CHILDREN}// 文本节点的 childFlags 始终都是 NO_CHILDRENconst textVNode = {tag: null,data: null,children: '我是文本',childFlags: ChildrenFlags.NO_CHILDREN}// 只有一个子节点的 Fragmentconst elementVNode = {flags: VNodeFlags.FRAGMENT,tag: null,data: null,childFlags: ChildrenFlags.SINGLE_VNODE,children: {tag: 'p',data: null}}
data
{flags: VNodeFlags.ELEMENT_HTML,tag: 'div',data: {class: ['class-a', 'active'],style: {background: 'red',color: 'green'},// 其他数据...}}<MyComponent @some-event="handler" prop-a="1" />{flags: VNodeFlags.COMPONENT_STATEFUL,tag: 'div',data: {on: {'some-event': handler},propA: '1'// 其他数据...}}
那么当前的VNode对象为:
export interface VNode {// _isVNode 属性在上文中没有提到,它是一个始终为 true 的值,有了它,我们就可以判断一个对象是否是 VNode 对象_isVNode: true// el 属性在上文中也没有提到,当一个 VNode 被渲染为真实 DOM 之后,el 属性的值会引用该真实DOMel: Element | nullflags: VNodeFlagstag: string | FunctionalComponent | ComponentClass | nulldata: VNodeData | nullchildren: VNodeChildrenchildFlags: ChildrenFlags}
