乐趣区

关于javascript:探索-Snabbdom-模块系统原理

近几年随着 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 库。其中有几个外围个性:

  1. 外围代码 200 行,并且提供丰盛的测试用例;
  2. 领有弱小模块零碎,并且反对模块拓展和灵便组合;
  3. 在每个 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

模块中能够应用这些钩子:precreateupdatedestroyremovepost
单个元素能够应用这些钩子:initcreateinsertprepatchupdatepostpatchdestroyremove

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 的模块零碎,形象为一个通用模块生命周期模型,其中蕴含三个核心层:

  1. 模块定义层

在本层能够依照模块开发标准,自定义各种模块。

  1. 模块应用层

个别是在业务开发层或组件层中,用来导入模块。

  1. 模块初始化层

个别是在开发的模块零碎的插件中,提供初始化函数(init 函数),执行初始化函数会遍历每个 Hooks,并执行对应处理函数列表的每个函数。

形象后的模型如下:

在应用 Module 的时候就能够灵便组合搭配应用啦,在模块初始化层,就会做好调用。

六、总结

本文次要以 Snabbdom-demo 仓库为学习示例,学习了 Snabbdom 运行流程和 Snabbdom 模块零碎的运行流程,还通过手写一个简略的 Snabbdom 模块,带大家领略一下 Snabbdom 模块的魅力,最初为大家总结了一个通用模块插件模型。

大家好好把握 Snabbdom 对了解 Vue 会很有帮忙。

退出移动版