前言
上节我们解析了用h函数创建vnode的过程,相当于凭空创建vnode,如果是真实dom想转换成vnode又该如何?本节就来学习一下。
tovnode
tovnode函数可以将一个DOM节点,转换为虚拟DOM,有了这个函数我们就能将页面上存在的dom变成虚拟dom进行渲染。
<div id="app">
<span>真实dom</span>
</div>
const vnode = toVNode(document.querySelector("#app"))
在snabbdom源码文件中有专门的tovnode文件,打开此文件就能找到toVNode的源码,该函数源码过长我们先看前半部分的源码:
- toVNode有两个参数,node表示真实dom;domApi表示操作的API默认为snabbdom自己封装的htmlDomApi。
- 利用isElement判断node是否为节点元素,满足条件就开始解析node参数,将node里的属性解析出来用于后续生成vnode,cn表示类名利用getAttribute直接获取,sel标签名则是通过tagName方法。
function isElement(node: Node): node is Element {
return node.nodeType === 1;
}
function tagName(elm: Element): string {
return elm.tagName;
}
- 获取node的属性列表attributes跟子元素childNodes,然后进行循环处理。首先是attributes,如果是data-开头就放到dataset里面,否则就放到attrs属性数组里面。dataset里面的key是由datasetkey生成:
// 取属性名字下标5开始取值并将字母转为大写
function datasetKey(attributeName: string) {
return attributeName
.slice(5)
.replace(/-([a-z])/g, (_, ch) => ch.toUpperCase());
}
接着循环childNodes递归执行toVNode函数将其放到children数组中。前半部分就是数据的收集,通过各种方法获取到转换vnode的数据,下面我们来看后半部分代码:
- 通过Object.keys获取attrs与dataset,将其赋值给data。
- 如果sel是svg就用addNS将data进行处理,最后用vnode函数得到虚拟dom。
- 注意此时else if是跟上面isElement判断匹配的。通过isText判断node是否为文本类型,如果为文本就用getTextContent获取文本内容,当作参数调用vnode。
// 通过nodeType进行判断
function isText(node: Node): node is Text {
return node.nodeType === 3;
}
function getTextContent(node: Node): string | null {
return node.textContent;
}
- 用isComment判断是否为注释节点,满足条件获取text调用vnode,注意此时vnode的第一个参数为!。
function isComment(node: Node): node is Comment {
return node.nodeType === 8;
}
- 最后只传递node参数执行vnode函数。
toVNode整体思路就是通过nodeType判断节点类型,根据不同类型传递不同参数调用vnode。元素节点处理起来相对复杂不过代码写的很清晰,掌握了此思路我们也能写个简化虚拟dom方法。
createElm
tovnode是将真实dom转换为虚拟dom,在渲染过程中虚拟dom会变成真实dom,在此过程中调用的是createElm方法,两者作用相反的函数,放在一起更好理解,正所谓趁热打铁我们来看下createElm方法:
- 两个参数vnode虚拟dom;insertedVnodeQueue节点队列。
- 定义变量获取数据,经过toVNode的讲解sel、data等属性我们都不陌生,并且sel的判断与toVNode逻辑相呼应。
- 结合着toVNode函数,sel几种情况就很明朗了,其中!== undefined表示元素节点。
- sel在toVNode中sel是拼接到一起的,所以这里通过indexOf与slice获取到tag、id、class等属性。
- tag标签元素,利用createElement创建,如果有ns就用createElementNS创建;id、class通过setAttribute添加,其中class存在多个用replace替换一下。
- 执行create周期函数,判断是否为文本节点类型,比如h1标签,通过createTextNode创建添加。
- 存在children是递归执行添加,如果存在hook就执行hook函数。
- 判断fragments进行添加,最后用createTextNode添加文本。
createElm利用domApi创建真实dom并添加,其中hooks等功能增加了其复杂性。
总结
代码上看createElm要比toVNode复杂,两者结合理解会变简单。从h函数到toVNode再到createElm,理解了这三个函数,也就掌握了虚拟dom大致思路了。