h函数、vnode节点、createElement
一、vnode节点
vnode是js对象中的一个普通对象,这个对象描述了网页中的真实DOM。本次笔记只涵盖最基本的vnode对象,对象包含sel(DOM标签)
,data(DOM的某些属性,如key,href等),text(DOM的内部文本), children(?Array)
,elm(vnode指向的真实节点)。基于此,可以得到基本的vnode函数:
// vnode.js
export default function vnode(sel, data, children, text, elm) {
// 将key取出,方便后续使用
const key = data.key
return {
sel,
data,
children,
text,
elm,
key
}
}
二、h函数
h函数是一个函数,通过调用函数的方式生成vnode树。
常见的vnode调用:
h('h1', {}, '我是一个标题')
h('ul', {}, [
h('li', { key: 'A'}, '我是A'),
h('li', { key: 'B'}, '我是B'),
h('li', { key: 'C'}, '我是C'),
])
h('p', {}, h('span', {}, '我是文本'))
本次笔记只适用于以上三种h函数的调用,基于此得到基本的h函数
// h.js 每个h函数只需要考虑单层元素能否生成正确的vnode节点,子元素在调用时自己会重新调用h函数,如例子所示: h({ li, { key: 'A'}, '我是B'})
import vnode from "./vnode";
/**
*
* @param {*} sel
* @param {*} data
* @param {*} c
* h(sel, {}, text | Array | h())
*/
export default function h(sel, data, c) {
if (arguments.length !== 3) {
throw new Error('argument error')
}
// 检查参数c的类型
if(typeof c === 'string' || typeof c === 'number') {
// 说明是形态 h(sel, {}, text)
return vnode(sel, data, undefined, c, undefined)
} else if (Array.isArray(c)) {
// 说明是形态 h(sel, {}, Array<Vnode>)
// 循环遍历,确保每个子节点都是正确的格式
for(let i = 0; i < c.length; i++) {
if (!(typeof c[i] === 'object' && c[i].hasOwnProperty('sel'))) {
throw new Error('argument type error')
}
}
return vnode(sel, data, c, undefined, undefined)
} else if (typeof c === 'object' && c.hasOwnProperty('sel')) {
// 说明是形态 h(sel, {}, vnode) 传入的c是唯一的children
return vnode(sel, data, c, undefined, undefined)
} else {
throw new Error('argument type error')
}
}
三、createElement
createElement是一个函数,功能是将生成的虚拟节点转化为真实DOM, 并将虚拟节点的vnode的elm指向该生成的DOM。
// createElement.js-递归调用
export default function createElement(vnode) {
let domNode = document.createElement(vnode.sel)
if (vnode.text && (!vnode.children || vnode.children === 0)) {
// h(sel, {}, text)形式生成的vnode
// domNode的子节点是文本,直接将文本挂载到真实的DOM上
domNode.innerText = vnode.text
} else if (vnode.children && !Array.isArray(vnode.children)) {
// h(sel, {}, vnode)形式生成的vnode
// domNode的子节点是一个虚拟节点,需要调用createElement自身生成真实DOM,并将真实DOM挂载到domNode上
let chDOM = createElement(vnode.children)
domNode.appendChild(chDOM)
} else if (Array.isArray(vnode.children) && vnode.children.length > 0) {
// h(sel, {}, Array<vnode>)形式生成的vnode
// domNode的每个子节点都是虚拟节点,需要循环调用createElement自身生成真实DOM,并以此挂载到domNode上
for(let i = 0; i < vnode.children.length; i++) {
let chVnode = vnode.children[i]
let chDOM = createElement(chVnode)
domNode.appendChild(chDOM)
}
}
// 將vnode的elm指向真实DOM
vnode.elm = domNode
return vnode.elm
}