🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
>[success] # 分析snabbdom 源码h函数 ~~~ 1.先从分析 h 函数,如何创建虚拟节点 ~~~ >[info] ## 分析这段代码 ~~~ 1.h创建虚拟节点(vnodes)。h函数第一个参数'字符串形式的标签/选择器'、 第二个参数'一个可选的数据对象'、第三个参数'一个可选的字符串或数组作为子代' 2.先看一下,h函数在使用的时候传参的几种使用方法: 1.1. 一个参数h('div#container.cls') 创建一个div标签 id是container,class是cls/也可以直接是一个选择器 1.2.第二个参数类型可以使用,'VNodeData','VNodeChildren'这两种类型对象, 这里可以这么理解'VNodeData'是给你设置的标签属于一些属性,例如点击事件,样式等,'VNodeChildren' 是设置标签内部一些子元素(VNodeChildren 的类型可以是VNode | string | number | undefined | null 或这些类型数组) 1.3.三个参数的时候就是可以创建一个完整的这种形式: "<div id='container' class='cls' style='color:red'>我是例子<span>例子<span></div>" 2.看一个完整的版的 传参例子(这个例子中第二个参数类型就是VNodeData,第三个参数类型VNodeChildren) let vnode = h('div', { style: { backgroundColor: 'red' }, on: { click: eventHandler } }, [ '我是文本' h('h1', 'Hello Snabbdom'), h('p', '这是p标签') ]) ~~~ >[danger] ##### 分析代码 -- src/h.ts ~~~js 1.首先明确'JavaScript 中没有重载的概念','TypeScript 中有重载,不过重载的实现还是通过代码调整参数', 可以理解为'通过规定了不同参数个数的时,参数的类型就是重载',Snabbdom是ts 编写的 2.参数重载这里的分析: 2.1.一个参数的时候是一个字符串 2.2.第二个参数可以能是'VNodeData'类型,或者是'VNodeChildren' 类型 2.3.三个参数,依次分别是'字符串','VNodeData' ,'VNodeChildren' 类型 3.VNodeData类型定义如下: 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?: Array<any>; // for thunks [key: string]: any; // for any other 3rd party module } 4.VNodeChildren类型定义: export type VNodeChildElement = VNode | string | number | undefined | null; export type ArrayOrElement<T> = T | T[]; export type VNodeChildren = ArrayOrElement<VNodeChildElement> 4.1.这里简单对'VNodeChildElement'分析他可以是'VNode | string | number | undefined | null' 这些类型或这类型数组,这里说明一下'VNode'类型是h函数的返回类型,因此可以说就是 另外一个h函数 ~~~ * snabbdom 用过ts 重载可以限制不同参数个数时候对应参数类型 ~~~ export function h(sel: string): VNode; export function h(sel: string, data: VNodeData): VNode; export function h(sel: string, children: VNodeChildren): VNode; export function h(sel: string, data: VNodeData, children: VNodeChildren): VNode; export function h(sel: any, b?: any, c?: any): VNode {.....} ~~~ >[danger] ##### VNode -- src\package\vnode.ts ~~~ 1.'h()' 函数返回虚拟dom,在'src\package\vnode.ts' snabbdom对虚拟dom定义对象属性分别有 1.1.'sel '选择器 1.2.'data' 节点数据:属性/样式/事件等 类型VNodeData|undefined 1.3.'children'子节点类型 Array < VNode | string > | undefined 1.4.'elm'记录 vnode 对应的真实 DOM 1.5.'text'节点中的内容,和 children 只能互斥 1.6.'key' 做优化 2.在来看'h()'函数传入的参数'sel: string, data: VNodeData, children: VNodeChildren',可以理解成'h()'函数 将对应传入参数,进行判断重新分配到'vnode' 对应对象位置上 ~~~ ~~~js export interface VNode { // 选择器 sel: string | undefined; // 节点数据:属性/样式/事件等 data: VNodeData | undefined; // 子节点,和 text 只能互斥 children: Array < VNode | string > | undefined; // 记录 vnode 对应的真实 DOM elm: Node | undefined; // 节点中的内容,和 children 只能互斥 text: string | undefined; // 优化用 key: Key | undefined; } export function vnode(sel: string | undefined, data: any | undefined, children: Array < VNode | string > | undefined, text: string | undefined, elm: Element | Text | undefined): VNode { let key = data === undefined ? undefined : data.key; return { sel, data, children, text, elm, key }; } export default vnode; ~~~ >[danger] ##### src/h.ts 源码 ~~~ 1.'h()'函数最终返回的是'vnode'对象,因为'h()' 函数传入的参数不是固定的,可以理解'h()' 函数主要 做的就是将使用时候不固定的参数进行分配,匹配其要对应的'vnode对象key上' 2.因此下面'snabdom 中 h()函数'的代码主要就是针对参数个数不同做了同的判断处理和赋值 3.这里对处理 children 中的原始值(string/number)这里做个说明: [ '我是文本' h('h1', 'Hello Snabbdom'), h('p', '这是p标签') ] 如果children 数组中有string 或者number 由于他们不是 h函数,因此想把这类东西后续转换成 虚拟dom就需要先包装成h函数 4.分析一个案例 h('div', { style: { backgroundColor: 'red' }, on: { click: eventHandler } }, [ h('h1', h('p', '这是p标签')), h('p', '这是p标签') ]) 注意这里h('h1', h('p', '这是p标签')),嵌套形式的情况下,下面代码也没有递归是,是如果铺平运行? 注意h 是一个函数现在嵌套的" h('p', '这是p标签')" 是一个参数,此时代码会先执行这个作为参数执行函数 返回'Vnode' 对象,代码变成了h('h1',Vnode) 上面分析过第二个参数可以是'Vnode' ~~~ * [这一段带注释的代码来源](https://github.com/lagoufed/vuejs-enhancement) ~~~ export function h(sel: any, b ? : any, c ? : any): VNode { var data: VNodeData = {}, children: any, text: any, i: number; // 处理参数,实现重载的机制 if (c !== undefined) { // 处理三个参数的情况 // sel、data、children/text if (b !== null) { data = b; } if (is.array(c)) { children = c; } // 如果 c 是字符串或者数字 else if (is.primitive(c)) { text = c; } // 如果 c 是 VNode else if (c && c.sel) { children = [c]; } } else if (b !== undefined && b !== null) { // 处理两个参数的情况 // 如果 b 是数组 if (is.array(b)) { children = b; } // 如果 b 是字符串或者数字 else if (is.primitive(b)) { text = b; } // 如果 b 是 VNode else if (b && b.sel) { children = [b]; } else { data = b; } } if (children !== undefined) { // 处理 children 中的原始值(string/number) for (i = 0; i < children.length; ++i) { // 如果 child 是 string/number,创建文本节点 // 因为你的文本节点是'我是文本'类似这种在数组中不是一个h函数的 // 表现形式因此需要被转换成一个h函数的表现形式 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] === '#') ) { // 如果是 svg,添加命名空间 addNS(data, children, sel); } // 返回 VNode return vnode(sel, data, children, text, undefined); }; // 导出模块 export default h; ~~~