Vue3 初探(二) 新特性
这篇博客我们继续来看Vue3的一些新特性,包括Teleport,片段,触发组件选项。
大部分内容来自官方文档,对部分难理解的部分进行了解释与总结。
Teleport
Vue 鼓励我们通过将 UI 和相关行为封装到组件中来构建 UI。我们可以将它们嵌套在另一个内部,以构建一个组成应用程序 UI 的树。
然而,有时组件模板的一部分逻辑上属于该组件,而从技术角度来看,最好将模板的这一部分移动到 DOM 中 Vue app 之外的其他位置。
也就是说我们希望有一种组件,它在逻辑上属于A组件的子组件,可以与A进行正常的父子组件可以进行的操作,但是实际上他又渲染为B组件的子组件。
一个常见的场景是创建一个包含全屏模式的组件。在大多数情况下,你希望模态的逻辑存在于组件中,但是模态的定位很快就很难通过 CSS 来解决,或者需要更改组件组合。
考虑下面的 HTML 结构。
1 | <body> |
让我们来看看 modal-button
组件:
该组件将有一个 button
元素来触发模态的打开,以及一个具有类 .modal
的 div
元素,它将包含模态的内容和一个用于自关闭的按钮。
1 | const app = Vue.createApp({}); |
当在初始的 HTML 结构中使用这个组件时,我们可以看到一个问题——模态是在深度嵌套的 div
中渲染的,而模态的 position:absolute
以父级相对定位的 div
作为引用。
Teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下呈现 HTML,而不必求助于全局状态或将其拆分为两个组件。
让我们修改 modal-button
以使用 <teleport>
,并告诉 Vue “Teleport 这个 HTML 到该‘body’标签”。
1 | app.component('modal-button', { |
因此,一旦我们单击按钮打开模式,Vue 将正确地将模态内容渲染为 body
标签的子级。
与 Vue components 一起使用
如果 <teleport>
包含 Vue 组件,则它仍将是 <teleport>
父组件的逻辑子组件:
1 | const app = Vue.createApp({ |
在这种情况下,即使在不同的地方渲染 child-component
,它仍将是 parent-component
的子级,并将从中接收 name
prop。
这也意味着来自父组件的注入按预期工作,并且子组件将嵌套在 Vue Devtools 中的父组件之下,而不是放在实际内容移动到的位置。
片段
在 Vue 3 中,组件现在正式支持多根节点组件,即片段!
2.x 语法
在 2.x 中,不支持多根组件,当用户意外创建多根组件时会发出警告,因此,为了修复此错误,许多组件被包装在一个 <div>
中。
1 | <!-- Layout.vue --> |
3.x 语法
在 3.x 中,组件现在可以有多个根节点!但是,这确实要求开发者明确定义属性应该分布在哪里。
1 | <!-- Layout.vue --> |
非Prop的Attribute的继承
一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 props 或 emits 定义的 attribute。常见的示例包括 class
、style
和 id
属性。
Attribute 继承
当组件返回单个根节点时,非 prop attribute 将自动添加到根节点的 attribute 中。例如,在 <date-picker>
组件的实例中:
1 | app.component('date-picker', { |
如果我们需要通过 data status
property 定义 <date-picker>
组件的状态,它将应用于根节点 (即 div.date-picker
)。
1 | <!-- 具有非prop attribute的Date-picker组件--> |
同样的规则适用于事件监听器
多个根节点上的 Attribute 继承
与单个根节点组件不同,具有多个根节点的组件不具有自动 attribute 回退行为。如果未显式绑定 $attrs
,将发出运行时警告。
1 | <custom-layout id="custom-layout" @click="changeValue"></custom-layout> |
1 | // 这将发出警告 |
自定义事件
事件名
不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。举个例子,如果触发一个 camelCase 名字的事件:
1 | this.$emit('myEvent') |
则监听这个名字的 kebab-case 版本是不会有任何效果的:
1 | <!-- 没有效果 --> |
不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或 property 名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on
事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 @myEvent
将会变成 @myevent
——导致 myEvent
不可能被监听到。
因此,我们推荐你始终使用 kebab-case 的事件名
定义自定义事件
可以通过 emits
选项在组件上定义已发出的事件。
1 | app.component('custom-form', { |
当在 emits
选项中定义了原生事件 (如 click
) 时,将使用组件中的事件替代原生事件侦听器。
验证抛出的事件
与 prop 类型验证类似,如果使用对象语法而不是数组语法定义发出的事件,则可以验证它。
要添加验证,将为事件分配一个函数,该函数接收传递给 $emit
调用的参数,并返回一个布尔值以指示事件是否有效。
1 | app.component('custom-form', { |
多个 v-model
绑定
通过利用以特定 prop 和事件为目标的能力,正如我们之前在 v-model
参数中所学的那样,我们现在可以在单个组件实例上创建多个 v-model 绑定。
每个 v-model 将同步到不同的 prop,而不需要在组件中添加额外的选项:
1 | <user-name |
1 | const app = Vue.createApp({}) |
自定义v-model修饰符
在 2.x 中,我们对组件 v-model
上的 .trim
等修饰符提供了硬编码支持。但是,如果组件可以支持自定义修饰符,则会更有用。在 3.x 中,添加到组件 v-model
的修饰符将通过 modelModifiers
prop 提供给组件:
让我们创建一个示例自定义修饰符 capitalize
,它将 v-model
绑定提供的字符串的第一个字母大写。
添加到组件 v-model
的修饰符将通过 modelModifiers
prop 提供给组件。在下面的示例中,我们创建了一个组件,其中包含默认为空对象的 modelModifiers
prop。
请注意,当组件的 created
生命周期钩子触发时,modelModifiers
prop 包含 capitalize
,其值为 true
——因为它被设置在 v-model
绑定 v-model.capitalize="bar"
。
现在我们已经设置了 prop,我们可以检查 modelModifiers
对象键并编写一个处理器来更改发出的值。在下面的代码中,每当 <input/>
元素触发 input
事件时,我们都将字符串大写。
1 | <div id="app"> |
1 | const app = Vue.createApp({ |
对于带参数的 v-model
绑定,生成的 prop 名称将为 arg + "Modifiers"
:
1 | <my-component v-model:foo.capitalize="bar"></my-component> |
1 | app.component('my-component', { |