首先我们可以看到,在 createElement 函数调用的地方,我们的文本被当成字符串传入,然后这个参数是接收子节点的,并且在我们的逻辑之中我们使用了 appendChild,这个函数是接收 DOM 节点的。显然我们的 文本字符串不是一个节点,自然就会报错。
通过这种调试方式我们可以马上定位到,我们需要在哪里添加逻辑去实现我们这个功能。这种方式也可以算是一种捷径吧。
所以接下来我们就回到 main.js,在我们挂上子节点之前,判断一下 child 的类型,如果它的类型是 “String” 字符串的话,就使用 createTextNode() 来创建一个文本节点,然后再挂載到父元素上。这样我们就完成了字符节点的处理了。
function createElement(type, attributes, ...children) { // 创建元素 let element = document.createElement(type); // 挂上属性 for (let name in attributes) { element.setAttribute(name, attributes[name]); } // 挂上所有子元素 for (let child of children) { if (typeof child === 'string') child = document.createTextNode(child); element.appendChild(child); } // 最后我们的 element 就是一个节点 // 所以我们可以直接返回 return element; } let a = <div>hello world</div>; document.body.appendChild(a);
我们用这个最新的代码 webpack 打包之后,就可以在浏览器上看到我们的文字被显示出来了。
到了这里我们编写的 createElement 已经是一个比较有用的东西了,我们已经可以用它来做一定的 DOM 操作了。甚至它可以完全代替我们自己去写 document.createElement 的这种反复繁琐的操作了。
这里我们可以验证一下,我们在 div 当中重新加上我们之前的三个 span, 并且在每个 span 中加入文本。11
let a = ( <div> hello world: <span>a</span> <span>b</span> <span>c</span> </div> );
然后我们重新 webpack 打包后,就可以看到确实是可以完整这种 DOM 的操作的。
现在的代码已经可以完成一定的组件化的基础能力。
实现自定义标签之前我们都是在用一些,HTML 自带的标签。如果我们现在把 div 中的 d 改为大写 D 会怎么样呢?
let a = ( <Div> hello world: <span>a</span> <span>b</span> <span>c</span> </Div> );
果不其然,就是会报错的。不过这就是我们找到问题的根源的关键,这里我们发现当我们把 div 改为 Div 的时候,传入我们 createElement 的 div 从字符串 ‘div' 变成了一个 Div 类。
当然我们的 JavaScript 中并没有定义 Div 类,这里自然就会报 Div 未定义的错误。知道问题的所在,我们就可以去解决它,首先我们需要先解决未定义的问题,所以我们先建立一个 Div 的类。
// 在 createElment 函数之后加入 class Div {}
然后我们就需要在 createElement 里面做类型判断,如果我们遇到的 type 是字符类型,就按原来的方式处理。如果我们遇到是其他情况,我们就实例化传过来的 type。
function createElement(type, attributes, ...children) { // 创建元素 let element; if (typeof type === 'string') { element = document.createElement(type); } else { element = new type(); } // 挂上属性 for (let name in attributes) { element.setAttribute(name, attributes[name]); } // 挂上所有子元素 for (let child of children) { if (typeof child === 'string') child = document.createTextNode(child); element.appendChild(child); } // 最后我们的 element 就是一个节点 // 所以我们可以直接返回 return element; }
这里我们还有一个问题,我们有什么办法可以让自定义标签像我们普通 HTML 标签一样操作呢?在最新版的 DOM 标准里面是有办法的,我们只需要去注册一下我们自定义标签的名称和类型。
但是我们现行比较安全的浏览版本里面,还是不太建议这样去做的。所以在使用我们的自定义 element 的时候,还是建议我们自己去写一个接口。
首先我们是需要建立标签类,这个类能让任何标签像我们之前普通 HTML 标签的元素一样最后挂載到我们的 DOM 树上。
它会包含以下方法:mountTo() —— 创建一个元素节点,用于后面挂載到 parent 父级节点上
setAttribute() —— 给元素挂上所有它的属性
appendChild() —— 给元素挂上所有它的子元素