Skip to content

04|属性透传 ​

第二章:组件模型与通信纵览 / 04。调用方传入但未被声明为 props 或 emits 的内容,会进入 attrs;封装组件必须决定这些内容最终落到哪个 DOM 节点。

前置问题:谁才是交互目标 ​

如果 TextField 的根元素是 <label>,调用方传入的 disabled、name、aria-describedby 显然应落到内部 <input>,而不是标签外壳。默认继承无法知道这个意图,所以包装组件要接管落点。

最小可运行封装 ​

vue
<script setup lang="ts">
defineOptions({ inheritAttrs: false })

defineProps<{
  label: string
  help?: string
  helpId?: string
}>()
</script>

<template>
  <label class="field">
    <span>{{ label }}</span>
    <input v-bind="$attrs" />
  </label>
  <p v-if="help" :id="helpId">{{ help }}</p>
</template>
vue
<script setup lang="ts">
import TextField from './TextField.vue'
</script>

<template>
  <TextField
    label="邮箱"
    help="用于接收通知"
    help-id="email-help"
    id="email"
    name="email"
    type="email"
    aria-describedby="email-help"
    data-testid="email-input"
  />
</template>

label、help 与 helpId 被 defineProps 消费,不会留在 $attrs。id、name、type、aria-describedby 和 data-testid 没有被声明,因此进入 $attrs,再由 v-bind="$attrs" 一次绑定到 <input>。

默认继承规则 ​

组件形状默认行为需要注意
单个元素根节点attrs 自动落到根元素class 与 style 会和根节点已有值合并
根节点是另一个组件attrs 可继续向该组件透传最终落点取决于更深一层的合同
多个根节点Vue 不会猜落点,并在未绑定时警告至少一个根节点显式 v-bind="$attrs"
inheritAttrs: false关闭自动落点$attrs 仍然可读,可手动绑定

正文例子同时使用多根节点和 inheritAttrs: false,即使将来重排根节点,输入属性仍稳定地落在 <input>。

class 和 style 也属于 attrs ​

若调用方写 <TextField class="wide" />,且组件保持默认单根继承,wide 会加在根 <label> 上,不是内部 <input>。包装原生控件时,这种“看起来有样式却选错节点”的错误很常见。若希望同时给外壳和输入传样式,应设计两个明确 prop,而不是让一个 class 同时承担两种含义。

在脚本中读取 attrs ​

vue
<script setup lang="ts">
import { useAttrs } from 'vue'

const attrs = useAttrs()
console.log(attrs['aria-label'])
</script>

连字符属性要用方括号读取。useAttrs() 返回的对象会在渲染时反映最新 attrs,但它不是普通响应式对象,不能指望 watch(attrs, ...) 捕获变化。若某个值要参与计算、监听或业务分支,就把它声明成 prop;attrs 只承担透传。

失败路径:忘记多根节点的落点 ​

先只删掉 v-bind="$attrs":因为仍有 inheritAttrs: false,Vue 尊重你接管属性的决定,不再尝试自动透传,也不会因此告警。输入会静默失去 name、ARIA 与测试选择器。再移除 defineOptions({ inheritAttrs: false }),恢复默认继承:此时多根组件没有指定出口,才会出现 extraneous non-props attributes 警告。无警告不能证明透传正确,最终要检查真实input。

另一个失败是把 $attrs 同时绑定到多个根节点:这会复制 id,产生无效 HTML,也可能让同一个监听器触发多次。一个 attrs 集合通常只应有一个明确目标。

何时应该声明 prop ​

  • 值会影响组件自己的渲染逻辑:声明 prop。
  • 值只是原样交给某个 DOM 元素:保留为 attr。
  • 调用方需要选择“外壳还是内部控件”:设计两个有名字的入口。
  • 需要监听值的变化:声明 prop,不监听 useAttrs()。

练习:精确透传搜索输入属性 ​

实现 SearchField.vue:

  • props 只有 label: string 与 help: string。
  • 模板保留两个根节点:一个 <label> 和一个帮助 <p>。
  • 调用方传入的 id、name、placeholder、aria-describedby、data-testid 与 class 全部只落到内部 <input>。
  • label、help 不得出现在 <input> 的 DOM 属性中。
  • 只删除显式透传时确认属性静默丢失;再恢复默认继承以复现多根警告。最后恢复关闭继承与显式透传,确认只有一个带该 id 的元素。

参考答案:查看第 2 章第 04 题参考答案。

通关自检:给定任意包装组件,你能列出哪些键被 props/emits 消费、哪些留在 attrs,以及 attrs 最终绑定到哪个真实元素。

用心记录每一步成长