>[success] # Snabbdom 1. **Vue 2.x** 内部使用的 `Virtual DOM` 就是改造的 `Snabbdom`,官网对其介绍Snabbdom 则**极其简单、高效并且可拓展,同时核心代码 ≈ 200 行**。我们提供了一个**具有丰富功能同时支持自定义拓展的模块化结构**。为了使核心代码更简洁,所有**非必要的功能都将模块化引入** >[info] ## Snabbdom 基本使用 1. `yarn init -y` 初始化项目 2. `yarn add snabbdom` 安装snabbdom 3. `yarn add webpack webpack-cli` 安装打包工具方便看运行效果 * 项目结构目录 ~~~ |-- dist | | -- index.html | | -- main.js // webpack 打包后产出打包文件 |-- package.json |-- pnpm-lock.yaml |-- src | |-- index.js ~~~ >[danger] ##### 简单案例 ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"></div> <script src="./src/index.js"></script> </body> </html> ~~~ * src/index.js ~~~ import { init, classModule, propsModule, styleModule, eventListenersModule, h, } from 'snabbdom' // 注册使用模块 let patch = init([ classModule, // 开启 classes 功能 propsModule, // 支持传入 props styleModule, // 支持内联样式同时支持动画 eventListenersModule, // 添加事件监听 ]) // 创建 虚拟node 节点 let vnode = h('div#container.cls', 'hello word') let app = document.querySelector('#app') // 将虚拟node 节点插入指定容器中 let oldVnode = patch(app, vnode) setTimeout(() => { // 假设的时刻改变后的内容 vnode = h( 'div', { style: { color: 'yellow' }, }, 'Hello Snabbdom' ) // patch 传入新老的虚拟dom对象,进行比较重新渲染 patch(oldVnode, vnode) }, 1000) ~~~ >[info] ## 使用 Snabbdom 简单说明 1. **Snabbdom 的核心库**并不能处理元素的**属性/样式/事件**等,如果需要处理的话,可以使用模块注册进入 2. 官方提供了六个模块 * `attributes设置 DOM 元素的属性`,使用`setAttribute ()`处理布尔类型的属性 * `props和 attributes 模块相似`,**设置 DOM 元素的属性中attributes设置 DOM 元素的属性**,使用 `setAttribute ()`处理布尔类型的属性`props`是`element[attr] = value`**不处理布尔类型的属性** * `class切换类样式`,给元素设置类样式是通过 sel 选择器 * `dataset设置 data-* `的自定义属性 3. 决定使用模块进行导入模块,在`init()` 中注册模块使用 `h()` 创建`Vnode`,`init`函数返回的`patch` * 关于`patch`函数 会接受两个参数,**一个 DOM 元素**或者 **一个表示当前视图的 vnode**,第一次创建时候第一个参数一般往往都是**一个 DOM 元素**,因为如果第一个参数传入一个包含父节点的 `DOM `元素,那么新的 `vnode `将转换为一个 `DOM `节点并替换传入的元素,后续为了进行比较新`老虚拟dom` 来优化渲染,所以新老的 `Vnode` 都要经过 `patch` 函数 * 案例点击时候切换dom ~~~ import { init, eventListenersModule, styleModule,h } from 'snabbdom' // 2. 注册模块 let patch = init([styleModule, eventListenersModule]) // 3. 使用 h() 函数的第二个参数传入模块需要的数据(对象) let vnode = h( 'div', { style: { backgroundColor: 'red', }, on: { click: eventHandler, }, }, [h('h1', 'Hello Snabbdom'), h('p', '这是p标签')] ) let app = document.querySelector('#app') let oldVnode = patch(app, vnode) function eventHandler() { vnode = h('div', 'hello') patch(oldVnode, vnode) } ~~~ >[info] ## 项目结构目录 ~~~ │ h.ts h()函数,用来创建 VNode │ hooks.ts 所有钩子函数的定义 │ htmldomapi.ts 对 DOM API 的包装 │ is.ts 判断数组和原始值的函数 │ jsx-global.d.ts jsx 的类型声明文件 │ jsx.ts 处理 jsx │ snabbdom.bundle.ts 入口,已经注册了模块 │ snabbdom.ts 初始化,返回 init/h/thunk │ thunk.ts 优化处理,对复杂视图不可变值得优化 │ tovnode.ts DOM 转换成 VNode │ vnode.ts 虚拟节点定义 │ ├─helpers │ attachto.ts 定义了 vnode.ts 中 AttachData 的数据结构 │ └─modules 所有模块定义 / 钩子函数 attributes.ts class.ts dataset.ts eventlisteners.ts hero.ts example 中使用到的自定义钩子 module.ts 定义了模块中用到的钩子函数 props.ts style.ts ~~~ >[info] ## 参考 [参考一个全程带注释链接](https://github.com/tramp-xu/codeAnalysis/blob/master/snabbdom-js/snabbdom.js)