vue.js 3设计与实现 -- 挂载与更新(二)

四、class 的处理

​ 在上一节中,我们讲解了如何正确地把 vnode.props 中定义的属性设置到 DOM 元素上。但在 Vue.js 中,仍然有一些属性需要特殊处理,比如 class 属性。为什么需要对 class 属性进行特殊处理呢?这是因为 Vue.js 对 calss 属性做了增强。

​ 在 Vue.js 中为元素设置类名有以下几种方式。 方式一:指定 class 为一个字符串值。

<p class="foo bar"></p>

​ 这段模板对应的 vnode 是:

const vnode = {
    
    type: 'p', 
    props: {
    
    	class: 'foo bar' 
    } 
}

​ 方式二:指定 class 为一个对象值。

<p :class="cls"></p>

​ 假设对象 cls 的内容如下:

const cls = {
    foo: true, bar: false } 

​ 那么,这段模板对应的 vnode 是:

const vnode = {
    
    type: 'p', 
    props: {
    
    	class: {
    foo: true, bar: false } 
    } 
} 

​ 方式三:class 是包含上述两种类型的数组。

<p :class="arr"></p>

​ 这个数组可以是字符串值与对象值的组合:

const arr = [ 
    // 字符串
    'foo bar', 
    // 对象
    {
    
    	baz: true
    } 
] 

​ 那么,这段模板对应的 vnode 是:

const vnode = {
    
    type: 'p', 
    props: {
    
        class: [ 
            'foo bar', 
            {
    baz: true } 
        ] 
    } 
} 

​ 可以看到,因为 class 的值可以是多种类型,所以我们必须在设置元素的 class 之前将值归 一化为统一的字符串形式,再把该字符串作为元素的 class 值去设置。因此,我们需要封装 normalizeClass 函数,用它来将不同类型的 class 值正常化为字符串,例如:

const vnode = {
    
    type: 'p', 
    props: {
    
    	// 使用 normalizeClass 函数对值进行序列化
    	class: normalizeClass([ 
    		'foo bar', 
    		{
    baz: true } 
    	]) 
    } 
}

​ 最后的结果等价于:

const vnode = {
    
    type: 'p', 
    props: {
    
    	// 序列化后的结果
    	class: 'foo bar baz' 
    } 
} 

​ 至于 normalizeClass 函数的实现,这里我们不会做详细讲解,因为它本质上就是一个数据结构转换的小算法,实现起来并不复杂。

​ 假设现在我们已经能够对 class 值进行正常化了。接下来,我们将讨论如何将正常化后的 class 值设置到元素上。其实,我们目前实现的渲染器已经能够完成 class 的渲染了。观察前文中函数的代码,由于 class 属性对应的 DOM Properties 是 el.className,所以表达式 ‘class’ in el 的值将会是 false,因此,patchProps 函数会使用 setAttribute 函数来完成 class 的设置。但是我们知道,在浏览器中为一个元素设置 class 有三种方式,即使用 setAttribute、el.className 或 el.classList。那么哪一种方法的性能更好呢?图 4-1 对比了这三种方式为元素设置 1000 次 class 的性能。

在这里插入图片描述

4-1 el.className、setAttribute 和 el.classList 的性能比较

​ 可以看到,el.className 的性能最优。因此,我们需要调整 patchProps 函数的实现,如下面的代码所示:

const renderer = createRenderer({
    
    // 省略其他选项

    patchProps(el, key, prevValue, nextValue) {
    
    	// 对 class 进行特殊处理
    	if (key === 'class') {
    
    		el.className = nextValue || '' 
    	} else if (shouldSetAsProps(el, key, nextValue)) {
    
    		const type = typeof el[key] 
    		if (type === 'boolean' && nextValue === '') {
   
    			el[key] = true
    		} else {
    
    			el[key] = nextValue 
    		} 
    	} else {
    
    		el.setAttribute(key, nextValue) 
    	} 
    } 
})

​ 从上面的代码中可以看到,我们对 class 进行了特殊处理,即使用 el.className 代替 setAttribute 函数。其实除了 class 属性之外,Vue.js 对 style 属性也做了增强,所以我们也需要对 style 做类似的处理。

​ 通过对 class 的处理,我们能够意识到,vnode.props 对象中定义的属性值的类型并不总是与 DOM 元素属性的数据结构保持一致,这取决于上层 API 的设计。Vue.js 允许对象类型的值作为 class 是为了方便开发者,在底层的实现上,必然需要对值进行正常化后再使用。另外,正常化值的过程是有代价的,如果需要进行大量的正常化操作,则会消耗更多性能。

五、卸载操作

​ 前文主要讨论了挂载操作。接下来,我们将会讨论卸载操作。卸载操作发生在更新阶段,更新指的是,在初次挂载完成之后,后续渲染会触发更新,如下面的代码所示:

// 初次挂载
renderer.render(vnode, document.querySelector('#app')) 
// 再次挂载新 vnode,将触发更新
renderer.render(newVNode, document.querySelector(<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值