Snabbdom
前情提要
- 刀耕火种时代:手动操作dom,不需要考虑兼容性;随着时间推移,前端需求越来越复杂,需要处理更复杂的数据和dom结构。
- MVVM框架:解决视图和状态同步问题,当视图发生变化,自动更新数据;当数据发生变化,自动更新视图。
- 模板引擎:利于seo,数据安全性高,无须担心js被用户禁用,但是无法跟踪dom状态。
Snabbdom
一个高性能的虚拟DOM库
在vue的仓库中可以看到Snabbdom的身影,vue使用的虚拟dom基于Snabbdom改造。
vnode
一个普通的js对象,是一个节点描述对象,描述如何创建真实节点
export interface VNode {
// 选择器
sel: string | undefined;
data: VNodeData | undefined;
elm: Node | undefined;
// text和children为互斥关系
text: string | undefined;
children: Array<VNode | string> | undefined;
key: Key | undefined;
}
// 在patch过程中,通过vnode的key和sel选择器字段判断俩个节点是否相同
function sameVnode (vnode1: VNode, vnode2: VNode): boolean {
return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel
}
// 通过对象是否存在sel节点判断是vnode节点还是普通的dom节点
function isVnode (vnode: any): vnode is VNode {
// 通过是否有sel属性识别Vnode节点
return vnode.sel !== undefined
}
虚拟dom
-
解决的问题:描述应用的状态与视图的关系,将状态渲染成视图。jquery是命令式操作dom,状态不好管理,逻辑混乱;angular/react/vue都是声明式操作dom,只需要把状态维护好,如何操作dom由框架实现,虚拟dom就是描述状态的一种方式。
-
手动DOM操作比较麻烦,并且很难跟踪以前的DOM状态。每次更改网页状态时实际上都重新创建了整个DOM,则您的网页将非常缓慢,并且输入文本将失去焦点。js的运算速度比dom操作的执行速度快,使用patch计算出真正需要更新的节点,大大减少dom操作,提升性能。
-
跨平台,以JavaScript对象为基础而不依赖真实平台环境。
浏览器渲染dom
服务端渲染SSR
原生应用
小程序 -
虚拟dom过程:渲染函数——>vnode——>真实节点
Snabbdom小例子
创建虚拟dom,通过patch更新节点
import { init } from "Snabbdom/build/package/init"
import { h } from "Snabbdom/build/package/h"
const patch = init([])
// 参数1:标签+选择器
// 参数2:标签的文本内容
let vnode = h('div#container.cls', 'hello world')
let app = document.querySelector('#app')
// 参数1:旧的vnode或者dom元素
// 参数2:新的vnode
// 返回新的vnode
let oldnode = patch(app, vnode)
修改前
修改后
Snabbdom模块的作用
- Snabbdom的核心库不能处理dom元素的属性、样式、事件等,需要通过Snabbdom提供的模块实现
官方模块
-
attributes
<input type="button" id="p" value="prop全选" test="自定义属性" />
html标签的自定义与预定义属性。预定义(标签自带的)属性有:type,id,value,name等
-
props
js原生对象的直接属性。
-
dataset,处理html中data-xx属性
-
class
-
style
-
eventlisteners
使用模块功能演示prop和attr的区别
- 导入模块
- init方法中注册模块
- h函数的第二个参数中使用模块
import { init } from "snabbdom/build/package/init"
import { h } from "snabbdom/build/package/h"
import { styleModule } from 'snabbdom/build/package/modules/style'
import { eventListenersModule } from 'snabbdom/build/package/modules/eventlisteners'
import { attributesModule } from "snabbdom/build/package/modules/attributes";
import { propsModule } from "snabbdom/build/package/modules/props";
// init中注册模块
const patch = init([
styleModule,
eventListenersModule,
attributesModule,
propsModule
]);
let vnode
function render(attrCheck, propCheck) {
vnode = patch(vnode, view(attrCheck, propCheck));
}
function view (attrCheck, propCheck, propBox) {
const attrCheckOps = {
name: "attr1",
type: "checkbox",
checked: attrCheck
};
if (attrCheck) attrCheckOps.checked = "checked";
if (propBox) propCheck = propBox;
// h函数第二个参数为对象是表示模块配置,字符串为文本内容
return h("div", [
h(
"h1",
{
style: {
backgroundColor: "yellow",
},
},
"attr与prop的区别"
),
h("input", {
attrs: attrCheckOps,
}),
"attr控制的勾选框",
h("br"),
h("input#attrBtn", {
attrs: {
type: "button",
value: "attr按钮选中",
},
on: {
click: attrEventHandler,
},
}),
h("hr"),
h("input", {
attrs: {
name: "prop1",
type: "checkbox",
},
on: {
click: checkboxHandler,
},
props: {
checked: !!propCheck,
},
}),
"prop控制的勾选框",
h("br"),
h("input#propBtn", {
props: {
type: "button",
value: "prop按钮选中",
},
on: {
click: propEventHandler,
},
}),
]);
}
function attrEventHandler () {
console.log("点击触发attrClick事件");
render(true);
}
function propEventHandler () {
console.log("点击触发propClick事件");
render(null, true);
}
function checkboxHandler (e) {
render(null, null, !e.target.checked);
}
window.addEventListener("DOMContentLoaded", () => {
vnode = document.getElementById("app");
render();
});
操作1,分别通过俩个按钮去控制checkbox,可以选中,attr方式实现选中会在html标签上添加“checked”属性
操作2,手动点击勾选框取消选中,可以看到attr方式的“checked”属性依然存在,但是勾选框状态为未选中。手动点击勾选框修改的实际是dom对象的checked属性(prop),不是attributes中的checked(attr)。
- 当prop和attr的值为Boolean
同时存在时,浏览器以prop的值为准。
初始化时修改attr的值,prop会更新,一旦手动触发prop的值改变,再修改attr的值不会更新prop;
修改prop值不会触发attr更新 - 非Boolean值,attr和prop会同步更新
操作3,点击prop勾选框取消勾选,再点击prop按钮,无法选中勾选框
点击prop按钮,成功让props的checked设为true
dom元素的对象属性checked仍为false
为什么我们在代码中根据props模块提供的能力使prop属性为true,勾选框的checked值仍为false呢?
查看Snabbdom源码的props模块,可以看到内部是通过对比Vnode节点的新旧prop值来判断是否更新dom的prop值,导致它仍为dom元素的prop值没有变化。
所以,我们只要把勾选框的响应的状态变更都更新到prop对象中,Snabbdom才能知道dom状态已经发生变化。
Snabbdom还提供了hooks钩子方便我们操作更复杂的dom结构,里面最复杂的是patch方法的diff流程,同层对比新旧节点。