◆

Vue v-bind 绑定多属性与属性覆盖顺序

2023-02-02 01:370

v-bind 平时都是一个属性一个属性地写,:id="x"、:class="y"。但如果要传的属性有十几个,或者属性名本身是动态决定的,逐个写就很啰嗦了。v-bind 支持不带参数直接绑一个对象,把对象的每个键值对都展开成一个属性——这是封装二次组件时的常用手法。

用了这种写法马上会遇到一个问题:如果对象里已经有 id,我又在标签上单独写了一个 id,最后哪个生效?这篇把绑定多属性的写法和覆盖规则一起记下来,覆盖规则跟书写顺序有关,很容易踩。

v-bind 绑定多属性方法

写法是 v-bind="对象",不带冒号后面的属性名。Vue 会遍历这个对象,把每个键当作属性名、值当作属性值绑上去。下面的 testObj 里有四个键,等价于在 Child 上分别写了 :id、:class、:style、:myCustom 四个绑定。

<template>
  <Child v-bind="testObj" />
</template>

<script lang="ts" setup>
import Child from './vbindChild.vue';
import { ref } from 'vue';

const testObj = ref({
  id: 'test',
  class: 'm-50px bg-blue-500',
  style: 'fontSize: 18px',
  myCustom: 'mytest',
});
</script>

这里的 Child 是个子组件,那么这些属性去哪了?规则是:子组件用 defineProps 声明过的键会变成 props,没声明的(比如 myCustom)落进 attrs,默认自动继承到子组件的根元素上。所以即使子组件一个 props 都没声明,id 和 class 也会出现在渲染出的根标签上。

这个特性用在包装组件时特别省事。比如二次封装一个 MyInput,只想加点自己的样式和逻辑,其余属性全部透传给内部的原生 input,写 v-bind="$attrs" 一句就够了,不用把 placeholder、maxlength、disabled 一个个列出来。配合 inheritAttrs: false 关掉根元素的自动继承,可以精确控制这些属性落到哪个内部元素上。

绑定的对象是 ref 也没关系,Vue 会自动解包,模板里不需要写 .value。对象里的键增删也是响应式的,往 testObj 里加一个键,对应属性会立刻出现在元素上。

v-bind 顺序

对象展开和单独写的属性撞名时就要看顺序了。下面两行 Child 内容完全一样,只是 v-bind="testObj" 的位置一个在前一个在后,结果就不同。

<template>
  <Child v-bind="testObj" style="background: #42b983" id="customId" />
  <Child id="customId" style="background: #42b983" v-bind="testObj" />
</template>

<script lang="ts" setup>
import Child from './vbindChild.vue';
import { ref } from 'vue';

const testObj = ref({
  id: 'test',
  class: 'm-50px bg-blue-500',
  style: 'fontSize: 18px',
  myCustom: 'mytest',
});
</script>

结果如图所示 v-bind

这是浏览器开发者工具里两个 Child 渲染出的真实 DOM。重点看两处:一是两行的 id 值不同,第一个是 customId、第二个是 test,正好对应各自最后声明的那一方;二是两行的 style 里都同时出现了 font-size 和 background,说明 style 没有整体替换,而是把两边的声明合到了一起。

如果一个元素同时定义了 v-bind="object" 和一个相同的独立 attribute,那么绑定的声明后声明的覆盖先声明的 比如第一个 Child 组件,id="customId",第二个 id="test",但 style 会合并不同的属性

也就是说,这套规则和 CSS 选择器优先级、或者「模板上写死的更具体所以优先」都没关系,纯粹按模板里从左到右的书写顺序,后面的赢。这一点和 JS 的 Object.assign({}, a, b) 是同一个直觉:后展开的覆盖先展开的。

由此可以推出两种写法各自的用途:

  • v-bind="obj" 放前面,后面单独写的属性是强制值,调用方给什么都盖不掉。适合组件内部必须锁死的属性。
  • v-bind="obj" 放后面,前面单独写的属性是默认值,对象里有同名键就覆盖掉。适合「给个兜底,允许外部定制」的场景。

class 和 style 是这条规则的例外,它们会合并而不是替换。Vue 对这两个属性做了特殊处理,因为多来源的样式叠加是很常见的需求——外层想加个 margin,不该把组件自带的 class 全冲掉。合并后如果出现同名的 CSS 属性(比如两边都写了 background),最终还是由后声明的那个生效,这时候就是 CSS 层叠在起作用了。

要注意示例里 style 写的是 'fontSize: 18px' 这种字符串形式。字符串形式的 style 在合并时行为不如对象形式可靠,建议统一写成对象::style="{ fontSize: '18px' }"。对象形式下 Vue 能按键逐个合并,语义清楚得多。

💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEVue v-bind 绑定多属性与属性覆盖顺序加载中