Vue3渲染器设计(二) 挂载与更新 已停更

文章参考了霍春阳的《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。

暂时停更,有缘再更新啦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值