近几年随着 React、Vue 等前端框架一直衰亡,Virtual DOM 概念也越来越火,被用到越来越多的框架、库中。Virtual DOM 是基于实在 DOM 的一层形象,用简略的 JS 对象形容实在 DOM。本文要介绍的 Snabbdom 就是 Virtual DOM 的一种简略实现,并且 Vue 的 Virtual DOM 也参考了 Snabbdom 实现形式。
对于想要深刻学习 Vue Virtual DOM 的敌人,倡议先学习 Snabbdom,对了解 Vue 会很有帮忙,并且其外围代码 200 多行。
本文筛选 Snabbdom 模块零碎作为次要外围点介绍,其余内容能够查阅官网文档《Snabbdom》。
一、Snabbdom 是什么
Snabbdom 是一个专一于简略性、模块化、弱小个性和性能的虚构 DOM 库。其中有几个外围个性:
- 外围代码 200 行,并且提供丰盛的测试用例;
- 领有弱小模块零碎,并且反对模块拓展和灵便组合;
- 在每个 VNode 和全局模块上,都有丰盛的钩子,能够在 Diff 和 Patch 阶段应用。
接下来从一个简略示例来体验一下 Snabbdom。
1. 疾速上手
装置 Snabbdom:
npm install snabbdom -D
接着新建 index.html,设置入口元素:
<div id="app"></div>
而后新建 demo1.js 文件,并应用 Snabbdom 提供的函数:
// demo1.js
import {h} from 'snabbdom/src/package/h'
import {init} from 'snabbdom/src/package/init'
const patch = init([])
let vnode = h('div#app', 'Hello Leo')
const app = document.getElementById('app')
patch(app, vnode)
这样就实现一个简略示例,在浏览器关上 index.html,页面将显示“Hello Leo”文本。
接下来,我会以 snabbdom-demo 我的项目作为学习示例,从简略示例到模块零碎应用的示例,深刻学习和剖析 Snabbdom 源码,重点剖析 Snabbdom 模块零碎。
二、Snabbdom-demo 剖析
Snabbdom-demo 我的项目中的三个演示代码,为咱们展现如何从简略到深刻 Snabbdom。
首先克隆仓库并装置:
$ git clone https://github.com/zyycode/snabbdom-demo.git
$ npm install
尽管本我的项目没有 README.md 文件,但我的项目目录比拟直观,咱们能够轻松的从 src 目录找到这三个示例代码的文件:
- 01-basicusage.js
- 02-basicusage.js
- 03-modules.js -> 本文外围介绍
接着在 index.html 中引入想要学习的代码文件,默认 <script src="./src/01-basicusage.js"></script>
,通过 package.json 可知启动命令并启动我的项目:
$ npm run dev
1. 简略示例剖析
当咱们要钻研一个库或框架等比较复杂的我的项目,能够通过官网提供的简略示例代码进行剖析,咱们这里抉择该我的项目中最简略的 01-basicusage.js 代码进行剖析,其代码如下:
// src/01-basicusage.js
import {h} from 'snabbdom/src/package/h'
import {init} from 'snabbdom/src/package/init'
const patch = init([])
let vnode = h('div#container.cls', 'Hello World')
const app = document.getElementById('app') // 入口元素
const oldVNode = patch(app, vnode)
// 假如时刻
vnode = h('div', 'Hello Snabbdom')
patch(oldVNode, vnode)
运行我的项目当前,能够看到页面展现了“Hello Snabbdom”文本,这里你会感觉奇怪,后面的“Hello World”文本去哪了?
起因很简略,咱们把 demo 中的上面两行代码正文后,页面便显示文本是“Hello World”:
vnode = h('div', 'Hello Snabbdom')
patch(oldVNode, vnode)
这里咱们能够猜想 patch()
函数能够将 VNode 渲染到页面。更进一步能够了解为,这边第一个执行 patch()
函数为 首次渲染 ,第二次执行 patch()
函数为 更新操作。
2. VNode 介绍
这里可能会有小伙伴纳闷,示例中的 VNode 是什么?这里简略解释下:
VNode,该对象用于形容节点的信息,它的全称是虚构节点(virtual node)。与“虚构节点”相关联的另一个概念是“虚构 DOM”,它是咱们对由 Vue 组件树建设起来的整个 VNode 树的称说。“虚构 DOM”由 VNode 组成的。
—— 全栈修仙之路《Vue 3.0 进阶之 VNode 探秘》
其实 VNode 就是一个 JS 对象,在 Snabbdom 中是这么定义 VNode 的类型:
export interface VNode {
sel: string | undefined; // selector 的缩写
data: VNodeData | undefined; // 上面 VNodeData 接口的内容
children: Array<VNode | string> | undefined; // 子节点
elm: Node | undefined; // element 的缩写,存储了实在的 HTMLElement
text: string | undefined; // 如果是文本节点,则存储 text
key: Key | undefined; // 节点的 key,在做列表时很有用
}
export interface VNodeData {
props?: Props
attrs?: Attrs
class?: Classes
style?: VNodeStyle
dataset?: Dataset
on?: On
hero?: Hero
attachData?: AttachData
hook?: Hooks
key?: Key
ns?: string // for SVGs
fn?: () => VNode // for thunks
args?: any[] // for thunks
[key: string]: any // for any other 3rd party module
}
在 VNode 对象中含形容节点选择器 sel
字段、节点数据 data
字段、节点所蕴含的子节点 children
字段等。
在这个 demo 中,咱们仿佛并没有看到模块零碎相干的代码,没事,因为这是最简略的示例,下一节会具体介绍。
咱们在学习一个函数时,能够重点理解该函数的“入参”和“出参”,大抵就能判断该函数的作用。
从这个 demo 次要执行过程能够看出,次要用到有三个函数:init()
/ patch()
/ h()
,它们到底做什么用的呢?咱们剖析一下 Snabbdom 源码中这三个函数的入参和出参状况:
3. init() 函数剖析
init()
函数被定义在 package/init.ts
文件中:
// node_modules/snabbdom/src/package/init.ts
export function init (modules: Array<Partial<Module>>, domApi?: DOMAPI) {// 省略其余代码}
其参数类型如下:
function init(modules: Array<Partial<Module>>, domApi?: DOMAPI): (oldVnode: VNode | Element, vnode: VNode) => VNode
export type Module = Partial<{
pre: PreHook
create: CreateHook
update: UpdateHook
destroy: DestroyHook
remove: RemoveHook
post: PostHook
}>
export interface DOMAPI {createElement: (tagName: any) => HTMLElement
createElementNS: (namespaceURI: string, qualifiedName: string) => Element
createTextNode: (text: string) => Text
createComment: (text: string) => Comment
insertBefore: (parentNode: Node, newNode: Node, referenceNode: Node | null) => void
removeChild: (node: Node, child: Node) => void
appendChild: (node: Node, child: Node) => void
parentNode: (node: Node) => Node | null
nextSibling: (node: Node) => Node | null
tagName: (elm: Element) => string
setTextContent: (node: Node, text: string | null) => void
getTextContent: (node: Node) => string | null
isElement: (node: Node) => node is Element
isText: (node: Node) => node is Text
isComment: (node: Node) => node is Comment
}
init()
函数接管一个模块数组 modules
和可选的 domApi
对象作为参数,返回一个函数,即 patch()
函数。domApi
对象的接口蕴含了很多 DOM 操作的办法。
这里的 modules
参数本文将重点介绍。
4. patch() 函数剖析
init()
函数返回了一个 patch()
函数,其类型为:
// node_modules/snabbdom/src/package/init.ts
patch(oldVnode: VNode | Element, vnode: VNode) => VNode
patch()
函数接管两个 VNode 对象作为参数,并返回一个新 VNode。
5. h() 函数剖析
h()
函数被定义在 package/h.ts
文件中:
// node_modules/snabbdom/src/package/h.ts
export function h(sel: string): VNode
export function h(sel: string, data: VNodeData | null): VNode
export function h(sel: string, children: VNodeChildren): VNode
export function h(sel: string, data: VNodeData | null, children: VNodeChildren): VNode
export function h (sel: any, b?: any, c?: any): VNode{// 省略其余代码}
h()
函数接管多种参数,其中必须有一个 sel
参数,作用是将节点内容挂载到该容器中,并返回一个新 VNode。
6. 小结
通过后面介绍,咱们在回过头看看这个 demo 的代码,大抵调用流程如下:
三、深刻 Snabbdom 模块零碎
学习完后面这些基础知识后,咱们曾经晓得 Snabbdom 应用形式,并且晓得其中三个外围办法入参出参状况和大抵作用,接下来开始看本文外围 Snabbdom 模块零碎。
1. Modules 介绍
Snabbdom 模块零碎是 Snabbdom 提供的一套 可拓展 、 可灵便组合 的模块零碎,用来为 Snabbdom 提供操作 VNode 时的各种模块反对,如咱们组建须要解决 style 则引入对应的 styleModule,须要处理事件,则引入 eventListenersModule 既可,这样就达到灵便组合,能够反对按需引入的成果。
Snabbdom 模块零碎的特点能够概括为:反对按需引入、独立治理、职责繁多、不便组合复用、可维护性强。
当然 Snabbdom 模块零碎还有其余内置模块:
模块名称 | 模块性能 | 示例代码 |
---|---|---|
attributesModule | 为 DOM 元素设置属性,在属性增加和更新时应用 setAttribute 办法。 |
h('a', { attrs: { href: '/foo'} }, 'Go to Foo') |
classModule | 用来动静设置和切换 DOM 元素上的 class 名称。 | h('a', { class: { active: true, selected: false} }, 'Toggle') |
datasetModule | 为 DOM 元素设置自定义数据属性(data- * )。而后能够应用 HTMLElement.dataset 属性拜访它们。 |
h('button', { dataset: { action: 'reset'} }, 'Reset') |
eventListenersModule | 为 DOM 元素绑定事件监听器。 | h('div', { on: { click: clickHandler} }) |
propsModule | 为 DOM 元素设置属性,如果同时应用 attributesModule,则会被 attributesModule 笼罩。 | h('a', { props: { href: '/foo'} }, 'Go to Foo') |
styleModule | 为 DOM 元素设置 CSS 属性。 | h('span', {style: { color: '#c0ffee'}}, 'Say my name') |
2. Hooks 介绍
Hooks 也称钩子,是 DOM 节点生命周期的一种办法。Snabbdom 提供丰盛的钩子抉择。模块既应用钩子来扩大 Snabbdom,也在一般代码中应用钩子,用来在 DOM 节点生命周期中执行任意代码。
这里大抵介绍一下所有的 Hooks:
钩子名称 | 触发机会 | 回调参数 |
---|---|---|
pre |
patch 阶段开始。 | none |
init |
已增加一个 VNode。 | vnode |
create |
基于 VNode 创立了一个 DOM 元素。 | emptyVnode, vnode |
insert |
一个元素已增加到 DOM 元素中。 | vnode |
prepatch |
一个元素行将进入 patch 阶段。 | oldVnode, vnode |
update |
一个元素开始更新。 | oldVnode, vnode |
postpatch |
一个元素实现 patch 阶段。 | oldVnode, vnode |
destroy |
一个元素间接或间接被删除。 | vnode |
remove |
一个元素间接从 DOM 元素中删除。 | vnode, removeCallback |
post |
patch 阶段完结。 | none |
模块中能够应用这些钩子:pre
, create
, update
, destroy
, remove
, post
。
单个元素能够应用这些钩子:init
, create
, insert
, prepatch
, update
, postpatch
, destroy
, remove
。
Snabbdom 是这么定义钩子的:
// snabbdom/src/package/hooks.ts
export type PreHook = () => any
export type InitHook = (vNode: VNode) => any
export type CreateHook = (emptyVNode: VNode, vNode: VNode) => any
export type InsertHook = (vNode: VNode) => any
export type PrePatchHook = (oldVNode: VNode, vNode: VNode) => any
export type UpdateHook = (oldVNode: VNode, vNode: VNode) => any
export type PostPatchHook = (oldVNode: VNode, vNode: VNode) => any
export type DestroyHook = (vNode: VNode) => any
export type RemoveHook = (vNode: VNode, removeCallback: () => void) => any
export type PostHook = () => any
export interface Hooks {
pre?: PreHook
init?: InitHook
create?: CreateHook
insert?: InsertHook
prepatch?: PrePatchHook
update?: UpdateHook
postpatch?: PostPatchHook
destroy?: DestroyHook
remove?: RemoveHook
post?: PostHook
}
接下来咱们通过 03-modules.js 文件的示例代码,咱们须要 款式解决 和事件操作,因而引入这两个模块,并进行 灵便组合:
// src/03-modules.js
import {h} from 'snabbdom/src/package/h'
import {init} from 'snabbdom/src/package/init'
// 1. 导入模块
import {styleModule} from 'snabbdom/src/package/modules/style'
import {eventListenersModule} from 'snabbdom/src/package/modules/eventlisteners'
// 2. 注册模块
const patch = init([styleModule, eventListenersModule])
// 3. 应用 h() 函数的第二个参数传入模块须要的数据(对象)let vnode = h('div', {style: { backgroundColor: '#4fc08d', color: '#35495d'},
on: {click: eventHandler}
}, [h('h1', 'Hello Snabbdom'),
h('p', 'This is p tag')
])
function eventHandler() {console.log('clicked.')
}
const app = document.getElementById('app')
patch(app, vnode)
下面代码中,引入了 styleModule 和 eventListenersModule 两个模块,并且作为参数组合,传入 init()
函数中。
此时咱们能够看到页面上显示的内容曾经有蕴含款式,并且点击事件也能失常输入日志 'clicked.'
:
这里咱们看下 styleModule 模块源码,把代码精简一下:
// snabbdom/src/package/modules/style.ts
function updateStyle (oldVnode: VNode, vnode: VNode): void {// 省略其余代码}
function forceReflow () {// 省略其余代码}
function applyDestroyStyle (vnode: VNode): void {// 省略其余代码}
function applyRemoveStyle (vnode: VNode, rm: () => void): void {// 省略其余代码}
export const styleModule: Module = {
pre: forceReflow,
create: updateStyle,
update: updateStyle,
destroy: applyDestroyStyle,
remove: applyRemoveStyle
}
在看看 eventListenersModule 模块源码:
// snabbdom/src/package/modules/eventlisteners.ts
function updateEventListeners (oldVnode: VNode, vnode?: VNode): void {// 省略其余代码}
export const eventListenersModule: Module = {
create: updateEventListeners,
update: updateEventListeners,
destroy: updateEventListeners
}
显著能够看出,两个模块返回的都是个对象,并且每个属性为一种钩子,如 pre/create
等,值为对应的处理函数,每个处理函数有对立的入参。
持续看下 styleModule 中,款式是如何绑定下来的。这里剖析它的 updateStyle
办法,因为元素创立(create 钩子)和元素更新(update 钩子)阶段都是通过这个办法解决:
// snabbdom/src/package/modules/style.ts
function updateStyle (oldVnode: VNode, vnode: VNode): void {
var cur: any
var name: string
var elm = vnode.elm
var oldStyle = (oldVnode.data as VNodeData).style
var style = (vnode.data as VNodeData).style
if (!oldStyle && !style) return
if (oldStyle === style) return
// 1. 设置新旧 style 默认值
oldStyle = oldStyle || {}
style = style || {}
var oldHasDel = 'delayed' in oldStyle
// 2. 比拟新旧 style
for (name in oldStyle) {if (!style[name]) {if (name[0] === '-' && name[1] === '-') {(elm as any).style.removeProperty(name)
} else {(elm as any).style[name] = ''
}
}
}
for (name in style) {cur = style[name]
if (name === 'delayed' && style.delayed) {// 省略局部代码} else if (name !== 'remove' && cur !== oldStyle[name]) {if (name[0] === '-' && name[1] === '-') {(elm as any).style.setProperty(name, cur)
} else {
// 3. 设置新 style 到元素
(elm as any).style[name] = cur
}
}
}
}
3. init() 剖析
接着咱们看下 init()
函数外部如何解决这些 Module。
首先在 init.ts 文件中,能够看到申明了默认反对的 Hooks 钩子列表:
// snabbdom/src/package/init.ts
const hooks: Array<keyof Module> = ['create', 'update', 'remove', 'destroy', 'pre', 'post']
接着看 hooks
是如何应用的:
// snabbdom/src/package/init.ts
export function init (modules: Array<Partial<Module>>, domApi?: DOMAPI) {
let i: number
let j: number
const cbs: ModuleHooks = { // 创立 cbs 对象,用于收集 module 中的 hook
create: [],
update: [],
remove: [],
destroy: [],
pre: [],
post: []}
// 收集 module 中的 hook,并保留在 cbs 中
for (i = 0; i < hooks.length; ++i) {cbs[hooks[i]] = []
for (j = 0; j < modules.length; ++j) {const hook = modules[j][hooks[i]]
if (hook !== undefined) {(cbs[hooks[i]] as any[]).push(hook)
}
}
}
// 省略其余代码,稍后介绍
}
下面代码中,创立 hooks
变量用来申明默认反对的 Hooks 钩子,在 init()
函数中,创立 cbs
对象,通过两层循环,保留每个 module 中的 hook 函数到 cbs
对象的指定钩子中。
通过断点能够看到这是 demo 中,cbs
对象是上面这个样子:
这里 cbs
对象收集了每个 module 中的 Hooks 处理函数,保留到对应 Hooks 数组中。比方这里的 create
钩子中保留了 updateStyle
函数和 updateEventListeners
函数。
到这里,init()
函数曾经保留好所有 module 的 Hooks 处理函数,接下来就要看看 init()
函数返回的 patch()
函数,这外面将用到后面保留好的 cbs
对象。
4. patch() 剖析
init()
函数中最终返回一个 patch()
函数,这边造成一个闭包,闭包外面能够应用到 init()
函数作用域定义的变量和办法,因而在 patch()
函数中能应用 cbs
对象。
patch()
函数会在不同机会点(能够参照后面的 Hooks 介绍),遍历 cbs
对象中不同 Hooks 处理函数列表。
// snabbdom/src/package/init.ts
export function init (modules: Array<Partial<Module>>, domApi?: DOMAPI) {
// 省略其余代码
return function patch (oldVnode: VNode | Element, vnode: VNode): VNode {
let i: number, elm: Node, parent: Node
const insertedVnodeQueue: VNodeQueue = []
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]() // [Hooks]遍历 pre Hooks 处理函数列表
if (!isVnode(oldVnode)) {oldVnode = emptyNodeAt(oldVnode) // 当 oldVnode 参数不是 VNode 则创立一个空的 VNode
}
if (sameVnode(oldVnode, vnode)) { // 当两个 VNode 为同一个 VNode,则进行比拟和更新
patchVnode(oldVnode, vnode, insertedVnodeQueue)
} else {createElm(vnode, insertedVnodeQueue) // 当两个 VNode 不同,则创立新元素
if (parent !== null) { // 当该 oldVnode 有父节点,则插入该节点,而后移除原来节点
api.insertBefore(parent, vnode.elm!, api.nextSibling(elm))
removeVnodes(parent, [oldVnode], 0, 0)
}
}
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]() // [Hooks]遍历 post Hooks 处理函数列表
return vnode
}
}
patchVnode()
函数定义如下:
function patchVnode (oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
// 省略其余代码
if (vnode.data !== undefined) {for (let i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) // [Hooks]遍历 update Hooks 处理函数列表
}
}
createVnode()
函数定义如下:
function createElm (vnode: VNode, insertedVnodeQueue: VNodeQueue): Node {
// 省略其余代码
const sel = vnode.sel
if (sel === '!') {// 省略其余代码} else if (sel !== undefined) {for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode) // [Hooks]遍历 create Hooks 处理函数列表
const hook = vnode.data!.hook
}
return vnode.elm
}
removeNodes()
函数定义如下:
function removeVnodes (parentElm: Node,vnodes: VNode[],startIdx: number,endIdx: number): void {
// 省略其余代码
for (; startIdx <= endIdx; ++startIdx) {const ch = vnodes[startIdx]
if (ch != null) {rm = createRmCb(ch.elm!, listeners)
for (let i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm) // [Hooks]遍历 remove Hooks 处理函数列表
}
}
}
这部分代码跳转较多,总结一下这个过程,如下图:
四、自定义 Snabbdom 模块
后面咱们介绍了 Snabbdom 模块零碎是如何收集 Hooks 并保留下来,而后在不同机会点执行不同的 Hooks。
在 Snabbdom 中,所有模块独立在 src/package/modules
下,应用的时候能够灵便组合,也不便做解耦和跨平台,并且所有 Module 返回的对象中每个 Hooks 类型如下:
// snabbdom/src/package/init.ts
export type Module = Partial<{
pre: PreHook
create: CreateHook
update: UpdateHook
destroy: DestroyHook
remove: RemoveHook
post: PostHook
}>
// snabbdom/src/package/hooks.ts
export type PreHook = () => any
export type CreateHook = (emptyVNode: VNode, vNode: VNode) => any
export type UpdateHook = (oldVNode: VNode, vNode: VNode) => any
export type DestroyHook = (vNode: VNode) => any
export type RemoveHook = (vNode: VNode, removeCallback: () => void) => any
export type PostHook = () => any
因而,如果开发者须要自定义模块,只需实现不同 Hooks 并导出即可。
接下来咱们实现一个简略的模块 replaceTagModule,用来 将节点文本主动过滤掉 HTML 标签。
1. 初始化代码
思考到不便调试,咱们间接在 node_modules/snabbdom/src/package/modules/
目录中新建 replaceTag.ts 文件,而后写个最简略的 demo 框架:
import {VNode, VNodeData} from '../vnode'
import {Module} from './module'
const replaceTagPre = () => {console.log("run replaceTagPre!")
}
const updateReplaceTag = (oldVnode: VNode, vnode: VNode): void => {console.log("run updateReplaceTag!", oldVnode, vnode)
}
const removeReplaceTag = (vnode: VNode): void => {console.log("run removeReplaceTag!", vnode)
}
export const replaceTagModule: Module = {
pre: replaceTagPre,
create: updateReplaceTag,
update: updateReplaceTag,
remove: removeReplaceTag
}
接下来引入到 03-modules.js 代码中,并简化下代码:
import {h} from 'snabbdom/src/package/h'
import {init} from 'snabbdom/src/package/init'
// 1. 导入模块
import {styleModule} from 'snabbdom/src/package/modules/style'
import {eventListenersModule} from 'snabbdom/src/package/modules/eventlisteners'
import {replaceTagModule} from 'snabbdom/src/package/modules/replaceTag';
// 2. 注册模块
const patch = init([
styleModule,
eventListenersModule,
replaceTagModule
])
// 3. 应用 h() 函数的第二个参数传入模块须要的数据(对象)let vnode = h('div', '<h1>Hello Leo</h1>')
const app = document.getElementById('app')
const oldVNode = patch(app, vnode)
let newVNode = h('div', '<div>Hello Leo</div>')
patch(oldVNode, newVNode)
刷新浏览器,就能够看到 replaceTagModule 的每个钩子都被失常执行:
2. 实现 updateReplaceTag() 函数
咱们删除掉多余代码,接下来实现 updateReplaceTag()
函数,当 vnode 创立和更新时,都会调用该办法。
import {VNode, VNodeData} from '../vnode'
import {Module} from './module'
const regFunction = str => str && str.replace(/\<|\>|\//g, "");
const updateReplaceTag = (oldVnode: VNode, vnode: VNode): void => {const oldVnodeReplace = regFunction(oldVnode.text);
const vnodeReplace = regFunction(vnode.text);
if(oldVnodeReplace === vnodeReplace) return;
vnode.text = vnodeReplace;
}
export const replaceTagModule: Module = {
create: updateReplaceTag,
update: updateReplaceTag,
}
在 updateReplaceTag()
函数中,比拟新旧 vnode 的文本内容是否统一,如果统一则间接返回,否则将新的 vnode 的替换后的文本设置到 vnode 的 text 属性,实现更新。
其中有个细节:
vnode.text = vnodeReplace;
这里间接对 vnode.text
进行赋值,页面上的内容也随之发生变化。这是因为 vnode
是个响应式对象,通过调用其 setter
办法,会触发响应式更新,这样就实现页面内容更新。
于是咱们看到页面内容中的 HTML 标签被清空了。
3. 小结
这个大节中,咱们实现一个简略的 replaceTagModule
模块,体验了一下 Snabbdom 模块灵便组合的特点,当咱们须要自定义某些模块时,便能够依照 Snabbdom 的模块开发方式,开发自定义模块,而后通过 Snabbdom 的 init()
函数注入模块即可。
咱们再回顾一下 Snabbdom 模块零碎特点:反对按需引入、独立治理、职责繁多、不便组合复用、可维护性强。
五、通用模块生命周期模型
上面我将后面 Snabbdom 的模块零碎,形象为一个通用模块生命周期模型,其中蕴含三个核心层:
- 模块定义层
在本层能够依照模块开发标准,自定义各种模块。
- 模块应用层
个别是在业务开发层或组件层中,用来导入模块。
- 模块初始化层
个别是在开发的模块零碎的插件中,提供初始化函数(init 函数),执行初始化函数会遍历每个 Hooks,并执行对应处理函数列表的每个函数。
形象后的模型如下:
在应用 Module 的时候就能够灵便组合搭配应用啦,在模块初始化层,就会做好调用。
六、总结
本文次要以 Snabbdom-demo 仓库为学习示例,学习了 Snabbdom 运行流程和 Snabbdom 模块零碎的运行流程,还通过手写一个简略的 Snabbdom 模块,带大家领略一下 Snabbdom 模块的魅力,最初为大家总结了一个通用模块插件模型。
大家好好把握 Snabbdom 对了解 Vue 会很有帮忙。