Vue.js 3 是如何正确设置元素属性的?

设置 DOM 的属性有两种方法

  • setAttribute

  • 直接设置元素的 DOM Properties

但是,无论是使用 setAttribute 函数,还是直接将属性设置在 DOM 对象上,都存在缺陷。

例如有以下模板:

<button :disabled="false">Button</button>

编译为 vnode 为:

const button = {
  type: 'button',
  props: {
    disabled: false
  }
}

用户的本意不是禁用按钮,但如果渲染器仍然使用 setAttribute 函数设置属性值,则会产生意外的效果,即按钮被禁用了:

el.setAttribute('disabled', false)

同时,发现使用 setAttribute 函数设置的值总是会被字符串化,用户明明设置的是 boolean 值,通过 setAttribute 函数设置后变成了 string ,所以单独使用 setAttribute 函数设置元素的属性显然是不合理的。

接下来试试直接在 DOM 对象上设置属性值:

el.disabled = false

发现按钮没有被禁用了,似乎可行。那再看看下面的模板:

<button disabled>Button</button>

上面模板对应的 vnode 是:

const button = {
  type: 'button',
  props: {
    disabled: ''
  }
}

可以看到,在模板经过编译后得到 vnode 对象中,props.disabled 的值是一个空字符串。如果直接用它设置元素的 DOM Properties,那么相当于:

el.disabled = ''

由于 el.disabled 是布尔类型的值,所以当我们将它设置为空字符串时,浏览器会将它的值矫正为布尔类型的值,即 false。所以上面这句代码的执行结果等价于:

el.disabled = false

这违背了用户的本意,因为用户希望禁用按钮,而 el.disabled = false 则是不禁用的意思。

综上,无论使用 setAttribute 函数,还是直接设置元素的 DOM Properties ,都存在缺陷。要彻底解决这个问题,只能做特殊处理,即优先设置元素的 DOM Properties,但当值为空字符串时,要手动将值矫正为 true。只有这样,才能保证代码的行为符合预期。

Vue3 源码中,使用 shouldSetAsProp 函数判断属性是否应该作为 DOM Properties 被设置。如果返回 true ,则代表应该作为 DOM Properties 被设置,否则应该使用 setAttribute 函数来设置。

function shouldSetAsProp(
  el: Element,
  key: string,
  value: unknown,
  isSVG: boolean
) {
  if (isSVG) {
    // most keys must be set as attribute on svg elements to work
    // ...except innerHTML & textContent
    if (key === 'innerHTML' || key === 'textContent') {
      return true
    }
    // or native onclick with function values
    if (key in el && nativeOnRE.test(key) && isFunction(value)) {
      return true
    }
    return false
  }

  // these are enumerated attrs, however their corresponding DOM properties
  // are actually booleans - this leads to setting it with a string "false"
  // value leading it to be coerced to `true`, so we need to always treat
  // them as attributes.
  // Note that `contentEditable` doesn't have this problem: its DOM
  // property is also enumerated string values.
  if (key === 'spellcheck' || key === 'draggable' || key === 'translate') {
    return false
  }

  // #1787, #2840 form property on form elements is readonly and must be set as
  // attribute.
  if (key === 'form') {
    return false
  }

  // #1526 <input list> must be set as attribute
  if (key === 'list' && el.tagName === 'INPUT') {
    return false
  }

  // #2766 <textarea type> must be set as attribute
  if (key === 'type' && el.tagName === 'TEXTAREA') {
    return false
  }

  // native onclick with string value, must be set as attribute
  if (nativeOnRE.test(key) && isString(value)) {
    return false
  }

  return key in el
}

👆 上面代码摘自 Vue.js 3.2.45 版本

如上面代码所示,对于 svg 元素:

  1. innerHTML 、textContent 属性可以直接作为 DOM Properties 设置。

  2. 如果属性在 DOM Properties 中(key in el),原生事件(onclickoninput 等)并且是 function 类型,则可以作为 DOM Properties 设置。

  3. 其他情况都不能作为 DOM Properties 直接设置。

就如代码注释中所说:

  • most keys must be set as attribute on svg elements to work…except innerHTML & textContent(对于 svg 元素,大部分属性需要使用 setAttribute 函数设置属性,除了 innerHTML 、textContent)

对于非 svg 元素:

  1. spellcheck 、draggable、translate 是枚举类型的属性 。使用 setAttribute 函数来设置,因此返回 false

  2. form 属性为只读属性,必须使用 setAttribute 函数

  3. list 属性、tagName 为 INPUT ,必须使用 setAttribute 函数

  4. type 属性,tagName 为 TEXTAREA ,必须使用 setAttribute 函数

  5. 原生 dom 事件(onclickoninput) 并且是字符串类型,使用 setAttribute 函数

  6. key in el ,兜底处理,dom (el)对象上有 key 属性,则作为 DOM Properties 直接设置。

在 Vue3 的源码中,使用 patchProp 函数来处理元素的属性更新和设置。

function patchProp(
  el,
  key,
  prevValue,
  nextValue
) {
  if (key === 'class') {
    // class 的处理,非本文重点,略过讲解
  } else if (key === 'style') {
    // style 的处理,非本文重点,略过讲解
  } else if (isOn(key)) {
    // 事件的处理,非本文重点,略过讲解
  } else if (shouldSetAsProp(el, key, nextValue, isSVG)) {    
    patchDOMProp(
      el,
      key,
      nextValue,
      prevChildren,
    )
  } else {
    patchAttr(el, key, nextValue, isSVG, parentComponent)
  }
}

👆 上面代码摘自 Vue.js 3.2.45 版本

上面的代码中

  • patchDOMProp 函数的核心逻辑为 el[key] = value

  • patchAttr 函数的核心逻辑为 el.setAttribute(key, value)

总结

回到一开始提的问题:Vue.js 3 是如何正确设置元素属性的?

Vue3 设置元素属性时,优先使用直接设置 DOM Properties ,对于某些情况做特殊判断处理,例如:当属性为只读时使用 setAttribute 函数、当值为空字符串时,手动将值矫正为 true 。

Vue3 通过结合直接设置 DOM Properties 和 setAttribute 函数的方式来正确地设置元素属性的。

Vue3 也结合了开源社区的反馈,不断地完善设置元素属性的代码逻辑。

原文链接:https://juejin.cn/post/7318606126995406857 作者:云浪

(0)
上一篇 2024年1月2日 上午10:21
下一篇 2024年1月2日 上午10:31

相关推荐

发表回复

登录后才能评论