深入理解虚拟DOM

Virtual DOM

JS对象来描述DOM对象。不是真实的DOM对象。

为什么使用Virtual DOM

  • 手动操作DOM比较麻烦 还要考虑浏览器兼容性 随着项目的复杂DOM会越来越复杂
  • 为了简化视图操作 使用模板引擎 但不能更好的解决跟踪状态的改变
  • 当状态改变时不需要立即更新DOM 只会创建一个虚拟树来描述DOM 虚拟DOM内部将弄清楚如何有效DIFF更新DOM
  • 虚拟DOM可以维护程序的状态,跟踪上一次的状态
  • 通过比较前后两次的状态差异更新真实的DOM

作用

  • 维护视图和状态的关系
  • 复杂视图情况下提升渲染性能
  • 除了渲染DOM以外 还可以实现SSR 原生 小程序等

Virtual DOM 库

  • Snabbdom (VUE2.0内部使用)
  • virtual-dom

Snabbdom 库

核心

  • 使用h()函数创建JS对象来描述真实DOM
  • init()设置模块 创建patch()
  • patch() 比较新旧两个vnode
  • 把变化的内容更新到真实的DOM树上
  1. h() 函数
    创建vnode,调用vnode函数返回虚拟节点
    new Vue render也使用了h函数
  2. Vnode
  3. patch
    patch(oldVnode,newVnode)
    diff过程 更新同级别的比较
  4. init
  5. createElm
  6. addVnodes removeVnodes
  7. patchVnode

1.创建项目

1
2
- src/index.js
- index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "snabbdom-demo",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev": "parcel index.html --open",
"build": "parcel build index.html"
},
"dependencies": {
"parcel-bundler": "^1.12.4",
"snabbdom": "^1.0.1"
}
}

2.导入及使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { h, thumk, init } from 'snabbdom'

// # Hello World
// path 对比两个vnode的差异更新到真实DOM
let patch = init([])
// 第一个参数 标签+选择器
// 第二个参数 如果是字符串就是标签的内容
let vnode = h('div#container.cls','HelloWorld')
// 第一个参数 可以是DOM元素 内部会把DOM元素转换成VNODE
// 第二个参数 Vnode
// 返回:Vnode
let app = document.querySelector('#app')
let oldVnode = patch(app, vnode)
// 假设的时刻
vnode = h('div','Hello Snabbdom')
patch(oldVnode, vnode)import { h, thumk, init } from 'snabbdom'

// # Hello World
// path 对比两个vnode的差异更新到真实DOM
let patch = init([])
// 第一个参数 标签+选择器
// 第二个参数 如果是字符串就是标签的内容
let vnode = h('div#container.cls','HelloWorld')
// 第一个参数 可以是DOM元素 内部会把DOM元素转换成VNODE
// 第二个参数 Vnode
// 返回:Vnode
let app = document.querySelector('#app')
let oldVnode = patch(app, vnode)
// 假设的时刻
vnode = h('div','Hello Snabbdom')
patch(oldVnode, vnode)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { h, init } from 'snabbdom'

// div中放置子元素h1 p

let patch = init([])
let vnode = h('div#container',[
h('h1','Hello Snabbdom'),
h('p','这是一个P标签'),
])
let app = document.querySelector('#app')
let oldVnode = patch(app, vnode)

setTimeout(()=>{
vnode = h('div#container',[
h('h1','Hello World'),
h('p','这是一个P标签 Hello P'),
])
patch(oldVnode, vnode)

// 清空页面 官方方法是错的 patch(oldVnode, null)
patch(oldVnode, h('!'))

},2000)

3.使用模块

核心库不能处理元素的属性样式事件,提供了常用模块

  • attributes 设置DOM属性 setAttribute()
  • props 设置DOM属性 element[attr] = value
  • class 样式
  • dataset 设置data-*自定义属性
  • eventlisteners 注册和移除事件
  • style 设置行内样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 导入模块
import {h,init} from 'snabbdom'
import style from 'snabbdom/modules/style'
import eventlistenners from 'snabbdom/modules/eventlistenners'
// 注册模块
let patch = init([style,eventlistenners])
// 使用H函数的第二个参数传所模块需要的数据(对象)
let vnode = h('div',{
style:{
backgroundColor:'red'
},
on:{
click:eventHandler
}
},[
h('h1','Hello Snabbdom'),
h('p','这个是P标签')
])
function eventHandler(){
console.log('click')
}
let app = document.querySelector('#app')