文章参考了霍春阳的《Vue.js设计与实现》,是自己在阅读过程中的一些思考和理解
文章中所用的ref函数,为介绍响应式文章中所写的响应式代码,并非官方所提供的库。推荐想了解响应式原理读者可以去看看 : )
挂载子节点和元素的属性
vnode的children属性,不一定是字符串,也可以包含属于它的子节点。因此我们需要将children属性设置为数组。同时需要修改挂载函数。代码如下:
const node = {
type: 'div',
children: [
{
type: 'h1',
children: '起飞'
}
]
}
// 挂载函数
function mountElement(vnode, container) {
// 建立传入节点的类型
const el = createElement(vnode.type)
// 如果children是字符串,则说明是文本类
if(typeof vnode.children === 'string') {
// 直接将其放入标签中
setElementText(el, vnode.children)
}
// 如果children是数组,则遍历这个数组,执行patch操作
else if(Array.isArray(vnode.children)) {
vnode.children.forEach(child => {
// 由于是挂载操作,因此oldNode设为null
patch(null, child, el)
})
}
// 容器中添加节点
insert(el, container)
}
接下来,我们通过vnode去描述一个标签属性,以及如何渲染这些属性。代码如下:
const vnode = {
type: 'div',
// 新增props属性
props: {
id: 'mean',
},
children: [{
type: 'h2',
children: 'wwww'
}
]
}
// mount函数新增
// 如果vnode上存在props属性,则在节点上配置对应属性
// 这里只做最简单的操作,实际上的属性设置更复杂
if (vnode.props) {
for(const key in vnode.props) {
el.setAttribute(key, vnode.props[key])
}
}
HTML Attributes与DOM Properties
HTML Attributes设置的是DOM Properties的初始值,通过HTML的方法获取的值是最初的值,通过DOM节点获取的值是最新的值
正确的设置属性元素
在HTML标签中写的属性,浏览器会调用节点的setAttribute函数,并将value设置在key上。代码如下:
<button disabled>check</button>
这段代码会被解析为:
vnode = {
type: 'botton',
props: {
disabled: ''
}
}
在渲染器中,调用setAttribute操作时,会执行el.setAttribute(disabled,‘’),确实可以其设置为禁用状态。但是在Vue文件中,有v-bond指令,代码如下:
<button :disabled='false'>check</button>
在解析这个节点时,disabled的值变为了false,但是setAttribute函数在执行时,会将传入的value值总是转为字符串。因此false会被转为’false’,一个非空的字符串代表true,因此在渲染器执行后,按钮还是被设置为了禁用。这就与用户的想法背道而驰了。而当我们尝试用DOM节点来设置时,会发现结果与用于的想法也是相反的。这就表示我们需要对这类属性进行特殊处理。代码如下:
// 如果要设置的属性是布尔类,且传入的为空,则将其设置为true,否则还是照常设置
if(key in el) {
if (typeof el[key] === 'boolean' && nextValue === '') {
el[key] = true
} else {
el[key] = nextValue
}
}
对每个存在于节点中的属性,都通过DOM Properties的方法来设置。而对布尔类型的空字符串进行特殊的设置,设置为true。但是也有一些DOM属性值,是没有实际效果的,只是作为标志区分不同的同类型标签,这类属性同时也存在于HTML的属性中,这是就不需要走特殊的处理逻辑,而直接调用setAttribute函数即可。例如表单中的form属性。因此就需要一个判断函数来区分。代码如下:
function shouldSetAttr(el, key, value) {
// 特殊处理的例子
if(key === 'from' && el.tagName === 'INPUT') return false
return key in el
}
这里只写了一个input作为例子,实际上还有很多。最后,由于属性的设置在不同平台也有不同的参数,为了兼容,将上述方法抽离作为渲染器的配置函数传入渲染器。代码如下:
// 属性值设置,提取到渲染器配置中,同样是为了兼容不同平台
patchProps(el, key, preValue, nextValue) {
// 走特殊处理,如果要设置的属性不在el中,则直接调用setAttribute操作
if (shouldSetAttr(el, key, nextValue)) {
// 如果要设置的属性是布尔类,且传入的为空,则将其设置为true,否则还是照常设置
if (typeof el[key] === 'boolean' && nextValue === '') {
el[key] = true
} else {
el[key] = nextValue
}
} else {
el.setAttribute(key, nextValue)
}
}
在vnode.props参数中对每个key-value都执行这个函数即可达到目的。
class的处理
在Vue中,对class的设置涉及到变量,而变量又涉及到不同的结构,例如对象、数组等等。在vnode中class作为props中的一个参数传递给渲染器,因此首先需要一个结构转换函数,将不同结构的class转换为字符串,接着要将其设置在标签属性上,这里涉及到属性设置的三个方法,通过el.className赋值、通过setAttribute函数和通过el.classList赋值。通过对比发现,el.className方法的效率最好,因此我们需要修改patchProps函数,将props中的class属性采用el.className方式设置。代码如下:
patchProps(el, key, preValue, nextValue) {
// 走特殊处理,如果要设置的属性不在el中,则直接调用setAttribute操作
if (shouldSetAttr(el, key, nextValue)) {
// 对class属性采用节点属性设置的高效率方式设置
if (key === 'class') {
el.className = nextValue || ''
}
// 如果要设置的属性是布尔类,且传入的为空,则将其设置为true,否则还是照常设置
else if (typeof el[key] === 'boolean' && nextValue === '') {
el[key] = true
除了class外,对style的设置也是类似的方法进行处理。可以发现,vnode.props中的属性值类型不总是与DOM元素的数据结构保持一致,这取决于上层的API。
暂时停更,有缘再更新啦