报告:Vue.js内部当数据变化后,操作的是虚拟DOM,会对比新旧两次虚拟DOM的差异,再把差异更新到真实DOM
虚拟DOM库Snabbdom源码解析
Virtual DOM
虚拟DOM:由普通的JS对象来描述DOM对象,因为不是真实的DOM对象,所以叫Virtual DOM
{
sel: 'div',
data: {},
children: undefined,
text: '1',
elm: undefined,
key: undefined
}
为什么使用?
- 手动操作DOM麻烦,还需要考虑浏览器兼容性问题,虽有jQuery等简化DOM操作,随着项目的复杂DOM操作复杂提升
- 为了简化DOM复杂,有了
MVVM
框架,解决了视图和状态的同步问题 - 使用
模板引擎
简化视图,但是它没有解决跟踪状态变化问题,于是出现了Virtual DOM Virtual DOM
好处是当状态改变不需要立即更新DOM,创建一个虚拟树来描述DOM,Virtual DOM内部将弄清楚如何有效(diff
)的更新DOM
描述:
- 虚拟DOM可以维护程序的状态,跟踪上一次的状态
- 通过比较前后两次状态的差异更新真实DOM
作用
-
维护视图和状态的关系
-
复杂视图情况下提升渲染性能
-
除了渲染DOM以外,还可以实现SSR(
Nuxt.js/Next.js
)、原生应用(Weex/React Native
)、小程序(mpvue/uin-app
)等
Virtual DOM库
- Snabbdom
- virtual-dom
创建项目
- 打包工具:
parcel
- 创建项目,并安装
parcel
yarn init -y
yarn add parcel-bundler
- 配置
package.json
的scripts
"scripts": {
"dev": "parcel index.html --open",
"build": "parcel build index.html"
}
- 创建目录结构
index.html
package.json
-src
main.js
导入Snabbdom
- Snabbdom导入使用commonjs模块语法,流行使用ES6模块化语法
import
- ES6模块与CommonJS模块的差异
yarn add snabbdom
import { h, thunk, init } from 'snabbdom'
-
Snabbdom核心仅提供最基本的功能,只导出了三个函数init()、h()、thunk()
-
init()
是一个高阶函数,返回patch()
-
h()
返回虚拟节点VNode,这个函数我们在使用Vuejs的时候见过new Vue({ router, store, render: h => h(App) }).$mount('#app')
-
thunk()
是一种优化策略,可以在处理不可变数据时使用
-
-
注意:导入不能使用
import snabbdom from 'snabbdom'
-
原因:末尾到处使用的语法时export到处API,没有使用export default导出默认输出
export {h} from './h' export {thunk} from './thunk' export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI){ }
-
Snabbdom代码演示
- 1.hello world
//main.js
import {h, init} from 'snabbdom'
/**
* 1.hello world
* 参数:数组、模块
* 返回值:patch函数,作用对比两个vnode的差异更新到真实DOM
*/
let patch = init([])
/**
* 第一个参数:标签+选择器
* 第二个参数:如果是字符串的话就是标签中内容
*/
let vnode = h('div#container.cls', 'hello world')
let app = document.querySelector('#app')
/**
* 第一个参数:可以是DOM元素,内部会把DOM元素转换为VNode
* 第二个参数:VNode
* 返回值:VNode
*/
let oldVnode = patch(app, vnode)
//假设的时刻:覆盖之前的元素
vnode = h('div', 'hello snabbdom')
patch(oldVnode, vnode)
- div中放置子元素h1,p
import { h, init } from 'snabbdom'
let patch = init([])
let vnode = h('div#contatainer', [
h('h1', 'hello'),
h('p', 'p标签')
])
let app = document.querySelector('#app')
let oldVnode = patch(app, vnode)
setTimeout(() => {
vnode = h('div#contatainer', [
h('h1', '22222'),
h('p', '11111')
])
patch(oldVnode, vnode)
//清空页面元素-错误:无法读取null的key属性
//patch(oldVnode, null)
patch(oldVnode, h('!'))
},2000)
<script src="./src/main.js"></script>
<!--要引入-->
模块
- snabbdom的核心库并不能处理元素的属性/样式/事件等,如果需要处理的话,可以使用模块
- 常用模块:官方提供了6个模块
- attributes
- 设置DOM元素属性,使用
setAttribute()
- 处理布尔类型的属性
- 设置DOM元素属性,使用
- props
- 和
attributes
模块相似,设置DOM元素的属性element[attr] = value
- 不处理布尔类型的属性
- 和
- class
- 切换类样式
- 注意:给元素设置类样式是通过
sel
选择器
- dataset
- 设置
data-*
的自定义属性
- 设置
- eventlisteners
- 注册和移除事件
- style
- 设置行内样式,支持动画
delayed/remove/destroy
- attributes
模块使用
- 模块使用步骤:
- 导入需要的模块
init()
中注册模块- 使用
h()
函数创建VNode的时候,可以把第二个参数设置为对象,其它参数往后移
- 代码
//main.js
import { init, h } from 'snabbdom'
// 1.导入需要的模块
import style from 'snabbdom/modules/style'
import eventlisteners from 'snabbdom/modules/eventlisteners'
// 2.init()中注册模块
let patch = init([
style,
eventlisteners
])
// 3.使用h()函数的第二个参数传入模块需要的数据(对象)
/**以前的h()
* let vnode = h('div#contatainer', [
* h('h1', 'hello'),
* h('p', 'p标签')
* ])
*/
let vnode = h('div', {
style: {
backgroundColor: 'red'
},
on: {
click: eventHandler
}
},[
h('h1','hello!'),
h('p','这是p')
])
function eventHandler() {
console.log('1')
}
let app = document.quertSelector('#app')
patch(app, vnode)
Snabbdom源码解析
Snabbdom核心
- 使用h()函数创建JavaScript对象(VNode)描述真实DOM
- init()设置模块,创建patch()
- patch()比较新旧两个VNode
- 把变化的内容更新到真实DOM树上
h()
-
Vue中的h()函数支持组件机制,但Snabbdom不支持,但一样可以传入选择器
new Vue({ router, store, render: h => h(App) }).$mount('#app')
- h()最早适用于
hyperscript
,使用JavaScript创建超文本 - Snabbdom中h()不是创建超文本,而是创建VNode
- h()最早适用于
-
函数重载
- 参数个数或类型不同的函数
- JavaScript中没有重载的概念
- typescript中有重载,不过重载的实现还是通过代码调整参数
-
可以定义两个重名函数,通过参数个数、类型不同来区分
-
分析:
h.ts
中重载的几个h()函数
VNode
export interface VNode {
//选择器
sel: string | undefined;
//节点数据:属性/样式/事件
data: VNodeData | undefined;
//子节点:和text只能互斥
children: Array<VNode | string> | undefined;
//记录vnode对应的真实DOM
elm: Node | undefined;
//节点中的内容,和children只能互斥
text: string | undefined;
//优化用
key: Key | undefined;
}
VNode渲染真实DOM
patch(oldVnode, newVnode )
patch()
打补丁,把新节点中变化的内容渲染到真实DOM,最后返回新节点,作为下一次处理的旧节点patchVnode()
:对比新旧节点VNode是否是相同节点- 若不是相同节点,删除之前的内容
removeVnodes()
,重新渲染 - 如果是相同节点:
- 判断新的VNode是否有text,如果有并且和oldVnode的text不同,
setTextContent()
直接更新文本内容 updateChildren()
:如果新的VNode有children,判断子节点是否有变化,判断子节点的过程使用的就是diff算法
- 判断新的VNode是否有text,如果有并且和oldVnode的text不同,
- 若不是相同节点,删除之前的内容
- diff过程只进行同层级比较
init函数
- 返回一个patch(),是高阶函数
patch函数
- 把vnode渲染成真实dom
本文首发于我的GitHub博客,其它博客同步更新。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!