snabbdom (二)
大约 3 分钟
vnode, h, hook 定义
vnode.ts
export type Key = string | number;
export interface VNode {
// 选择器
sel: string | undefined;
// 节点的样式/ 事件/ 属性等
data: VNodeData | undefined;
// 子节点 可以是一个vnode数组 或者字符串数组
children: Array<VNode | string> | undefined;
// 记录的真实dom
elm: Node | undefined;
// 文本节点 有children就不能有text
text: string | undefined;
// 优化用
key: Key | undefined;
}
export interface VNodeData {
props?: Props;
attrs?: Attrs; // 属性
class?: Classes; // 选择器
style?: VNodeStyle; // 样式
dataset?: Dataset; // 自定义数据dataset
on?: On; // 监听方法
hero?: Hero;
attachData?: AttachData;
hook?: Hooks; // 钩子函数
key?: Key; // 用于后面 patch
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 };
}
h.ts
export type VNodes = VNode[]; // 一个虚拟DOM数组
export type VNodeChildElement = VNode | string | number | undefined | null; // 虚拟DOM的children里面的元素 可以是什么类型
export type ArrayOrElement<T> = T | T[];
export type VNodeChildren = ArrayOrElement<VNodeChildElement>; // 虚拟DOM的children
// 添加命名空间 svg
function addNS(
data: any,
children: VNodes | undefined,
sel: string | undefined,
): void {
data.ns = 'http://www.w3.org/2000/svg';
if (sel !== 'foreignObject' && children !== undefined) {
for (let i = 0; i < children.length; ++i) {
const childData = children[i].data;
if (childData !== undefined) {
addNS(
childData,
(children[i] as VNode).children as VNodes,
children[i].sel,
);
}
}
}
}
// h方法的重载
export function h(sel: string): VNode; // 只传入一个tag#ID1.class1 之类的字符串
export function h(sel: string, data: VNodeData | null): VNode; // 第二参数是传入的类似这样的一个对象 用来设置tag元素的样式 事件 属性等
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) {
// 如果第二个参数不null
if (b !== null) {
data = b;
}
// 如果c是数组
if (is.array(c)) {
children = c; // 说明是子元素 放入到children属性中
} else if (is.primitive(c)) {
// 说明是字符串文本
text = c; // 放到text属性
} else if (c && c.sel) {
// 如果c是vnode
children = [c]; // 把c弄成数组 赋值给属性children
}
} else if (b !== undefined && b !== null) {
// 只有两个参数
if (is.array(b)) {
// 判断是否是数组
children = b; // 传入children
} else if (is.primitive(b)) {
// 字符串或者number 传入文本
text = b;
} else if (b && b.sel) {
// 是否是vnode
children = [b];
} else {
data = b;
} // 说明是class , 什么的
}
if (children !== undefined) {
// 如果children属性存在 说明有子节点 再创建子节点
for (i = 0; i < children.length; ++i) {
if (is.primitive(children[i]))
children[i] = vnode(
undefined,
undefined,
undefined,
children[i],
undefined,
);
}
}
// 如果传入的svg
if (
sel[0] === 's' &&
sel[1] === 'v' &&
sel[2] === 'g' &&
(sel.length === 3 || sel[3] === '.' || sel[3] === '#')
) {
// 添加命名空间
addNS(data, children, sel);
}
// 返回vnode
return vnode(sel, data, children, text, undefined);
}
hooks.ts
import { VNode } from './vnode';
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;
}
该文件是用来定义 modules 里面模块的钩子方法, 举个例子 props.ts 主要的代码后面再讲 大体如下
import { Module } from './module';
function updateProps(oldVnode: VNode, vnode: VNode): void {}
export const propsModule: Module = { create: updateProps, update: updateProps };
我们可以看出 props.ts 导出的是一个 propsModule 它里面有着 Hooks 里面的两个属性 一个是 create, 一个是 update, 相应的实现则是在 props.ts 里面实现的