- 开始时间:2019-11-05
- 目标主要版本:3.x
- 引用 issue:N/A
- 实现的 PR:N/A
摘要
- 在一个组件上使用的
v-on监听器将 fallthrough,被注册为子组件的 root 的原生监听器。不再需要.native修饰器。 inheritAttrs: false现在只会影响class和style。this.$attrs现在包含所有传递给组件的东西,除了那些明确声明为 props,包括class、style和v-on监听器。this.$listeners被移除。- 函数式组件的属性 fallthrough 行为得到调整:
- 有明确的
props声明:像有状态组件一样完全 fallthrough。 - 没有声明
props:只有class、style和v-on监听器的 fallthrough。
- 有明确的
基本范例
N/A
动机
在 Vue2.x 中,组件有一个隐式的特性 fallthrough 行为。任何传递给组件的特性,如果没有被组件声明为 props,就会被认为是一个不相干的特性。例如:
<MyComp id="foo"/>
如果 MyComp 没有声明一个名为 id 的 prop,那么 id 就被认为是一个不相干的属性,将隐式的应用于 MyComp 的根节点。
这种行为在调整父子间的布局风格时非常方便(通过传递 class 和 style),或将 a11y 属性应用于子组件。
这种行为可以用 inheritAttrs: false 来禁用,在这种情况下,用户期望明确地控制属性应该应用在哪里。这些不相干的属性在一个实例中暴露:this.$attrs。
2.x 的行为有许多不一致的地方和问题:
inheritAttrs: false不会影响class和style。- 隐式的 fallthrough 不适用于事件监听器,如果用户希望在子组件的 root 上添加一个原生的事件监听器,则需要使用
.native修饰器。 class、style和v-on监听器不包括在$attrs中,这使得高阶组件(HOC)要正确地将所有东西传递给嵌套的子组件变得很麻烦。- 函数式组件没有隐式的 attrs fallthrough 行为。
在 3.x 中,我们还引入了 Fragment(一个组件模版中多个根节点),这需要对行为进行额外的考虑。
具体设计
v-on 监听器 Fallthrough
有了以下用法:
<MyButton @click="hello" />
- 在 v2 中,
@click智慧注册一个组件的自定义事件监听器。要在MyButton的 root 上附加一个原生的监听器,需要@click.native。 - 在 v3 中,
@click监听器将 fullthrough 并在MyButton的 root 注册一个原生监听器。这意味着组件作者不再需要将原生 DOM 事件代理为自定义事件,以便在没有.native修饰器的情况下支持v-on的使用。事实上,.native修饰符将完全被删除。
避免不必要的原生监听器
当组件作者打算使用该事件名称(应该指的是 click 之类的)作为组件发出的自定义事件时,v3 的行为可能导致不必要的注册原生事件监听器。
有了扁平的 VNode 数据和移除 .native 修饰符,所有的监听器都作为 onXXX 函数传递给子组件:
<foo @click="foo" @custom="bar" />
编译成:
h(foo, {onClick: foo,onCustom: bar})
有了 fallthrough,所有的父级监听器都作为原生 DOM 监听器应用于目标元素。在上面的例子中,子元素中的原生点击事件和由 this.$emit("click") 发出的自定义事件都会父元素的 foo 处理器。这可能会导致不必要的行为。
在 #16 中提出的 emits 选项的引入,提供了一种明确声明事件为组件自定义事件的方法,这样事件的监听器将被排除在 fallthrough 之外(正手动控制的情况下,还有 this.$attrs)。这两个 RFC 应该一并考虑。
显式控制 fallthrough
inheritAttrs: false
有了 inheritAttrs: false,隐式的 fallthrough 被禁用。组件可以选择有意忽略所有不相干的 attrs,或者通过 v-bind="$attrs" 显式的控制 attrs 应该应用在哪里:
<div class="wrapper"><!-- apply attrs to an inner element instead of root --><input v-bind="$attrs"></div>
this.$attrs(以及 setup() 和函数式组件的 context.attrs)现在包含所有传递给组件的属性(只要它没有被声明为 props)。这包括 class、style、普通属性和 v-on 监听器。这是基于 Render Function API Change 中提出的扁平 props 结构。
v-on 监听器被包含在 $attrs 中作为 onXXX props。例如,@click 将生成 $attrs 中的 onClick 属性。如果用户想单独处理属性和监听器,可以用简单的辅助函数来完成,将以 on 开头的 props 和不以 on 开头的 props 分开。
多 root/fragment 组件
在 Vue3 中,组件可以有多个根元素(即 fragment root)。这种情况下,不能进行自动合并。用户将负责把 attrs 分散到所需的元素中。
<template><span>hello</span><div v-bind="$attrs">main element</div></template>
如果 $attrs 是非空的,并且用户没有执行明确的展开语法(在渲染过程中通过访问 this.$attrs 检查),将会发出一个运行时警告。组件应该把 $attrs 绑定到一个元素上,或者用 inheritAttrs: false 明确的消除这个警告。
在渲染函数中
在手动渲染函数中,仅仅使用展开语法似乎很方便:
export default {props: { /* ... */ },inheritAttrs: false,render() {return h('div', { class: 'foo', ...this.$attrs })}}
然而,这将会导致 attrs 覆盖任何现有的同名 props。例如,本地的 class 可能会被覆盖,而且我们可能想合并 class。Vue 提供了一个 mergeProps 帮助器,处理 class、style 和 onXXX 监听器的合并:
import { mergeProps } from 'vue'export default {props: { /* ... */ },inheritAttrs: false,render() {return h('div', mergeProps({ class: 'foo' }, this.$attrs))}}
这也是 v-bind 内部使用的方法。
如果从 setup 返回 render 函数,attrs 对象会在 setup 上下文中暴露。
import { mergeProps } from 'vue'export default {props: { /* ... */ },inheritAttrs: false,setup(props, { attrs }) {return () => {return h('div', mergeProps({ class: 'foo' }, attrs))}}}
注意,attrs 对象在每次渲染前都会被更新,因此可以对其进行解构。
函数式组件
在 2.x 中,函数式组件不支持属性自动 fallthrough,需要手动合并 props。
在 v3 中,函数式组件使用不同的语法:它们现在被声明为普通函数(如 Render Function API Change 中规定的)。
带有显式 props 声明
一个有 props 声明的函数式组件将和有状态组件一样的自动 fallthrough 行为。它也可以用 inheritAttrs: false 明确地控制 attrs:
const Func = (props, { attrs }) => {return h('div', mergeProps({ class: 'foo' }, attrs), 'hello')}Func.props = { /*...*/ }Func.inheritAttrs = false
带有选项 props 声明
v3 的函数式组件也支持选项 props 声明。当一个函数式组件没有定义 props 选项时,它接收由父级传递的所有属性作为其 props:
const Foo = props => h('div', { class: 'foo' }, props.msg)
当一个函数式组件利用选项 props 声明时,只有 class、style 和 v-on 监听器有隐式的 fallthrough。
原因是 class、style 和 v-on 要被列入白名单:
- 它们涵盖了属性 fallthrough 的常见使用情况。
- 它们几乎没有与 props 名称冲突的风险。
- 它们需要特殊的合并逻辑,而不是简单的覆盖,所以隐式的处理它们会产生更多便利的价值。
如果一个选项 props 声明的函数式组件需要支持所有属性 fallthrough,它需要声明 inheritAttrs: false,从 props 中挑选所需的 attrs,并将其合并到 root 元素:
// destructure props, and use rest spread to grab unused ones as attrs.const Func = ({ msg, ...attrs }) => {return h('div', mergeProps({ class: 'foo' }, attrs), msg)}Func.inheritAttrs = false
API 弃用
.native修饰符将从 v-on 中被移除。this.$listeners将被移除。
缺点
N/A
备选方案
N/A
采纳策略
- 在 compat 构建中可以支持弃用的 API:
.native修饰符将是一个无用的,并在模版编译时发出警告。this.$listeners可以被支持,但有运行时警告。
- 从技术上讲,可能会出现用户依赖 2.x 行为的情况,即
inheritAttrs: false也不影响class和style,但是这应该是非常罕见的。我们将在迁移指南/帮助程序中设置一个专门的项目,提醒开发者检查这种情况。 - 由于函数式组件使用了新的语法,它们可能需要手动升级。我们应该在迁移指南中为函数式组件设置一个专门的章节。
没有解决的问题
N/A
