本文的意义在于教会大家如何手写一个h函数
上文中 我们简单理解了一下h函数 他的作用是构建一个虚拟的dom节点
掌握这个函数还是很有必要的 首先 你想要写出来 还是得去看原版的ts代码
这边 我们没必要把太多注意力放在TS上 所以 我们这边是看ts代码 然后 仿写js代码
我们在 案例的 node_modules 下找到 snabbdom/src/h.ts 打开这个文件
读h函数 你会发现 他最后 是返回的用了一个 vnode函数
好家伙 在这套娃呢
我们打开同目录下的vnode.ts
这个 vnode函数 看着就相对简单一些了
简单说 就是接了一些列参数 并给这些参数限制了类型
在ts中 sel: string | undefined 的意思就是
接受一个 参数 代理名定位以为 sel :后的内容代表 可以是string字符串类型 或者 也可以是 undefined 未定义
处理外这些参数之后 就把他们转成一个json并返回
所以 h函数最后返回vnode 就是返回一个vnode函数处理好的虚拟节点对象
对象中比较麻烦的 是这个 children 因为他存在嵌套的可能 就是 一个虚拟节点里面可能又套了一个虚拟节点 被套的虚拟节点 下面 可能还有n层这样嵌套的虚拟节点
所以 我们看h函数上面这个位置
会循环处理children 一层一层 都创建出 vonde节点
这里 h函数是不涉及递归的 就是用循环 一层一层往下调
这里 我们看到上面有很多的if h函数这个位置 就是涉及到 h函数重载
重载的概念很多后端语言都有 ts也将这种概念带入了前端开发中
就是定义很多一样名字的函数 接收的参数 类型 数量 不同 调用不同的逻辑函数
如果您不了解 可以读一下我的文章
TypeScript方法重载
上文我们演示过
知道了 h函数 有非常多的调法
好 那么 我们就来试着写一个自己的h函数
我们看到我们一直在写的这个按理 在根目录下的src下创建一个文件夹 叫 snabbdom
然后在我们刚创建的 snabbdom 下创建一个 h.js
然后 我们刚才也看了 h函数返回 需要一个vnode函数来处理一下格式
所以 在snabbdom下再创建一个 vnode.js
参考代码如下
export default function(sel,data,children,text,elm) {let key = data.key?data.key:undefined;return {sel,data,key,children,text,elm}
}
逻辑非常简单
就是 导出了一个函数 这个函数接收五个参数
然后 先定义一个局部变量 叫 key 他的赋值 用了一个三元运算符
判断 如果 data中有一个key字段 那么 就用 data里的key给key赋值
如果没有 则 key等于key:undefined
然后返回一个对象 就是 字段名 等于字段对应的值 学过ES6的朋友会理解
{key
}
和
{key: key
}
是一个意思
然后 我们来写刚刚创建的 h.js 内的代码
然后 首先要说明 我们这边h函数 不处理方法的重载 因为 重载这个 理解了概念 谁都可以写出来 很麻烦 而且他我觉得并不算h函数中很美观的东西
所以 我们这样写的函数 就必须要传三个参数
然后 我们 h.js编写代码如下
//引入我们刚刚写的vnode
import vnode from "./vnode";
//默认导出一个函数
export default function(a,b,c) {//确认调用时是否传了三个参数if(arguments.length != 3)//如果参数不是三个 抛出异常警告throw new Errow("对不起,本h函数必须传三个参数 我们是低配版");// 判断 如果传入的第三个参数 是 string或者number 表示 形态为 第一种 h(标签。属性,文本)if(typeof c == "string"||typeof c == 'number') {return vnode(a,b,undefined,c,undefined);//如果传的是一个数组 那么 是调的第二种形式 h(标签。属性,数组)}else if(Array.isArray(c)) {//定义一个children数组 来存一下当前这个节点的子集let children = [];//循环c这个数组for(let i = 0;i < c.length;i++) {//判断当前下标是不是一个对象 因为h函数一定会返回一个对象if(c[i] instanceof Object) {children.push(c[i]);}else{//如果不是 抛出异常提示throw new Errow(`第三个参数的第${(i+1)}项不是一个h函数`);}}//循环结束 表示children收集子集也结束了return vnode(a,b,children,undefined,undefined);//判断c是否是一个对象 如果是 表示调用了第三种 h(标签。属性,h对象)}else if(c instanceof Object) {//第三种 说明 他是唯一的子集 我们直接将他的子集放入children就好了return vnode(a,b,[c],undefined,undefined);//如果以上格式 全部都不符合 排除错误}else{throw new Errow("对不起,传入的第三个参数格式错误 请认真加粗");}
}
因为这个东西比较不太好去叙述 所以 我就比较努力的写注释 希望大家能通过注释来理解了
这个不叫递归 是相互嵌套 h函数的思维真的很巧妙 没有很复杂的代码 但设计思维 却人感觉很有趣 也很巧妙
然后 我们来测试三种情况
我们将 src下的 index.js 代码改写如下
import h from "./snabbdom/h";const dom1 = h("div",{props: {class: "dom"}
},"文本测试");const dom2 = h("div",{props: {class: "dom"}
},[h("div",{},"java"),h("div",{},"html"),h("div",{},[h("div",{},"react"),h("div",{},"css")])
]);const dom3 = h("div",{props: {class: "dom"}
},h("div",{},"java"));console.log(dom1);
console.log(dom2);
console.log(dom3);
这里 我们传了 第三个的各种情况 文本 数组 直接一个h函数
然后 我们运行项目
这三种 我们都打开看一下
第一种
第二种
第三种