本文为拉勾网大前端高薪训练营第一期笔记
8.1.4 Virtual DOM 的实现原理
虚拟DOM
虚拟DOM就是js对象描述dom对象,成本比真实DOM低很多,因为真实DOM的属性特别多
以snabbdom为例,导入时需要这样写
import {init, h, thunk} from 'snabbdom'
创建脚手架
md snabbdom-demo
cd snabbdom-demo
yarn init -y
yarn add parcel-bundler
//package.json
scripts:
dev: parcel index.html --open
build: parcel build index.html
snabbdom基本用法例子
import { h, init } from 'snabbdom'
// 1. hello world
// 参数:数组,模块
// 返回值:patch函数,作用对比两个vnode的差异更新到真实DOM
let patch = init([])
// 第一个参数:标签+选择器
// 第二个参数:如果是字符串的话就是标签中的内容
let vnode = h('div#container.cls', {
hook: {
init (vnode) {
console.log(vnode.elm)
},
create (emptyVnode, vnode) {
console.log(vnode.elm)
}
}
}, 'Hello World')
let app = document.querySelector('#app')
// 第一个参数:可以是DOM元素,内部会把DOM元素转换成VNode
// 第二个参数:VNode
// 返回值:VNde
let oldVnode = patch(app, vnode)
//此时页面上是Hello World
vnode = h('div', 'Hello Snabbdom')
patch(oldVnode, vnode)
//此时页面上是Hello Snabbdom,对比了oldVnode和vnode的差异,然后更新
snabbdom设置子元素
// 2. div中放置子元素 h1,p
import { h, init } from 'snabbdom'
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', 'Hello P')
])
patch(oldVnode, vnode)
// 清空页面元素 -- 错误
// patch(oldVnode, null)
// 传注释节点来清空页面元素
patch(oldVnode, h('!'))
}, 2000);
使用Snabbdom模块
官方提供了6个模块
- attributes
- 设置DOM元素的属性,使用setAttribute()
- 处理布尔类型的属性
- props
- 和attributes模块相似,设置DOM元素的属性element[attr] = value
- 不处理布尔类型的属性
- class
- 切换类样式
- 注意:给元素设置样式是通过sel选择器
- dataset
- 设置data-*的自定义属性
- eventlisteners
- 注册和移除事件
- style
- 设置行内样式,支持动画
- delayed/remove/destroy
以style和eventlisteners为例
import { init, h } from 'snabbdom'
// 1. 导入模块
import style from 'snabbdom/modules/style'
import eventlisteners from 'snabbdom/modules/eventlisteners'
// 2. 注册模块
let patch = init([
style,
eventlisteners
])
// 3. 使用 h() 函数的第二个参数传入模块需要的数据(对象)
let vnode = h('div', {
style: {
backgroundColor: 'red'
},
on: {
click: eventHandler
}
}, [
h('h1', 'Hello Snabbdom'),
h('p', '这是p标签')
])
function eventHandler () {
console.log('点击我了')
}
let app = document.querySelector('#app')
let oldVnode = patch(app, vnode)
vnode = h('div', 'hello')
patch(oldVnode, vnode)
Snabbdom
- patch(oldVnode, newVnode)
- 打补丁,把新节点中变化的内容渲染到真实DOM,最后返回新节点作为下一次处理的旧节点
- 对比新旧VNode是否相同节点(节点的key和sel相同)
- 如果不是相同节点,删除之前的内容,重新渲染
- 如果是相同节点,再判断新的VNode是否有text,如果有并且和oldVnode的text不同,直接更新文件内容
- 如果新的VNode有children,判断子节点是否有变化,判断子节点的过程使用的就是diff算法
- diff过程只进行同层级比较
createElm
patchVnode
updateChildren
对比节点时是对比sel key,如果没有key,就认为两个li是一样的,此时直接更新text
- 对比新旧开始节点
- 如果不同,对比新旧结束节点
- 如果不同,对比旧开始,新结束节点
- 如果不同,对比旧结束,新开始节点