什么是 Virtual DOM
- Virtual DOM(虚拟 DOM),是由普通的 JS 对象来描述 DOM 对象
- 使用 Virtual DOM 来描述真实 DOM
为什么要使用 Virtual DOM
- 前端开发刀耕火种的时代
- MVVM 框架解决视图和状态同步问题
- 模板引擎可以简化视图操作,没办法跟踪状态
- 虚拟 DOM 跟踪状态变化
- 虚拟 DOM 可以维护程序的状态,跟踪上一次的状态
- 通过比较前后两次状态差异更新真实 DOM
虚拟 DOM 的作用和虚拟 DOM 库
虚拟 DOM 的作用
- 维护视图和状态的关系
- 复杂视图情况下提升渲染性能
- 跨平台
- 浏览器平台渲染DOM
- 服务端渲染 SSR(Nuxt.js/Next.js)
- 原生应用(Weex/React Native)
- 小程序(mpvue/uni-app)等
虚拟 DOM 库
- Snabbdom
Vue.js 2.x 内部使用的虚拟 DOM 就是改造的 Snabbdom 大约 200 SLOC (single line of code) (源码大约就200行,所以研究虚拟DOM使用Snabbdom比研究Vue源码轻松) 通过模块可扩展(功能强大,类似于插件机制) 源码使用 TypeScript 开发 最快的 Virtual DOM 之一 - virtual-dom
Snabbdom 基本使用
创建项目
安装 parcel
- 创建项目目录
md snabbdom-demo
- 进入项目目录
cd snabbdom-demo
- 创建package.json
npm init -y
- 本地安装parcel
npm install parcel-bundler -D
注意:以上操作在 管理员身份运行的cmd窗口中操作,否则可能会在最后一步报错
配置 scripts
安装 parcel 之后,项目目录结构如下
"scripts": {
"dev": "parcel index.html --open",
"build": "parcel build index.html"
},
目录结构
- 根目录下创建入口文件index.html
- 创建src目录
Snabbdom 文档
导入 Snabbdom
创建完项目之后,要导入 Snabbdom
安装 Snabbdom
npm install snabbdom@2.1.0
导入 Snabbdom
- Snabbdom 的两个核心函数 init 和 h()
- init() 是一个高阶函数,返回 patch()
- h() 返回虚拟节点 VNode
补充
- 文档中导入的方式
- 实际导入的方式
parcel/webpack 4 不支持 package.json 中的 exports 字段
Demo
通过使用Snabbdom 模拟演示虚拟DOM的实现原理 index.html 引入 01-basicusage.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snabbdom-demo</title>
</head>
<body>
<div id="app"></div>
<script src="./src/01-basicusage.js"></script>
</body>
</html>
01-basicusage.js
- init函数(返回一个patch函数)作用:把虚拟 DOM 转化为真实 DOM 并挂载到 DOM树上
- h函数作用:创建虚拟 DOM,这里创建的是 VNode 虚拟节点
- vnode作用:描述真实 DOM
import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'
const patch = init([])
let vnode = h('div#container.cls','Hello World')
let app = document.querySelector('#app')
let oldVnode = patch(app, vnode)
vnode = h('div#container.xxx','Hello Snabbdom')
patch(oldVnode, vnode)
运行
npm run dev
浏览器执行结果 index.html 引入 02-basicusage.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snabbdom-demo</title>
</head>
<body>
<div id="app"></div>
<script src="./src/02-basicusage.js"></script>
</body>
</html>
02-basicusage.js
import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'
const 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', '这是一个ppppppppppp')
])
patch(oldVnode, vnode)
}, 2000)
浏览器执行结果 2s之后页面内容更新
|