赞
踩
最近在读snabbdom源码,在学习途中发现下载的GitHub源码结构和别人博客上的有一些差别,查看提交记录发现最近几周 snabbdom 做了一点点变动。我们到npm网站上也可以看到,18天前版本从0.7.4升级到了1.0.1。
通过以下链接看到snabbdom的npm相关信息:npm地址
所以如果想跑一下目前博客上的Demo就只能指定snabbdom包的版本了
npm i snabbdom@0.7.4
简单比对了下,比较明显的就是:
modules、helpers文件夹合并到了package中
snabbdom.ts改为=>init.ts
结构简单,就三个文件夹:
npm run compile
命令打包后即可运行
其实在Vue实例中已经见过h()了,它的作用就是创建一个虚拟节点VNode。
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
在h.ts文件中,h函数通过函数重载的方式定义,再通过if语句判断变量是否是undefined,来处理三个参数、两个参数时的情况。
// h 函数的重载 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 { var data: VNodeData = {} var children: any var text: any var i: number if (c !== undefined) { if (b !== null) { data = b } if (is.array(c)) { children = c } else if (is.primitive(c)) { text = c } else if (c && c.sel) { children = [c] } } else if (b !== undefined && b !== null) { if (is.array(b)) { children = b } else if (is.primitive(b)) { text = b } else if (b && b.sel) { children = [b] } else { data = b } } if (children !== undefined) { for (i = 0; i < children.length; ++i) { if (is.primitive(children[i])) children[i] = vnode(undefined, undefined, undefined, children[i], undefined) } } if ( sel[0] === 's' && sel[1] === 'v' && sel[2] === 'g' && (sel.length === 3 || sel[3] === '.' || sel[3] === '#') ) { addNS(data, children, sel) } return vnode(sel, data, children, text, undefined) };
VNode 就是用一个虚拟节点用来描述一个 DOM 元素,如果这个 VNode 有 children 就是 Virtual DOM。
结构比较简单,定义了VNode的接口和函数实现,该函数参数分别表示的是:
import { Hooks } from './hooks' import { AttachData } from './helpers/attachto' import { VNodeStyle } from './modules/style' import { On } from './modules/eventlisteners' import { Attrs } from './modules/attributes' import { Classes } from './modules/class' import { Props } from './modules/props' import { Dataset } from './modules/dataset' import { Hero } from './modules/hero' export type Key = string | number export interface VNode { sel: string | undefined data: VNodeData | undefined children: Array<VNode | string> | undefined elm: Node | undefined text: string | undefined key: Key | undefined } 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 } export function vnode (sel: string | undefined, data: any | undefined, children: Array<VNode | string> | undefined, text: string | undefined, elm: Element | Text | undefined): VNode { const key = data === undefined ? undefined : data.key return { sel, data, children, text, elm, key } }
核心代码,调用init函数时会返回一个patch函数,用于比较两个vnode差异并更新,这也是虚拟DOM的原理。
大致结构如下:
从48行到开始到最后都是init的实现,首先init传入两个参数:
代码实现上,先初始化转换虚拟节点的domapi
const api: DOMAPI = domApi !== undefined ? domApi : htmlDomApi
再把传入的所有modules的钩子函数,通过遍历全部存储到一个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)
}
}
}
接下来就是在函数内部定义了很多的辅助函数,它们主要是用到patch函数内部,也就是init返回值,它会在合适的时机调用:
最后就是返回的patch函数了,参数传入新旧节点,比较后进行更新。首先定义的insertedVnodeQueue是一个队列,保存新插入的节点,它是为了触发节点的钩子函数。之后for循环先执行了cbs对象中pre的钩子函数,再判断oldVnode是真实DOM还是VNode,如果是真实DOM则转为空的VNode。转化后,判断新旧节点是不是相同的节点,是则比较新旧节点差异并更新到DOM中,不是则需要把新节点渲染成DOM,再触发init/create的钩子函数。最后两个for循环分别执行用户的insert和模块的post钩子函数,执行完后把新节点返回,作为更新后的旧节点,以便于下次更新比较。
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]() if (!isVnode(oldVnode)) { oldVnode = emptyNodeAt(oldVnode) } if (sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode, insertedVnodeQueue) } else { elm = oldVnode.elm! parent = api.parentNode(elm) as Node createElm(vnode, insertedVnodeQueue) if (parent !== null) { api.insertBefore(parent, vnode.elm!, api.nextSibling(elm)) removeVnodes(parent, [oldVnode], 0, 0) } } for (i = 0; i < insertedVnodeQueue.length; ++i) { insertedVnodeQueue[i].data!.hook!.insert!(insertedVnodeQueue[i]) } for (i = 0; i < cbs.post.length; ++i) cbs.post[i]() return vnode } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。