--- url: /api/sfc-script-setup.md --- # \ ``` 里面的代码会被编译成组件 `setup()` 函数的内容。这意味着与普通的 ` ``` import 导入的内容也会以同样的方式暴露。这意味着我们可以在模板表达式中直接使用导入的 helper 函数,而不需要通过 `methods` 选项来暴露它: ```vue ``` ## 响应式 {#reactivity} 响应式状态需要明确使用[响应式 API](/api/reactivity-core) 来创建。和 `setup()` 函数的返回值一样,ref 在模板中使用的时候会自动解包: ```vue ``` ## 使用组件 {#using-components} ` ``` 这里 `MyComponent` 应当被理解为像是在引用一个变量。如果你使用过 JSX,此处的心智模型是类似的。其 kebab-case 格式的 `` 同样能在模板中使用——不过,我们强烈建议使用 PascalCase 格式以保持一致性。同时这也有助于区分原生的自定义元素。 ### 动态组件 {#dynamic-components} 由于组件是通过变量引用而不是基于字符串组件名注册的,在 ` ``` 请注意组件是如何在三元表达式中被当做变量使用的。 ### 递归组件 {#recursive-components} 一个单文件组件可以通过它的文件名被其自己所引用。例如:名为 `FooBar.vue` 的组件可以在其模板中用 `` 引用它自己。 请注意这种方式相比于导入的组件优先级更低。如果有具名的导入和组件自身推导的名字冲突了,可以为导入的组件添加别名: ```js import { FooBar as FooBarChild } from './components' ``` ### 命名空间组件 {#namespaced-components} 可以使用带 `.` 的组件标签,例如 `` 来引用嵌套在对象属性中的组件。这在需要从单个文件中导入多个组件的时候非常有用: ```vue ``` ## 使用自定义指令 {#using-custom-directives} 全局注册的自定义指令将正常工作。本地的自定义指令在 ` ``` 如果指令是从别处导入的,可以通过重命名来使其符合命名规范: ```vue ``` ## defineProps() 和 defineEmits() {#defineprops-defineemits} 为了在声明 `props` 和 `emits` 选项时获得完整的类型推导支持,我们可以使用 `defineProps` 和 `defineEmits` API,它们将自动地在 ` ``` - `defineProps` 和 `defineEmits` 都是只能在 ` ``` 当父组件通过模板引用的方式获取到当前组件的实例,获取到的实例会像这样 `{ a: number, b: number }` (ref 会和在普通实例中一样被自动解包) ## defineOptions() {#defineoptions} - 仅在 3.3+ 中支持 这个宏可以用来直接在 ` ``` - 这是一个宏定义,选项将会被提升到模块作用域中,无法访问 ` ``` ## `useSlots()` 和 `useAttrs()` {#useslots-useattrs} 在 ` ``` `useSlots` 和 `useAttrs` 是真实的运行时函数,它的返回与 `setupContext.slots` 和 `setupContext.attrs` 等价。它们同样也能在普通的组合式 API 中使用。 ## 与普通的 ` ``` 在同一组件中将 ` ``` 另外,await 的表达式会自动编译成在 `await` 之后保留当前组件实例上下文的格式。 :::warning 注意 `async setup()` 必须与 [`Suspense`](/guide/built-ins/suspense.html) 组合使用,该特性目前仍处于实验阶段。我们计划在未来的版本中完成该特性并编写文档——但如果你现在就感兴趣,可以参考其[测试](https://github.com/vuejs/core/blob/main/packages/runtime-core/__tests__/components/Suspense.spec.ts)来了解其工作方式。 ::: ## 导入语句 {#imports-statements} Vue 中的导入语句遵循 [ECMAScript 模块规范](https://nodejs.org/api/esm.html)。 此外,你还可以使用构建工具配置中定义的别名: ```vue ``` ## 泛型 {#generics} 可以使用 ` ``` `generic` 的值与 TypeScript 中位于 `<...>` 之间的参数列表完全相同。例如,你可以使用多个参数,`extends` 约束,默认类型和引用导入的类型: ```vue ``` You can use `@vue-generic` the directive to pass in explicit types, for when the type cannot be inferred: ```vue ``` 为了在 `ref` 中使用泛型组件的引用,你需要使用 [`vue-component-type-helpers`](https://www.npmjs.com/package/vue-component-type-helpers) 库,因为 `InstanceType` 在这种场景下不起作用。 ```vue --- --- url: /guide/essentials/class-and-style.md --- # Class 与 Style 绑定 {#class-and-style-bindings} 数据绑定的一个常见需求场景是操纵元素的 CSS class 列表和内联样式。因为 `class` 和 `style` 都是 attribute,我们可以和其他 attribute 一样使用 `v-bind` 将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue 专门为 `class` 和 `style` 的 `v-bind` 用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组。 ## 绑定 HTML class {#binding-html-classes} ### 绑定对象 {#binding-to-objects} 我们可以给 `:class` (`v-bind:class` 的缩写) 传递一个对象来动态切换 class: ```vue-html
``` 上面的语法表示 `active` 是否存在取决于数据属性 `isActive` 的[真假值](https://developer.mozilla.org/en-US/docs/Glossary/Truthy)。 你可以在对象中写多个字段来操作多个 class。此外,`:class` 指令也可以和一般的 `class` attribute 共存。举例来说,下面这样的状态:
```js const isActive = ref(true) const hasError = ref(false) ```
```js data() { return { isActive: true, hasError: false } } ```
配合以下模板: ```vue-html
``` 渲染的结果会是: ```vue-html
``` 当 `isActive` 或者 `hasError` 改变时,class 列表会随之更新。举例来说,如果 `hasError` 变为 `true`,class 列表也会变成 `"static active text-danger"`。 绑定的对象并不一定需要写成内联字面量的形式,也可以直接绑定一个对象:
```js const classObject = reactive({ active: true, 'text-danger': false }) ```
```js data() { return { classObject: { active: true, 'text-danger': false } } } ```
```vue-html
``` 这将渲染: ```vue-html
``` 我们也可以绑定一个返回对象的[计算属性](./computed)。这是一个常见且很有用的技巧:
```js const isActive = ref(true) const error = ref(null) const classObject = computed(() => ({ active: isActive.value && !error.value, 'text-danger': error.value && error.value.type === 'fatal' })) ```
```js data() { return { isActive: true, error: null } }, computed: { classObject() { return { active: this.isActive && !this.error, 'text-danger': this.error && this.error.type === 'fatal' } } } ```
```vue-html
``` ### 绑定数组 {#binding-to-arrays} 我们可以给 `:class` 绑定一个数组来渲染多个 CSS class:
```js const activeClass = ref('active') const errorClass = ref('text-danger') ```
```js data() { return { activeClass: 'active', errorClass: 'text-danger' } } ```
```vue-html
``` 渲染的结果是: ```vue-html
``` 如果你也想在数组中有条件地渲染某个 class,你可以使用三元表达式: ```vue-html
``` `errorClass` 会一直存在,但 `activeClass` 只会在 `isActive` 为真时才存在。 然而,这可能在有多个依赖条件的 class 时会有些冗长。因此也可以在数组中嵌套对象: ```vue-html
``` ### 在组件上使用 {#with-components} > 本节假设你已经有 [Vue 组件](/guide/essentials/component-basics)的知识基础。如果没有,你也可以暂时跳过,以后再阅读。 对于只有一个根元素的组件,当你使用了 `class` attribute 时,这些 class 会被添加到根元素上并与该元素上已有的 class 合并。 举例来说,如果你声明了一个组件名叫 `MyComponent`,模板如下: ```vue-html

Hi!

``` 在使用时添加一些 class: ```vue-html ``` 渲染出的 HTML 为: ```vue-html

Hi!

``` Class 的绑定也是同样的: ```vue-html ``` 当 `isActive` 为真时,被渲染的 HTML 会是: ```vue-html

Hi!

``` 如果你的组件有多个根元素,你将需要指定哪个根元素来接收这个 class。你可以通过组件的 `$attrs` 属性来指定接收的元素: ```vue-html

Hi!

This is a child component ``` ```vue-html ``` 这将被渲染为: ```html

Hi!

This is a child component ``` 你可以在[透传 Attribute](/guide/components/attrs) 一章中了解更多组件的 attribute 继承的细节。 ## 绑定内联样式 {#binding-inline-styles} ### 绑定对象 {#binding-to-objects-1} `:style` 支持绑定 JavaScript 对象值,对应的是 [HTML 元素的 `style` 属性](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style):
```js const activeColor = ref('red') const fontSize = ref(30) ```
```js data() { return { activeColor: 'red', fontSize: 30 } } ```
```vue-html
``` 尽管推荐使用 camelCase,但 `:style` 也支持 kebab-cased 形式的 CSS 属性 key (对应其 CSS 中的实际名称),例如: ```vue-html
``` 直接绑定一个样式对象通常是一个好主意,这样可以使模板更加简洁:
```js const styleObject = reactive({ color: 'red', fontSize: '30px' }) ```
```js data() { return { styleObject: { color: 'red', fontSize: '13px' } } } ```
```vue-html
``` 同样的,如果样式对象需要更复杂的逻辑,也可以使用返回样式对象的计算属性。 `:style` 指令也可以和常规的 style attribute 共存,就像 `:class`。 模板: ```vue-html

hello

``` 这将被渲染为: ```vue-html

hello

``` ### 绑定数组 {#binding-to-arrays-1} 我们还可以给 `:style` 绑定一个包含多个样式对象的数组。这些对象会被合并后渲染到同一元素上: ```vue-html
``` ### 自动前缀 {#auto-prefixing} 当你在 `:style` 中使用了需要[浏览器特殊前缀](https://developer.mozilla.org/en-US/docs/Glossary/Vendor_Prefix)的 CSS 属性时,Vue 会自动为他们加上相应的前缀。Vue 是在运行时检查该属性是否支持在当前浏览器中使用。如果浏览器不支持某个属性,那么将尝试加上各个浏览器特殊前缀,以找到哪一个是被支持的。 ### 样式多值 {#multiple-values} 你可以对一个样式属性提供多个 (不同前缀的) 值,举例来说: ```vue-html
``` 数组仅会渲染浏览器支持的最后一个值。在这个示例中,在支持不需要特别前缀的浏览器中都会渲染为 `display: flex`。 --- --- url: /guide/built-ins/keep-alive.md --- # KeepAlive {#keepalive} `` 是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。 ## 基本使用 {#basic-usage} 在组件基础章节中,我们已经介绍了通过特殊的 `` 元素来实现[动态组件](/guide/essentials/component-basics#dynamic-components)的用法: ```vue-html ``` 默认情况下,一个组件实例在被替换掉后会被销毁。这会导致它丢失其中所有已变化的状态——当这个组件再一次被显示时,会创建一个只带有初始状态的新实例。 在下面的例子中,你会看到两个有状态的组件——A 有一个计数器,而 B 有一个通过 `v-model` 同步 input 框输入内容的文字展示。尝试先更改一下任意一个组件的状态,然后切走,再切回来: 你会发现在切回来之后,之前已更改的状态都被重置了。 在切换时创建新的组件实例通常是有意义的,但在这个例子中,我们的确想要组件能在被“切走”的时候保留它们的状态。要解决这个问题,我们可以用 `` 内置组件将这些动态组件包装起来: ```vue-html ``` 现在,在组件切换时状态也能被保留了:
[在演练场中尝试一下](https://play.vuejs.org/#eNqtUsFOwzAM/RWrl4IGC+cqq2h3RFw495K12YhIk6hJi1DVf8dJSllBaAJxi+2XZz8/j0lhzHboeZIl1NadMA4sd73JKyVaozsHI9hnJqV+feJHmODY6RZS/JEuiL1uTTEXtiREnnINKFeAcgZUqtbKOqj7ruPKwe6s2VVguq4UJXEynAkDx1sjmeMYAdBGDFBLZu2uShre6ioJeaxIduAyp0KZ3oF7MxwRHWsEQmC4bXXDJWbmxpjLBiZ7DwptMUFyKCiJNP/BWUbO8gvnA+emkGKIgkKqRrRWfh+Z8MIWwpySpfbxn6wJKMGV4IuSs0UlN1HVJae7bxYvBuk+2IOIq7sLnph8P9u5DJv5VfpWWLaGqTzwZTCOM/M0IaMvBMihd04ruK+lqF/8Ajxms8EFbCiJxR8khsP6ncQosLWnWV6a/kUf2nqu75Fby04chA0iPftaYryhz6NBRLjdtajpHZTWPio=)
[在演练场中尝试一下](https://play.vuejs.org/#eNqtU8tugzAQ/JUVl7RKWveMXFTIseofcHHAiawasPxArRD/3rVNSEhbpVUrIWB3x7PM7jAkuVL3veNJmlBTaaFsVraiUZ22sO0alcNedw2s7kmIPHS1ABQLQDEBAMqWvwVQzffMSQuDz1aI6VreWpPCEBtsJppx4wE1s+zmNoIBNLdOt8cIjzut8XAKq3A0NAIY/QNveFEyi8DA8kZJZjlGALQWPVSSGfNYJjVvujIJeaxItuMyo6JVzoJ9VxwRmtUCIdDfNV3NJWam5j7HpPOY8BEYkwxySiLLP1AWkbK4oHzmXOVS9FFOSM3jhFR4WTNfRslcO54nSwJKcCD4RsnZmJJNFPXJEl8t88quOuc39fCrHalsGyWcnJL62apYNoq12UQ8DLEFjCMy+kKA7Jy1XQtPlRTVqx+Jx6zXOJI1JbH4jejg3T+KbswBzXnFlz9Tjes/V/3CjWEHDsL/OYNvdCE8Wu3kLUQEhy+ljh+brFFu)
:::tip 在 [DOM 内模板](/guide/essentials/component-basics#in-dom-template-parsing-caveats)中使用时,它应该被写为 ``。 ::: ## 包含/排除 {#include-exclude} `` 默认会缓存内部的所有组件实例,但我们可以通过 `include` 和 `exclude` prop 来定制该行为。这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组: ```vue-html ``` 它会根据组件的 [`name`](/api/options-misc#name) 选项进行匹配,所以组件如果想要条件性地被 `KeepAlive` 缓存,就必须显式声明一个 `name` 选项。 :::tip 在 3.2.34 或以上的版本中,使用 ` ```
一个持续存在的组件可以通过 [`activated`](/api/options-lifecycle#activated) 和 [`deactivated`](/api/options-lifecycle#deactivated) 选项来注册相应的两个状态的生命周期钩子: ```js export default { activated() { // 在首次挂载、 // 以及每次从缓存中被重新插入的时候调用 }, deactivated() { // 在从 DOM 上移除、进入缓存 // 以及组件卸载时调用 } } ```
请注意: - `onActivated``activated` 在组件挂载时也会调用,并且 `onDeactivated``deactivated` 在组件卸载时也会调用。 - 这两个钩子不仅适用于 `` 缓存的根组件,也适用于缓存树中的后代组件。 --- **参考** - [`` API 参考](/api/built-in-components#keepalive) --- --- url: /style-guide/PLEADE_DO_NOT_TRANSLATE.md --- # Please DO NOT Translate Please do not translate the style guide docs until further consideration since: 1. The English version still needs to be revamped. 2. Currently there is no entry link on the doc site. Thanks. Ref: https://github.com/vuejs-translations/docs-zh-cn/pull/237#issuecomment-1148418220 --- --- url: /guide/components/props.md --- # Props {#props} > 此章节假设你已经看过了[组件基础](/guide/essentials/component-basics)。若你还不了解组件是什么,请先阅读该章节。 ## Props 声明 {#props-declaration} 一个组件需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props,哪些是透传 attribute (关于透传 attribute,我们会在[专门的章节](/guide/components/attrs)中讨论)。
在使用 ` ``` 在没有使用 ` ``` 更多关于基于类型的声明的细节请参考[组件 props 类型标注](/guide/typescript/composition-api#typing-component-props)。
## 响应式 Props 解构 \*\* {#reactive-props-destructure} Vue 的响应系统基于属性访问跟踪状态的使用情况。例如,在计算属性或侦听器中访问 `props.foo` 时,`foo` 属性将被跟踪为依赖项。 因此,在以下代码的情况下: ```js const { foo } = defineProps(['foo']) watchEffect(() => { // 在 3.5 之前只运行一次 // 在 3.5+ 中在 "foo" prop 变化时重新执行 console.log(foo) }) ``` 在 3.4 及以下版本,`foo` 是一个实际的常量,永远不会改变。在 3.5 及以上版本,当在同一个 ` ``` ### 异步组件 {#async-components} 异步组件默认就是“suspensible”的。这意味着如果组件关系链上有一个 ``,那么这个异步组件就会被当作这个 `` 的一个异步依赖。在这种情况下,加载状态是由 `` 控制,而该组件自己的加载、报错、延时和超时等选项都将被忽略。 异步组件也可以通过在选项中指定 `suspensible: false` 表明不用 `Suspense` 控制,并让组件始终自己控制其加载状态。 ## 加载中状态 {#loading-state} `` 组件有两个插槽:`#default` 和 `#fallback`。两个插槽都只允许**一个**直接子节点。在可能的时候都将显示默认插槽中的节点。否则将显示后备插槽中的节点。 ```vue-html ``` 在初始渲染时,`` 将在内存中渲染其默认的插槽内容。如果在这个过程中遇到任何异步依赖,则会进入**挂起**状态。在挂起状态期间,展示的是后备内容。当所有遇到的异步依赖都完成后,`` 会进入**完成**状态,并将展示出默认插槽的内容。 如果在初次渲染时没有遇到异步依赖,`` 会直接进入完成状态。 进入完成状态后,只有当默认插槽的根节点被替换时,`` 才会回到挂起状态。组件树中新的更深层次的异步依赖**不会**造成 `` 回退到挂起状态。 发生回退时,后备内容不会立即展示出来。相反,`` 在等待新内容和异步依赖完成时,会展示之前 `#default` 插槽的内容。这个行为可以通过一个 `timeout` prop 进行配置:在等待渲染新内容耗时超过 `timeout` 之后,`` 将会切换为展示后备内容。若 `timeout` 值为 `0` 将导致在替换默认内容时立即显示后备内容。 ## 事件 {#events} `` 组件会触发三个事件:`pending`、`resolve` 和 `fallback`。`pending` 事件是在进入挂起状态时触发。`resolve` 事件是在 `default` 插槽完成获取新内容时触发。`fallback` 事件则是在 `fallback` 插槽的内容显示时触发。 例如,可以使用这些事件在加载新组件时在之前的 DOM 最上层显示一个加载指示器。 ## 错误处理 {#error-handling} `` 组件自身目前还不提供错误处理,不过你可以使用 [`errorCaptured`](/api/options-lifecycle#errorcaptured) 选项或者 [`onErrorCaptured()`](/api/composition-api-lifecycle#onerrorcaptured) 钩子,在使用到 `` 的父组件中捕获和处理异步错误。 ## 和其他组件结合 {#combining-with-other-components} 我们常常会将 `` 和 [``](./transition)、[``](./keep-alive) 等组件结合。要保证这些组件都能正常工作,嵌套的顺序非常重要。 另外,这些组件都通常与 [Vue Router](https://router.vuejs.org/zh/) 中的 `` 组件结合使用。 下面的示例展示了如何嵌套这些组件,使它们都能按照预期的方式运行。若想组合得更简单,你也可以删除一些你不需要的组件: ```vue-html ``` Vue Router 使用动态导入对[懒加载组件](https://router.vuejs.org/zh/guide/advanced/lazy-loading.html)进行了内置支持。这些与异步组件不同,目前他们不会触发 ``。但是,它们仍然可以有异步组件作为后代,这些组件可以照常触发 ``。 ## 嵌套使用 {#nested-suspense} - 仅在 3.3+ 支持 当我们有多个类似于下方的异步组件 (常见于嵌套或基于布局的路由) 时: ```vue-html ``` `` 创建了一个边界,它将如预期的那样解析树下的所有异步组件。然而,当我们更改 `DynamicAsyncOuter` 时,`` 会正确地等待它,但当我们更改 `DynamicAsyncInner` 时,嵌套的 `DynamicAsyncInner` 会呈现为一个空节点,直到它被解析为止 (而不是之前的节点或回退插槽)。 为了解决这个问题,我们可以使用嵌套的方法来处理嵌套组件的补丁,就像这样: ```vue-html ``` 如果你不设置 `suspensible` 属性,内部的 `` 将被父级 `` 视为同步组件。这意味着它将会有自己的回退插槽,如果两个 `Dynamic` 组件同时被修改,则当子 `` 加载其自己的依赖关系树时,可能会出现空节点和多个修补周期,这可能不是理想情况。设置后,所有异步依赖项处理都会交给父级 `` (包括发出的事件),而内部 `` 仅充当依赖项解析和修补的另一个边界。 --- **参考** - [`` API 参考](/api/built-in-components#suspense) --- --- url: /guide/built-ins/teleport.md --- # Teleport {#teleport} `` 是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。 ## 基本用法 {#basic-usage} 有时我们可能会遇到这样的场景:一个组件模板的一部分在逻辑上从属于该组件,但从整个应用视图的角度来看,它在 DOM 中应该被渲染在其他地方,甚至在整个 Vue 应用外部。 这类场景最常见的例子就是全屏的模态框。理想情况下,我们希望触发模态框的按钮和模态框本身的代码是在同一个单文件组件中,因为它们都与组件的开关状态有关。但这意味着该模态框将与按钮一起渲染在应用 DOM 结构里很深的地方。这会导致该模态框的 CSS 布局代码很难写。 试想下面这样的 HTML 结构: ```vue-html

Tooltips with Vue 3 Teleport

``` 接下来我们来看看 `` 的实现:
```vue ```
```vue ```
这个组件中有一个 ` ``` `` 接收一个 `to` prop 来指定传送的目标。`to` 的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段**传送到 `body`** 标签下”。 你可以点击下面这个按钮,然后通过浏览器的开发者工具,在 `` 标签下找到模态框元素:
我们也可以将 `` 和 [``](./transition) 结合使用来创建一个带动画的模态框。你可以看看[这个示例](/examples/#modal)。 :::tip `` 挂载时,传送的 `to` 目标必须已经存在于 DOM 中。理想情况下,这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由 Vue 渲染的,你需要确保在挂载 `` 之前先挂载该元素。 ::: ## 搭配组件使用 {#using-with-components} `` 只改变了渲染的 DOM 结构,它不会影响组件间的逻辑关系。也就是说,如果 `` 包含了一个组件,那么该组件始终和这个使用了 `` 的组件保持逻辑上的父子关系。传入的 props 和触发的事件也会照常工作。 这也意味着来自父组件的注入也会按预期工作,子组件将在 Vue Devtools 中嵌套在父级组件下面,而不是放在实际内容移动到的地方。 ## 禁用 Teleport {#disabling-teleport} 在某些场景下可能需要视情况禁用 ``。举例来说,我们想要在桌面端将一个组件当做浮层来渲染,但在移动端则当作行内组件。我们可以通过对 `` 动态地传入一个 `disabled` prop 来处理这两种不同情况: ```vue-html ... ``` 然后我们可以动态地更新 `isMobile`。 ## 多个 Teleport 共享目标 {#multiple-teleports-on-the-same-target} 一个可重用的 `` 组件可能同时存在多个实例。对于此类场景,多个 `` 组件可以将其内容挂载在同一个目标元素上,而顺序就是简单的顺次追加,后挂载的将排在目标元素下更后面的位置上,但都在目标元素中。 比如下面这样的用例: ```vue-html
A
B
``` 渲染的结果为: ```html
A
B
``` ## 延迟解析的 Teleport {#deferred-teleport} 在 Vue 3.5 及更高版本中,我们可以使用 `defer` prop 推迟 Teleport 的目标解析,直到应用的其他部分挂载。这允许 Teleport 将由 Vue 渲染且位于组件树之后部分的容器元素作为目标: ```vue-html ...
``` 请注意,目标元素必须与 Teleport 在同一个挂载/更新周期内渲染,即如果 `
` 在一秒后才挂载,Teleport 仍然会报错。延迟 Teleport 的原理与 `mounted` 生命周期钩子类似。 --- **参考** - [`` API 参考](/api/built-in-components#teleport) - [在 SSR 中处理 Teleports](/guide/scaling-up/ssr#teleports) --- --- url: /guide/built-ins/transition.md --- # Transition {#transition} Vue 提供了两个内置组件,可以帮助你制作基于状态变化的过渡和动画: - `` 会在一个元素或组件进入和离开 DOM 时应用动画。本章节会介绍如何使用它。 - `` 会在一个 `v-for` 列表中的元素或组件被插入,移动,或移除时应用动画。我们将在[下一章节](/guide/built-ins/transition-group)中介绍。 除了这两个组件,我们也可以通过其他技术手段来应用动画,比如切换 CSS class 或用状态绑定样式来驱动动画。这些其他的方法会在[动画技巧](/guide/extras/animation)章节中展开。 ## `` 组件 {#the-transition-component} `` 是一个内置组件,这意味着它在任意别的组件中都可以被使用,无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发: - 由 `v-if` 所触发的切换 - 由 `v-show` 所触发的切换 - 由特殊元素 `` 切换的动态组件 - 改变特殊的 `key` 属性 以下是最基本用法的示例: ```vue-html

hello

``` ```css /* 下面我们会解释这些 class 是做什么的 */ .v-enter-active, .v-leave-active { transition: opacity 0.5s ease; } .v-enter-from, .v-leave-to { opacity: 0; } ```
[在演练场中尝试一下](https://play.vuejs.org/#eNpVkEFuwyAQRa8yZZNWqu1sunFJ1N4hSzYUjRNUDAjGVJHluxcCipIV/OG/pxEr+/a+TwuykfGogvYEEWnxR2H17F0gWCHgBBtMwc2wy9WdsMIqZ2OuXtwfHErhlcKCb8LyoVoynwPh7I0kzAmA/yxEzsKXMlr9HgRr9Es5BTue3PlskA+1VpFTkDZq0i3niYfU6anRmbqgMY4PZeH8OjwBfHhYIMdIV1OuferQEoZOKtIJ328TgzJhm8BabHR3jeC8VJqusO8/IqCM+CnsVqR3V/mfRxO5amnkCPuK5B+6rcG2fydshks=)
[在演练场中尝试一下](https://play.vuejs.org/#eNpVkMFuAiEQhl9lyqlNuouXXrZo2nfwuBeKs0qKQGBAjfHdZZfVrAmB+f/M/2WGK/v1vs0JWcdEVEF72vQWz94Fgh0OMhmCa28BdpLk+0etAQJSCvahAOLBnTqgkLA6t/EpVzmCP7lFEB69kYRFAYi/ROQs/Cij1f+6ZyMG1vA2vj3bbN1+b1Dw2lYj2yBt1KRnXRwPudHDnC6pAxrjBPe1n78EBF8MUGSkixnLNjdoCUMjFemMn5NjUGacnboqPVkdOC+Vpgus2q8IKCN+T+suWENwxyWJXKXMyQ5WNVJ+aBqD3e6VSYoi)
:::tip `` 仅支持单个元素或组件作为其插槽内容。如果内容是一个组件,这个组件必须仅有一个根元素。 ::: 当一个 `` 组件中的元素被插入或移除时,会发生下面这些事情: 1. Vue 会自动检测目标元素是否应用了 CSS 过渡或动画。如果是,则一些 [CSS 过渡 class](#transition-classes) 会在适当的时机被添加和移除。 2. 如果有作为监听器的 [JavaScript 钩子](#javascript-hooks),这些钩子函数会在适当时机被调用。 3. 如果没有探测到 CSS 过渡或动画、也没有提供 JavaScript 钩子,那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行。 ## 基于 CSS 的过渡效果 {#css-based-transitions} ### CSS 过渡 class {#transition-classes} 一共有 6 个应用于进入与离开过渡效果的 CSS class。 ![过渡图示](./images/transition-classes.png) 1. `v-enter-from`:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。 2. `v-enter-active`:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。 3. `v-enter-to`:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 `v-enter-from` 被移除的同时),在过渡或动画完成之后移除。 4. `v-leave-from`:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。 5. `v-leave-active`:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。 6. `v-leave-to`:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 `v-leave-from` 被移除的同时),在过渡或动画完成之后移除。 `v-enter-active` 和 `v-leave-active` 给我们提供了为进入和离开动画指定不同速度曲线的能力,我们将在下面的小节中看到一个示例。 ### 为过渡效果命名 {#named-transitions} 我们可以给 `` 组件传一个 `name` prop 来声明一个过渡效果名: ```vue-html ... ``` 对于一个有名字的过渡效果,对它起作用的过渡 class 会以其名字而不是 `v` 作为前缀。比如,上方例子中被应用的 class 将会是 `fade-enter-active` 而不是 `v-enter-active`。这个“fade”过渡的 class 应该是这样: ```css .fade-enter-active, .fade-leave-active { transition: opacity 0.5s ease; } .fade-enter-from, .fade-leave-to { opacity: 0; } ``` ### CSS 的 transition {#css-transitions} `` 一般都会搭配[原生 CSS 过渡](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions)一起使用,正如你在上面的例子中所看到的那样。这个 `transition` CSS 属性是一个简写形式,使我们可以一次定义一个过渡的各个方面,包括需要执行动画的属性、持续时间和[速度曲线](https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function)。 下面是一个更高级的例子,它使用了不同的持续时间和速度曲线来过渡多个属性: ```vue-html

hello

``` ```css /* 进入和离开动画可以使用不同 持续时间和速度曲线。 */ .slide-fade-enter-active { transition: all 0.3s ease-out; } .slide-fade-leave-active { transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1); } .slide-fade-enter-from, .slide-fade-leave-to { transform: translateX(20px); opacity: 0; } ```
[在演练场中尝试一下](https://play.vuejs.org/#eNqFkc9uwjAMxl/F6wXQKIVNk1AX0HbZC4zDDr2E4EK0NIkStxtDvPviFQ0OSFzyx/m+n+34kL16P+lazMpMRBW0J4hIrV9WVjfeBYIDBKzhCHVwDQySdFDZyipnY5Lu3BcsWDCk0OKosqLoKcmfLoSNN5KQbyTWLZGz8KKMVp+LKju573ivsuXKbbcG4d3oDcI9vMkNiqL3JD+AWAVpoyadGFY2yATW5nVSJj9rkspDl+v6hE/hHRrjRMEdpdfiDEkBUVxWaEWkveHj5AzO0RKGXCrSHcKBIfSPKEEaA9PJYwSUEXPX0nNlj8y6RBiUHd5AzCOodq1VvsYfjWE4G6fgEy/zMcxG17B9ZTyX8bV85C5y1S40ZX/kdj+GD1P/zVQA56XStC9h2idJI/z7huz4CxoVvE4=)
[在演练场中尝试一下](https://play.vuejs.org/#eNqFkc1uwjAMgF/F6wk0SmHTJNQFtF32AuOwQy+hdSFamkSJ08EQ776EbMAkJKTIf7I/O/Y+ezVm3HvMyoy52gpDi0rh1mhL0GDLvSTYVwqg4cQHw2QDWCRv1Z8H4Db6qwSyHlPkEFUQ4bHixA0OYWckJ4wesZUn0gpeainqz3mVRQzM4S7qKlss9XotEd6laBDu4Y03yIpUE+oB2NJy5QSJwFC8w0iIuXkbMkN9moUZ6HPR/uJDeINSalaYxCjOkBBgxeWEijnayWiOz+AcFaHNeU2ix7QCOiFK4FLCZPzoALnDXHt6Pq7hP0Ii7/EGYuag9itR5yv8FmgH01EIPkUxG8F0eA2bJmut7kbX+pG+6NVq28WTBTN+92PwMDHbSAXQhteCdiVMUpNwwuMassMP8kfAJQ==)
### CSS 的 animation {#css-animations} [原生 CSS 动画](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations)和 CSS transition 的应用方式基本上是相同的,只有一点不同,那就是 `*-enter-from` 不是在元素插入后立即移除,而是在一个 `animationend` 事件触发时被移除。 对于大多数的 CSS 动画,我们可以简单地在 `*-enter-active` 和 `*-leave-active` class 下声明它们。下面是一个示例: ```vue-html

Hello here is some bouncy text!

``` ```css .bounce-enter-active { animation: bounce-in 0.5s; } .bounce-leave-active { animation: bounce-in 0.5s reverse; } @keyframes bounce-in { 0% { transform: scale(0); } 50% { transform: scale(1.25); } 100% { transform: scale(1); } } ```
[在演练场中尝试一下](https://play.vuejs.org/#eNqNksGOgjAQhl9lJNmoBwRNvCAa97YP4JFLbQZsLG3TDqzG+O47BaOezCYkpfB9/0wHbsm3c4u+w6RIyiC9cgQBqXO7yqjWWU9wA4813KH2toUpo9PKVEZaExg92V/YRmBGvsN5ZcpsTGGfN4St04Iw7qg8dkTWwF5qJc/bKnnYk7hWye5gm0ZjmY0YKwDlwQsTFCnWjGiRpaPtjETG43smHPSpqh9pVQKBrjpyrfCNMilZV8Aqd5cNEF4oFVo1pgCJhtBvnjEAP6i1hRN6BBUg2BZhKHUdvMmjWhYHE9dXY/ygzN4PasqhB75djM2mQ7FUSFI9wi0GCJ6uiHYxVsFUGcgX67CpzP0lahQ9/k/kj9CjDzgG7M94rT1PLLxhQ0D+Na4AFI9QW98WEKTQOMvnLAOwDrD+wC0Xq/Ubusw/sU+QL/45hskk9z8Bddbn)
[在演练场中尝试一下](https://play.vuejs.org/#eNqNUs2OwiAQfpWxySZ66I8mXioa97YP4LEXrNNKpEBg2tUY330pqOvJmBBgyPczP1yTb2OyocekTJirrTC0qRSejbYEB2x4LwmulQI4cOLTWbwDWKTeqkcE4I76twSyPcaX23j4zS+WP3V9QNgZyQnHiNi+J9IKtrUU9WldJaMMrGEynlWy2em2lcjyCPMUALazXDlBwtMU79CT9rpXNXp4tGYGhlQ0d7UqAUcXOeI6bluhUtKmhEVhzisgPFPKpWhVCTUqQrt6ygD8oJQajmgRhAOnO4RgdQm8yd0tNzGv/D8x/8Dy10IVCzn4axaTTYNZymsSA8YuciU6PrLL6IKpUFBkS7cKXXwQJfIBPyP6IQ1oHUaB7QkvjfUdcy+wIFB8PeZIYwmNtl0JruYSp8XMk+/TXL7BzbPF8gU6L95hn8D4OUJnktsfM1vavg==)
### 自定义过渡 class {#custom-transition-classes} 你也可以向 `` 传递以下的 props 来指定自定义的过渡 class: - `enter-from-class` - `enter-active-class` - `enter-to-class` - `leave-from-class` - `leave-active-class` - `leave-to-class` 你传入的这些 class 会覆盖相应阶段的默认 class 名。这个功能在你想要在 Vue 的动画机制下集成其他的第三方 CSS 动画库时非常有用,比如 [Animate.css](https://daneden.github.io/animate.css/): ```vue-html

hello

```
[在演练场中尝试一下](https://play.vuejs.org/#eNqNUctuwjAQ/BXXF9oDsZB6ogbRL6hUcbSEjLMhpn7JXtNWiH/vhqS0R3zxPmbWM+szf02pOVXgSy6LyTYhK4A1rVWwPsWM7MwydOzCuhw9mxF0poIKJoZC0D5+stUAeMRc4UkFKcYpxKcEwSenEYYM5b4ixsA2xlnzsVJ8Yj8Mt+LrbTwcHEgxwojCmNxmHYpFG2kaoxO0B2KaWjD6uXG6FCiKj00ICHmuDdoTjD2CavJBCna7KWjZrYK61b9cB5pI93P3sQYDbxXf7aHHccpVMolO7DS33WSQjPXgXJRi2Cl1xZ8nKkjxf0dBFvx2Q7iZtq94j5jKUgjThmNpjIu17ZzO0JjohT7qL+HsvohJWWNKEc/NolncKt6Goar4y/V7rg/wyw9zrLOy)
[在演练场中尝试一下](https://play.vuejs.org/#eNqNUcFuwjAM/RUvp+1Ao0k7sYDYF0yaOFZCJjU0LE2ixGFMiH9f2gDbcVKU2M9+tl98Fm8hNMdMYi5U0tEEXraOTsFHho52mC3DuXUAHTI+PlUbIBLn6G4eQOr91xw4ZqrIZXzKVY6S97rFYRqCRabRY7XNzN7BSlujPxetGMvAAh7GtxXLtd/vLSlZ0woFQK0jumTY+FJt7ORwoMLUObEfZtpiSpRaUYPkmOIMNZsj1VhJRWeGMsFmczU6uCOMHd64lrCQ/s/d+uw0vWf+MPuea5Vp5DJ0gOPM7K4Ci7CerPVKhipJ/moqgJJ//8ipxN92NFdmmLbSip45pLmUunOH1Gjrc7ezGKnRfpB4wJO0ZpvkdbJGpyRfmufm+Y4Mxo1oK16n9UwNxOUHwaK3iQ==)
### 同时使用 transition 和 animation {#using-transitions-and-animations-together} Vue 需要附加事件监听器,以便知道过渡何时结束。可以是 `transitionend` 或 `animationend`,这取决于你所应用的 CSS 规则。如果你仅仅使用二者的其中之一,Vue 可以自动探测到正确的类型。 然而在某些场景中,你或许想要在同一个元素上同时使用它们两个。举例来说,Vue 触发了一个 CSS 动画,同时鼠标悬停触发另一个 CSS 过渡。此时你需要显式地传入 `type` prop 来声明,告诉 Vue 需要关心哪种类型,传入的值是 `animation` 或 `transition`: ```vue-html ... ``` ### 深层级过渡与显式过渡时长 {#nested-transitions-and-explicit-transition-durations} 尽管过渡 class 仅能应用在 `` 的直接子元素上,我们还是可以使用深层级的 CSS 选择器,在深层级的元素上触发过渡效果: ```vue-html
Hello
``` ```css /* 应用于嵌套元素的规则 */ .nested-enter-active .inner, .nested-leave-active .inner { transition: all 0.3s ease-in-out; } .nested-enter-from .inner, .nested-leave-to .inner { transform: translateX(30px); opacity: 0; } /* ... 省略了其他必要的 CSS */ ``` 我们甚至可以在深层元素上添加一个过渡延迟,从而创建一个带渐进延迟的动画序列: ```css{3} /* 延迟嵌套元素的进入以获得交错效果 */ .nested-enter-active .inner { transition-delay: 0.25s; } ``` 然而,这会带来一个小问题。默认情况下,`` 组件会通过监听过渡根元素上的**第一个** `transitionend` 或者 `animationend` 事件来尝试自动判断过渡何时结束。而在嵌套的过渡中,期望的行为应该是等待所有内部元素的过渡完成。 在这种情况下,你可以通过向 `` 组件传入 `duration` prop 来显式指定过渡的持续时间 (以毫秒为单位)。总持续时间应该匹配延迟加上内部元素的过渡持续时间: ```vue-html ... ``` [在演练场中尝试一下](https://play.vuejs.org/#eNqVVd9v0zAQ/leO8LAfrE3HNKSFbgKmSYMHQNAHkPLiOtfEm2NHttN2mvq/c7bTNi1jgFop9t13d9995ziPyfumGc5bTLJkbLkRjQOLrm2uciXqRhsHj2BwBiuYGV3DAUEPcpUrrpUlaKUXcOkBh860eJSrcRqzUDxtHNaNZA5pBzCets5pBe+4FPz+Mk+66Bf+mSdXE12WEsdphMWQiWHKCicoLCtaw/yKIs/PR3kCitVIG4XWYUEJfATFFGIO84GYdRUIyCWzlra6dWg2wA66dgqlts7c+d8tSqk34JTQ6xqb9TjdUiTDOO21TFvrHqRfDkPpExiGKvBITjdl/L40ulVFBi8R8a3P17CiEKrM4GzULIOlFmpQoSgrl8HpKFpX3kFZu2y0BNhJxznvwaJCA1TEYcC4E3MkKp1VIptjZ43E3KajDJiUMBqeWUBmcUBUqJGYOT2GAiV7gJAA9Iy4GyoBKLH2z+N0W3q/CMC2yCCkyajM63Mbc+9z9mfvZD+b071MM23qLC69+j8PvX5HQUDdMC6cL7BOTtQXCJwpas/qHhWIBdYtWGgtDWNttWTmThu701pf1W6+v1Hd8Xbz+k+VQxmv8i7Fv1HZn+g/iv2nRkjzbd6npf/Rkz49DifQ3dLZBBYOJzC4rqgCwsUbmLYlCAUVU4XsCd1NrCeRHcYXb1IJC/RX2hEYCwJTvHYVMZoavbBI09FmU+LiFSzIh0AIXy1mqZiFKaKCmVhiEVJ7GftHZTganUZ56EYLL3FykjhL195MlMM7qxXdmEGDPOG6boRE86UJVPMki+p4H01WLz4Fm78hSdBo5xXy+yfsd3bpbXny1SA1M8c82fgcMyW66L75/hmXtN44a120ktDPOL+h1bL1HCPsA42DaPdwge3HcO/TOCb2ZumQJtA15Yl65Crg84S+BdfPtL6lezY8C3GkZ7L6Bc1zNR0=) 如果有必要的话,你也可以用对象的形式传入,分开指定进入和离开所需的时间: ```vue-html ... ``` ### 性能考量 {#performance-considerations} 你可能注意到我们上面例子中展示的动画所用到的 CSS 属性大多是 `transform` 和 `opacity` 之类的。用这些属性制作动画非常高效,因为: 1. 他们在动画过程中不会影响到 DOM 结构,因此不会每一帧都触发昂贵的 CSS 布局重新计算。 2. 大多数的现代浏览器都可以在执行 `transform` 动画时利用 GPU 进行硬件加速。 相比之下,像 `height` 或者 `margin` 这样的属性会触发 CSS 布局变动,因此执行它们的动画效果更昂贵,需要谨慎使用。 ## JavaScript 钩子 {#javascript-hooks} 你可以通过监听 `` 组件事件的方式在过渡过程中挂上钩子函数: ```vue-html ```
```js // 在元素被插入到 DOM 之前被调用 // 用这个来设置元素的 "enter-from" 状态 function onBeforeEnter(el) {} // 在元素被插入到 DOM 之后的下一帧被调用 // 用这个来开始进入动画 function onEnter(el, done) { // 调用回调函数 done 表示过渡结束 // 如果与 CSS 结合使用,则这个回调是可选参数 done() } // 当进入过渡完成时调用。 function onAfterEnter(el) {} // 当进入过渡在完成之前被取消时调用 function onEnterCancelled(el) {} // 在 leave 钩子之前调用 // 大多数时候,你应该只会用到 leave 钩子 function onBeforeLeave(el) {} // 在离开过渡开始时调用 // 用这个来开始离开动画 function onLeave(el, done) { // 调用回调函数 done 表示过渡结束 // 如果与 CSS 结合使用,则这个回调是可选参数 done() } // 在离开过渡完成、 // 且元素已从 DOM 中移除时调用 function onAfterLeave(el) {} // 仅在 v-show 过渡中可用 function onLeaveCancelled(el) {} ```
```js export default { // ... methods: { // 在元素被插入到 DOM 之前被调用 // 用这个来设置元素的 "enter-from" 状态 onBeforeEnter(el) {}, // 在元素被插入到 DOM 之后的下一帧被调用 // 用这个来开始进入动画 onEnter(el, done) { // 调用回调函数 done 表示过渡结束 // 如果与 CSS 结合使用,则这个回调是可选参数 done() }, // 当进入过渡完成时调用。 onAfterEnter(el) {}, // 当进入过渡在完成之前被取消时调用 onEnterCancelled(el) {}, // 在 leave 钩子之前调用 // 大多数时候,你应该只会用到 leave 钩子 onBeforeLeave(el) {}, // 在离开过渡开始时调用 // 用这个来开始离开动画 onLeave(el, done) { // 调用回调函数 done 表示过渡结束 // 如果与 CSS 结合使用,则这个回调是可选参数 done() }, // 在离开过渡完成、 // 且元素已从 DOM 中移除时调用 onAfterLeave(el) {}, // 仅在 v-show 过渡中可用 onLeaveCancelled(el) {} } } ```
这些钩子可以与 CSS 过渡或动画结合使用,也可以单独使用。 在使用仅由 JavaScript 执行的动画时,最好是添加一个 `:css="false"` prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡效果: ```vue-html{3} ... ``` 在有了 `:css="false"` 后,我们就自己全权负责控制什么时候过渡结束了。这种情况下对于 `@enter` 和 `@leave` 钩子来说,回调函数 `done` 就是必须的。否则,钩子将被同步调用,过渡将立即完成。 这里是使用 [GSAP 库](https://gsap.com/)执行动画的一个示例,你也可以使用任何你想要的库,比如 [Anime.js](https://animejs.com/) 或者 [Motion One](https://motion.dev/):
[在演练场中尝试一下](https://play.vuejs.org/#eNqNVMtu2zAQ/JUti8I2YD3i1GigKmnaorcCveTQArpQFCWzlkiCpBwHhv+9Sz1qKYckJ3FnlzvD2YVO5KvW4aHlJCGpZUZoB5a7Vt9lUjRaGQcnMLyEM5RGNbDA0sX/VGWpHnB/xEQmmZIWe+zUI9z6m0tnWr7ymbKVzAklQclvvFSG/5COmyWvV3DKJHTdQiRHZN0jAJbRmv9OIA432/UE+jODlKZMuKcErnx8RrazP8woR7I1FEryKaVTU8aiNdRfwWZTQtQwi1HAGF/YB4BTyxNY8JpaJ1go5K/WLTfhdg1Xq8V4SX5Xja65w0ovaCJ8Jvsnpwc+l525F2XH4ac3Cj8mcB3HbxE9qnvFMRzJ0K3APuhIjPefmTTyvWBAGvWbiDuIgeNYRh3HCCDNW+fQmHtWC7a/zciwaO/8NyN3D6qqap5GfVnXAC89GCqt8Bp77vu827+A+53AJrOFzMhQdMnO8dqPpMO74Yx4wqxFtKS1HbBOMdIX4gAMffVp71+Qq2NG4BCIcngBKk8jLOvfGF30IpBGEwcwtO6p9sdwbNXPIadsXxnVyiKB9x83+c3N9WePN9RUQgZO6QQ2sT524KMo3M5Pf4h3XFQ7NwFyZQpuAkML0doEtvEHhPvRDPRkTfq/QNDgRvy1SuIvpFOSDQmbkWTckf7hHsjIzjltkyhqpd5XIVNN5HNfGlW09eAcMp3J+R+pEn7L)
[在演练场中尝试一下](https://play.vuejs.org/#eNqNVFFvmzAQ/is3pimNlABNF61iaddt2tukvfRhk/xiwIAXsJF9pKmq/PedDTSwh7ZSFLjvzvd9/nz4KfjatuGhE0ES7GxmZIu3TMmm1QahtLyFwugGFu51wRQAU+Lok7koeFcjPDk058gvlv07gBHYGTVGALbSDwmg6USPnNzjtHL/jcBK5zZxxQwZavVNFNqIHwqF8RUAWs2jn4IffCfqQz+mik5lKLWi3GT1hagHRU58aAUSshpV2YzX4ncCcbjZDp099GcG6ZZnEh8TuPR8S0/oTJhQjmQryLUSU0rUU8a8M9wtoWZTQtIwi0nAGJ/ZB0BwKxJYiJpblFko1a8OLzbhdgWXy8WzP99109YCqdIJmgifyfYuzmUzfFF2HH56o/BjAldx/BbRo7pXHKMjGbrl1IcciWn9fyaNfC8YsIueR5wCFFTGUVAEsEs7pOmDu6yW2f6GBW5o4QbeuScLbu91WdZiF/VlvgEtujdcWek09tx3qZ+/tXAzQU1mA8mCoeicneO1OxKP9yM+4ElmLaEFr+2AecVEn8sDZOSrSzv/1qk+sgAOa1kMOyDlu4jK+j1GZ70E7KKJAxRafKzdazi26s8h5dm+NLpTeQLvP27S6+urz/7T5aaUao26TWATt0cPPsgcK3f6Q1wJWVY4AVJtcmHWhueyo89+G38guD+agT5YBf39s25oIv5arehu8krYkLAs8BeG86DfuANYUCG2NomiTrX7Msx0E7ncl0bnXT04566M4PQPykWaWw==)
## 可复用过渡效果 {#reusable-transitions} 得益于 Vue 的组件系统,过渡效果是可以被封装复用的。要创建一个可被复用的过渡,我们需要为 `` 组件创建一个包装组件,并向内传入插槽内容: ```vue{5} ``` 现在 `MyTransition` 可以在导入后像内置组件那样使用了: ```vue-html
Hello
``` ## 出现时过渡 {#transition-on-appear} 如果你想在某个节点初次渲染时应用一个过渡效果,你可以添加 `appear` prop: ```vue-html ... ``` ## 元素间过渡 {#transition-between-elements} 除了通过 `v-if` / `v-show` 切换一个元素,我们也可以通过 `v-if` / `v-else` / `v-else-if` 在几个组件间进行切换,只要确保任一时刻只会有一个元素被渲染即可: ```vue-html ``` [在演练场中尝试一下](https://play.vuejs.org/#eNqdk8tu2zAQRX9loI0SoLLcFN2ostEi6BekmwLa0NTYJkKRBDkSYhj+9wxJO3ZegBGu+Lhz7syQ3Bd/nJtNIxZN0QbplSMISKNbdkYNznqCPXhcwwHW3g5QsrTsTGekNYGgt/KBBCEsouimDGLCvrztTFtnGGN4QTg4zbK4ojY4YSDQTuOiKwbhN8pUXm221MDd3D11xfJeK/kIZEHupEagrbfjZssxzAgNs5nALIC2VxNILUJg1IpMxWmRUAY9U6IZ2/3zwgRFyhowYoieQaseq9ElDaTRrkYiVkyVWrPiXNdiAcequuIkPo3fMub5Sg4l9oqSevmXZ22dwR8YoQ74kdsL4Go7ZTbR74HT/KJfJlxleGrG8l4YifqNYVuf251vqOYr4llbXz4C06b75+ns1a3BPsb0KrBy14Aymnerlbby8Vc8cTajG35uzFITpu0t5ufzHQdeH6LBsezEO0eJVbB6pBiVVLPTU6jQEPpKyMj8dnmgkQs+HmQcvVTIQK1hPrv7GQAFt9eO9Bk6fZ8Ub52Qiri8eUo+4dbWD02exh79v/nBP+H2PStnwz/jelJ1geKvk/peHJ4BoRZYow==) ## 过渡模式 {#transition-modes} 在之前的例子中,进入和离开的元素都是在同时开始动画的,因此我们不得不将它们设为 `position: absolute` 以避免二者同时存在时出现的布局问题。 然而,很多情况下这可能并不符合需求。我们可能想要先执行离开动画,然后在其完成**之后**再执行元素的进入动画。手动编排这样的动画是非常复杂的,好在我们可以通过向 `` 传入一个 `mode` prop 来实现这个行为: ```vue-html ... ``` 将之前的例子改为 `mode="out-in"` 后是这样: `` 也支持 `mode="in-out"`,虽然这并不常用。 ## 组件间过渡 {#transition-between-components} `` 也可以作用于[动态组件](/guide/essentials/component-basics#dynamic-components)之间的切换: ```vue-html ```
[在演练场中尝试一下](https://play.vuejs.org/#eNqtksFugzAMhl/F4tJNKtDLLoxWKnuDacdcUnC3SCGJiMmEqr77EkgLbXfYYZyI8/v77dinZG9M5npMiqS0dScMgUXqzY4p0RrdEZzAfnEp9fc7HuEMx063sPIZq6viTbdmHy+yfDwF5K2guhFUUcBUnkNvcelBGrjTooHaC7VCRXBAoT6hQTRyAH2w2DlsmKq1sgS8JuEwUCfxdgF7Gqt5ZqrMp+58X/5A2BrJCcOJSskPKP0v+K8UyvQENBjcsqTjjdAsAZe2ukHpI3dm/q5wXPZBPFqxZAf7gCrzGfufDlVwqB4cPjqurCChFSjeBvGRN+iTA9afdE+pUD43FjG/bSHsb667Mr9qJot89vCBMl8+oiotDTL8ZsE39UnYpRN0fQlK5A5jEE6BSVdiAdrwWtAAm+zFAnKLr0ydA3pJDDt0x/PrMrJifgGbKdFPfCwpWU+TuWz5omzfVCNcfJJ5geL8pqtFn5E07u7fSHFOj6TzDyUDNEM=)
[在演练场中尝试一下](https://play.vuejs.org/#eNqtks9ugzAMxl/F4tJNamGXXVhWqewVduSSgStFCkkUDFpV9d0XJyn9t8MOkxBg5/Pvi+Mci51z5TxhURdi7LxytG2NGpz1BB92cDvYezvAqqxixNLVjaC5ETRZ0Br8jpIe93LSBMfWAHRBYQ0aGms4Jvw6Q05rFvSS5NNzEgN4pMmbcwQgO1Izsj5CalhFRLDj1RN/wis8olpaCQHh4LQk5IiEll+owy+XCGXcREAHh+9t4WWvbFvAvBlsjzpk7gx5TeqJtdG4LbawY5KoLtR/NGjYoHkw+PTSjIqUNWDkwOK97DHUMjVEdqKNMqE272E5dajV+JvpVlSLJllUF4+QENX1ERox0kHzb8m+m1CEfpOgYYgpqVHOmJNpgLQQa7BOdooO8FK+joByxLc4tlsiX6s7HtnEyvU1vKTCMO+4pWKdBnO+0FfbDk31as5HsvR+Hl9auuozk+J1/hspz+mRdPoBYtonzg==)
## 动态过渡 {#dynamic-transitions} `` 的 props (比如 `name`) 也可以是动态的!这让我们可以根据状态变化动态地应用不同类型的过渡: ```vue-html ``` 这个特性的用处是可以提前定义好多组 CSS 过渡或动画的 class,然后在它们之间动态切换。 你也可以根据你的组件的当前状态在 JavaScript 过渡钩子中应用不同的行为。最后,创建动态过渡的终极方式还是创建[可复用的过渡组件](#reusable-transitions),并让这些组件根据动态的 props 来改变过渡的效果。掌握了这些技巧后,就真的只有你想不到,没有做不到的了。 ## 使用 Key Attribute 过渡 {#transitions-with-the-key-attribute} 有时为了触发过渡,你需要强制重新渲染 DOM 元素。 以计数器组件为例:
```vue ```
```vue ```
如果不使用 `key` attribute,则只有文本节点会被更新,因此不会发生过渡。但是,有了 `key` 属性,Vue 就知道在 `count` 改变时创建一个新的 `span` 元素,因此 `Transition` 组件有两个不同的元素在它们之间进行过渡。
[在演练场中尝试一下](https://play.vuejs.org/#eNp9UsFu2zAM/RVCl6Zo4nhYd/GcAtvQQ3fYhq1HXTSFydTKkiDJbjLD/z5KMrKgLXoTHx/5+CiO7JNz1dAja1gbpFcuQsDYuxtuVOesjzCCxx1MsPO2gwuiXnzkhhtpTYggbW8ibBJlUV/mBJXfmYh+EHqxuITNDYzcQGFWBPZ4dUXEaQnv6jrXtOuiTJoUROycFhEpAmi3agCpRQgbzp68cA49ZyV174UJKiprckxIcMJA84hHImc9oo7jPOQ0kQ4RSvH6WXW7JiV6teszfQpDPGqEIK3DLSGpQbazsyaugvqLDVx77JIhbqp5wsxwtrRvPFI7NWDhEGtYYVrQSsgELzOiUQw4I2Vh8TRgA9YJqeIR6upDABQh9TpTAPE7WN3HlxLp084Foi3N54YN1KWEVpOMkkO2ZJHsmp3aVw/BGjqMXJE22jml0X93STRw1pReKSe0tk9fMxZ9nzwVXP5B+fgK/hAOCePsh8dAt4KcnXJR+D3S16X07a9veKD3KdnZba+J/UbyJ+Zl0IyF9rk3Wxr7jJenvcvnrcz+PtweItKuZ1Np0MScMp8zOvkvb1j/P+776jrX0UbZ9A+fYSTP)
[在演练场中尝试一下](https://play.vuejs.org/#eNp9U8tu2zAQ/JUFTwkSyw6aXlQ7QB85pIe2aHPUhZHWDhOKJMiVYtfwv3dJSpbbBgEMWJydndkdUXvx0bmi71CUYhlqrxzdVAa3znqCBtey0wT7ygA0kuTZeX4G8EidN+MJoLadoRKuLkdAGULfS12C6bSGDB/i3yFx2tiAzaRIjyoUYxesICDdDaczZq1uJrNETY4XFx8G5Uu4WiwW55PBA66txy8YyNvdZFNrlP4o/Jdpbq4M/5bzYxZ8IGydloR8Alg2qmcVGcKqEi9eOoe+EqnExXsvTVCkrBkQxoKTBspn3HFDmprp+32ODA4H9mLCKDD/R2E5Zz9+Ws5PpuBjoJ1GCLV12DASJdKGa2toFtRvLOHaY8vx8DrFMGdiOJvlS48sp3rMHGb1M4xRzGQdYU6REY6rxwHJGdJxwBKsk7WiHSyK9wFQhqh14gDyIVjd0f8Wa2/bUwOyWXwQLGGRWzicuChvKC4F8bpmrTbFU7CGL2zqiJm2Tmn03100DZUox5ddCam1ffmaMPJd3Cnj9SPWz6/gT2EbsUr88Bj4VmAljjWSfoP88mL59tc33PLzsdjaptPMfqP4E1MYPGOmfepMw2Of8NK0d238+JTZ3IfbLSFnPSwVB53udyX4q/38xurTuO+K6/Fqi8MffqhR/A==)
--- **参考** - [`` API 参考](/api/built-in-components#transition) --- --- url: /guide/built-ins/transition-group.md --- # TransitionGroup {#transitiongroup} `` 是一个内置组件,用于对 `v-for` 列表中的元素或组件的插入、移除和顺序改变添加动画效果。 ## 和 `` 的区别 {#differences-from-transition} `` 支持和 `` 基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器,但有以下几点区别: - 默认情况下,它不会渲染一个容器元素。但你可以通过传入 `tag` prop 来指定一个元素作为容器元素来渲染。 - [过渡模式](./transition#transition-modes)在这里不可用,因为我们不再是在互斥的元素之间进行切换。 - 列表中的每个元素都**必须**有一个独一无二的 `key` attribute。 - CSS 过渡 class 会被应用在列表内的元素上,**而不是**容器元素上。 :::tip 当在 [DOM 内模板](/guide/essentials/component-basics#in-dom-template-parsing-caveats)中使用时,组件名需要写为 ``。 ::: ## 进入 / 离开动画 {#enter-leave-transitions} 这里是 `` 对一个 `v-for` 列表添加进入 / 离开动画的示例: ```vue-html
  • {{ item }}
  • ``` ```css .list-enter-active, .list-leave-active { transition: all 0.5s ease; } .list-enter-from, .list-leave-to { opacity: 0; transform: translateX(30px); } ``` ## 移动动画 {#move-transitions} 上面的示例有一些明显的缺陷:当某一项被插入或移除时,它周围的元素会立即发生“跳跃”而不是平稳地移动。我们可以通过添加一些额外的 CSS 规则来解决这个问题: ```css{1,13-17} .list-move, /* 对移动中的元素应用的过渡 */ .list-enter-active, .list-leave-active { transition: all 0.5s ease; } .list-enter-from, .list-leave-to { opacity: 0; transform: translateX(30px); } /* 确保将离开的元素从布局流中删除 以便能够正确地计算移动的动画。 */ .list-leave-active { position: absolute; } ``` 现在它看起来好多了,甚至对整个列表执行洗牌的动画也都非常流畅: [完整的示例](/examples/#list-transition) ### 自定义过渡组 class {#custom-transitiongroup-classes} 你还可以通过向 `` 传递 `moveClass` prop 为移动元素指定自定义过渡 class,类似于[自定义过渡 class](/guide/built-ins/transition.html#custom-transition-classes)。 ## 渐进延迟列表动画 {#staggering-list-transitions} 通过在 JavaScript 钩子中读取元素的 data attribute,我们可以实现带渐进延迟的列表动画。首先,我们把每一个元素的索引渲染为该元素上的一个 data attribute: ```vue-html{11}
  • {{ item.msg }}
  • ``` 接着,在 JavaScript 钩子中,我们基于当前元素的 data attribute 对该元素的进场动画添加一个延迟。以下是一个基于 [GSAP library](https://gsap.com/) 的动画示例: ```js{5} function onEnter(el, done) { gsap.to(el, { opacity: 1, height: '1.6em', delay: el.dataset.index * 0.15, onComplete: done }) } ```
    [在演练场中查看完整示例](https://play.vuejs.org/#eNqlVMuu0zAQ/ZVRNklRm7QLWETtBW4FSFCxYkdYmGSSmjp28KNQVfl3xk7SFyvEponPGc+cOTPNOXrbdenRYZRHa1Nq3lkwaF33VEjedkpbOIPGeg6lajtnsYIeaq1aiOlSfAlqDOtG3L8SUchSSWNBcPrZwNdCAqVqTZND/KxdibBDjKGf3xIfWXngCNs9k4/Udu/KA3xWWnPz1zW0sOOP6CcnG3jv9ImIQn67SvrpUJ9IE/WVxPHsSkw97gbN0zFJZrB5grNPrskcLUNXac2FRZ0k3GIbIvxLSsVTq3bqF+otM5jMUi5L4So0SSicHplwOKOyfShdO1lariQo+Yy10vhO+qwoZkNFFKmxJ4Gp6ljJrRe+vMP3yJu910swNXqXcco1h0pJHDP6CZHEAAcAYMydwypYCDAkJRdX6Sts4xGtUDAKotIVs9Scpd4q/A0vYJmuXo5BSm7JOIEW81DVo77VR207ZEf8F23LB23T+X9VrbNh82nn6UAz7ASzSCeANZe0AnBctIqqbIoojLCIIBvoL5pJw31DH7Ry3VDKsoYinSii4ZyXxhBQM2Fwwt58D7NeoB8QkXfDvwRd2XtceOsCHkwc8KCINAk+vADJppQUFjZ0DsGVGT3uFn1KSjoPeKLoaYtvCO/rIlz3vH9O5FiU/nXny/pDT6YGKZngg0/Zg1GErrMbp6N5NHxJFi3N/4dRkj5IYf5ULxCmiPJpI4rIr4kHimhvbWfyLHOyOzQpNZZ57jXNy4nRGFLTR/0fWBqe7w==)
    [在演练场中查看完整示例](https://play.vuejs.org/#eNqtVE2P0zAQ/SujXNqgNmkPcIjaBbYCJKg4cSMcTDJNTB07+KNsVfW/M3aabNpyQltViT1vPPP8Zian6H3bJgeHURatTKF5ax9yyZtWaQuVYS3stGpg4peTXOayUNJYEJwea/ieS4ATNKbKYPKoXYGwRZzAeTYGPrNizxE2NZO30KZ2xR6+Kq25uTuGFrb81vrFyQo+On0kIJc/PCV8CmxL3DEnLJy8e8ksm8bdGkCjdVr2O4DfDvWRgtGN/JYC0SOkKVTTOotl1jv3hi3d+DngENILkey4sKinU26xiWH9AH6REN/Eqq36g3rDDE7jhMtCuBLN1NbcJIFEHN9RaNDWqjQDAyUfcac0fpA+CYoRCRSJsUeBiWpZwe2RSrK4w2rkVe2rdYG6LD5uH3EGpZI4iuurTdwDNBjpRJclg+UlhP914UnMZfIGm8kIKVEwciYivhoGLQlQ4hO8gkWyfD1yVHJDKgu0mAUmPXLuxRkYb5Ed8H8YL/7BeGx7Oa6hkLmk/yodBoo21BKtYBZpB7DikroKDvNGUeZ1HoVmyCNIO/ibZtJwy5X8pJVru9CWVeTpRB51+6wwhgw7Jgz2tnc/Q6/M0ZeWwKvmGZye0Wu78PIGexC6swdGxEnw/q6HOYUkt9DwMwhKxfS6GpY+KPHc45G8+6EYAV7reTjucf/uwUtSmvvTME1wDuISlVTwTqf0RiiyrtKR0tEs6r5l84b645dRkr5zoT8oXwBMHg2Tlke+jbwhj2prW5OlqZPtvkroYqnH3lK9nLgI46scnf8Cn22kBA==)
    --- **参考** - [`` API 参考](/api/built-in-components#transitiongroup) --- --- url: /guide/typescript/composition-api.md --- # TypeScript 与组合式 API {#typescript-with-composition-api} > 这一章假设你已经阅读了[搭配 TypeScript 使用 Vue](./overview) 的概览。 ## 为组件的 props 标注类型 {#typing-component-props} ### 使用 ` ``` 这被称之为“运行时声明”,因为传递给 `defineProps()` 的参数会作为运行时的 `props` 选项使用。 然而,通过泛型参数来定义 props 的类型通常更直接: ```vue ``` 这被称之为“基于类型的声明”。编译器会尽可能地尝试根据类型参数推导出等价的运行时选项。在这种场景下,我们第二个例子中编译出的运行时选项和第一个是完全一致的。 基于类型的声明或者运行时声明可以择一使用,但是不能同时使用。 我们也可以将 props 的类型移入一个单独的接口中: ```vue ``` 这同样适用于 `Props` 从另一个源文件中导入的情况。该功能要求 TypeScript 作为 Vue 的一个 peer dependency。 ```vue ``` #### 语法限制 {#syntax-limitations} 在 3.2 及以下版本中,`defineProps()` 的泛型类型参数仅限于类型字面量或对本地接口的引用。 这个限制在 3.3 中得到了解决。最新版本的 Vue 支持在类型参数位置引用导入和有限的复杂类型。但是,由于类型到运行时转换仍然基于 AST,一些需要实际类型分析的复杂类型,例如条件类型,还未支持。你可以使用条件类型来指定单个 prop 的类型,但不能用于整个 props 对象的类型。 ### Props 解构默认值 {#props-default-values} 当使用基于类型的声明时,我们失去了为 props 声明默认值的能力。可以通过使用[响应式 Props 解构](/guide/components/props#reactive-props-destructure)解决这个问题。 : ```ts interface Props { msg?: string labels?: string[] } const { msg = 'hello', labels = ['one', 'two'] } = defineProps() ``` 在 3.4 及更低版本,响应式 Props 解构不会被默认启用。另一种选择是使用 `withDefaults` 编译器宏: ```ts interface Props { msg?: string labels?: string[] } const props = withDefaults(defineProps(), { msg: 'hello', labels: () => ['one', 'two'] }) ``` 这将被编译为等效的运行时 props `default` 选项。此外,`withDefaults` 帮助程序为默认值提供类型检查,并确保返回的 props 类型删除了已声明默认值的属性的可选标志。 :::info 请注意,在使用 `withDefaults` 时,默认值的可变引用类型 (如数组或对象) 应该在函数中进行包装,以避免意外修改和外部副作用。这样可以确保每个组件实例都会获得自己默认值的副本。当使用解构时,这**不**是必要的。 ::: ### 非 ` ``` 对于运行时声明,我们可以使用 `PropType` 工具类型: ```ts import type { PropType } from 'vue' const props = defineProps({ book: Object as PropType }) ``` 其工作方式与直接指定 `props` 选项基本相同: ```ts import { defineComponent } from 'vue' import type { PropType } from 'vue' export default defineComponent({ props: { book: Object as PropType } }) ``` `props` 选项通常用于 Options API,因此你会在[选项式 API 与 TypeScript](/guide/typescript/options-api#typing-component-props) 指南中找到更详细的例子。这些例子中展示的技术也适用于使用 `defineProps()` 的运行时声明。 ## 为组件的 emits 标注类型 {#typing-component-emits} 在 ` ``` 类型参数可以是以下的一种: 1. 一个可调用的函数类型,但是写作一个包含[调用签名](https://www.typescriptlang.org/docs/handbook/2/functions.html#call-signatures)的类型字面量。它将被用作返回的 `emit` 函数的类型。 2. 一个类型字面量,其中键是事件名称,值是数组或元组类型,表示事件的附加接受参数。上面的示例使用了具名元组,因此每个参数都可以有一个显式的名称。 我们可以看到,基于类型的声明使我们可以对所触发事件的类型进行更细粒度的控制。 若没有使用 ` ``` 没有类型标注时,这个 `event` 参数会隐式地标注为 `any` 类型。这也会在 `tsconfig.json` 中配置了 `"strict": true` 或 `"noImplicitAny": true` 时报出一个 TS 错误。因此,建议显式地为事件处理函数的参数标注类型。此外,你在访问 `event` 上的属性时可能需要使用类型断言: ```ts function handleChange(event: Event) { console.log((event.target as HTMLInputElement).value) } ``` ## 为 provide / inject 标注类型 {#typing-provide-inject} provide 和 inject 通常会在不同的组件中运行。要正确地为注入的值标记类型,Vue 提供了一个 `InjectionKey` 接口,它是一个继承自 `Symbol` 的泛型类型,可以用来在提供者和消费者之间同步注入值的类型: ```ts import { provide, inject } from 'vue' import type { InjectionKey } from 'vue' const key = Symbol() as InjectionKey provide(key, 'foo') // 若提供的是非字符串值会导致错误 const foo = inject(key) // foo 的类型:string | undefined ``` 建议将注入 key 的类型放在一个单独的文件中,这样它就可以被多个组件导入。 当使用字符串注入 key 时,注入值的类型是 `unknown`,需要通过泛型参数显式声明: ```ts const foo = inject('foo') // 类型:string | undefined ``` 注意注入的值仍然可以是 `undefined`,因为无法保证提供者一定会在运行时 provide 这个值。 当提供了一个默认值后,这个 `undefined` 类型就可以被移除: ```ts const foo = inject('foo', 'bar') // 类型:string ``` 如果你确定该值将始终被提供,则还可以强制转换该值: ```ts const foo = inject('foo') as string ``` ## 为模板引用标注类型 {#typing-template-refs} 在 Vue 3.5 和 @vue/language-tools 2.1 (为 IDE 语言服务和 vue-tsc 提供支持) 中,在单文件组件中由 `useTemplateRef()` 创建的 ref 类型可以基于匹配的 ref attribute 所在的元素**自动推断**为静态类型。 在无法自动推断的情况下,仍然可以通过泛型参数将模板 ref 转换为显式类型。 ```ts const el = useTemplateRef('el') ```
    3.5 前的用法 模板引用需要通过一个显式指定的泛型参数和一个初始值 `null` 来创建: ```vue ```
    可以通过类似于 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input#technical_summary) 的页面来获取正确的 DOM 接口。 注意为了严格的类型安全,有必要在访问 `el.value` 时使用可选链或类型守卫。这是因为直到组件被挂载前,这个 ref 的值都是初始的 `null`,并且在由于 `v-if` 的行为将引用的元素卸载时也可以被设置为 `null`。 ## 为组件模板引用标注类型 {#typing-component-template-refs} 在 Vue 3.5 和 @vue/language-tools 2.1 (为 IDE 语言服务和 vue-tsc 提供支持) 中,在单文件组件中由 `useTemplateRef()` 创建的 ref 类型可以基于匹配的 ref attribute 所在的元素**自动推断**为静态类型。 在无法自动推断的情况下 (如非单文件组件使用或动态组件),仍然可以通过泛型参数将模板 ref 强制转换为显式类型。 为了获取导入组件的实例类型,我们需要先通过 `typeof` 获取其类型,然后使用 TypeScript 的内置 `InstanceType` 工具提取其实例类型: ```vue{5} ``` 如果组件的具体类型无法获得,或者你并不关心组件的具体类型,那么可以使用 `ComponentPublicInstance`。这只会包含所有组件都共享的属性,比如 `$el`。 ```ts import { useTemplateRef } from 'vue' import type { ComponentPublicInstance } from 'vue' const child = useTemplateRef('child') ``` 如果引用的组件是一个[泛型组件](/guide/typescript/overview.html#generic-components),例如 `MyGenericModal`: ```vue ``` 则需要使用 [`vue-component-type-helpers`](https://www.npmjs.com/package/vue-component-type-helpers) 库中的 `ComponentExposed` 来引用组件类型,因为 `InstanceType` 在这种场景下不起作用。 ```vue ``` 请注意在 `@vue/language-tools` 2.1 以上版本中,静态模板 ref 的类型可以被自动推导,上述这些仅在极端情况下需要。 --- --- url: /guide/typescript/options-api.md --- # TypeScript 与选项式 API {#typescript-with-options-api} > 这一章假设你已经阅读了[搭配 TypeScript 使用 Vue](./overview) 的概览。 :::tip 虽然 Vue 的确支持在选项式 API 中使用 TypeScript,但在使用 TypeScript 的前提下更推荐使用组合式 API,因为它提供了更简单、高效和可靠的类型推导。 ::: ## 为组件的 props 标注类型 {#typing-component-props} 选项式 API 中对 props 的类型推导需要用 `defineComponent()` 来包装组件。有了它,Vue 才可以通过 `props` 以及一些额外的选项,比如 `required: true` 和 `default` 来推导出 props 的类型: ```ts import { defineComponent } from 'vue' export default defineComponent({ // 启用了类型推导 props: { name: String, id: [Number, String], msg: { type: String, required: true }, metadata: null }, mounted() { this.name // 类型:string | undefined this.id // 类型:number | string | undefined this.msg // 类型:string this.metadata // 类型:any } }) ``` 然而,这种运行时 `props` 选项仅支持使用构造函数来作为一个 prop 的类型——没有办法指定多层级对象或函数签名之类的复杂类型。 我们可以使用 `PropType` 这个工具类型来标记更复杂的 props 类型: ```ts import { defineComponent } from 'vue' import type { PropType } from 'vue' interface Book { title: string author: string year: number } export default defineComponent({ props: { book: { // 提供相对 `Object` 更确定的类型 type: Object as PropType, required: true }, // 也可以标记函数 callback: Function as PropType<(id: number) => void> }, mounted() { this.book.title // string this.book.year // number // TS Error: argument of type 'string' is not // assignable to parameter of type 'number' this.callback?.('123') } }) ``` ### 注意事项 {#caveats} 如果你的 TypeScript 版本低于 `4.7`,在使用函数作为 prop 的 `validator` 和 `default` 选项值时需要格外小心——确保使用箭头函数: ```ts import { defineComponent } from 'vue' import type { PropType } from 'vue' interface Book { title: string year?: number } export default defineComponent({ props: { bookA: { type: Object as PropType, // 如果你的 TypeScript 版本低于 4.7,确保使用箭头函数 default: () => ({ title: 'Arrow Function Expression' }), validator: (book: Book) => !!book.title } } }) ``` 这会防止 TypeScript 将 `this` 根据函数内的环境作出不符合我们期望的类型推导。这是之前版本的一个[设计限制](https://github.com/microsoft/TypeScript/issues/38845),不过现在已经在 [TypeScript 4.7](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#improved-function-inference-in-objects-and-methods) 中解决了。 ## 为组件的 emits 标注类型 {#typing-component-emits} 我们可以给 `emits` 选项提供一个对象来声明组件所触发的事件,以及这些事件所期望的参数类型。试图触发未声明的事件会抛出一个类型错误: ```ts import { defineComponent } from 'vue' export default defineComponent({ emits: { addBook(payload: { bookName: string }) { // 执行运行时校验 return payload.bookName.length > 0 } }, methods: { onSubmit() { this.$emit('addBook', { bookName: 123 // 类型错误 }) this.$emit('non-declared-event') // 类型错误 } } }) ``` ## 为计算属性标记类型 {#typing-computed-properties} 计算属性会自动根据其返回值来推导其类型: ```ts import { defineComponent } from 'vue' export default defineComponent({ data() { return { message: 'Hello!' } }, computed: { greeting() { return this.message + '!' } }, mounted() { this.greeting // 类型:string } }) ``` 在某些场景中,你可能想要显式地标记出计算属性的类型以确保其实现是正确的: ```ts import { defineComponent } from 'vue' export default defineComponent({ data() { return { message: 'Hello!' } }, computed: { // 显式标注返回类型 greeting(): string { return this.message + '!' }, // 标注一个可写的计算属性 greetingUppercased: { get(): string { return this.greeting.toUpperCase() }, set(newValue: string) { this.message = newValue.toUpperCase() } } } }) ``` 在某些 TypeScript 因循环引用而无法推导类型的情况下,可能必须进行显式的类型标注。 ## 为事件处理函数标注类型 {#typing-event-handlers} 在处理原生 DOM 事件时,应该为我们传递给事件处理函数的参数正确地标注类型。让我们看一下这个例子: ```vue ``` 没有类型标注时,这个 `event` 参数会隐式地标注为 `any` 类型。这也会在 `tsconfig.json` 中配置了 `"strict": true` 或 `"noImplicitAny": true` 时抛出一个 TS 错误。因此,建议显式地为事件处理函数的参数标注类型。此外,在访问 `event` 上的属性时你可能需要使用类型断言: ```ts import { defineComponent } from 'vue' export default defineComponent({ methods: { handleChange(event: Event) { console.log((event.target as HTMLInputElement).value) } } }) ``` ## 扩展全局属性 {#augmenting-global-properties} 某些插件会通过 [`app.config.globalProperties`](/api/application#app-config-globalproperties) 为所有组件都安装全局可用的属性。举例来说,我们可能为了请求数据而安装了 `this.$http`,或者为了国际化而安装了 `this.$translate`。为了使 TypeScript 更好地支持这个行为,Vue 暴露了一个被设计为可以通过 [TypeScript 模块扩展](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation)来扩展的 `ComponentCustomProperties` 接口: ```ts import axios from 'axios' declare module 'vue' { interface ComponentCustomProperties { $http: typeof axios $translate: (key: string) => string } } ``` 参考: - [对组件类型扩展的 TypeScript 单元测试](https://github.com/vuejs/core/blob/main/packages-private/dts-test/componentTypeExtensions.test-d.tsx) ### 类型扩展的位置 {#type-augmentation-placement} 我们可以将这些类型扩展放在一个 `.ts` 文件,或是一个影响整个项目的 `*.d.ts` 文件中。无论哪一种,都应确保在 `tsconfig.json` 中包括了此文件。对于库或插件作者,这个文件应该在 `package.json` 的 `types` 属性中被列出。 为了利用模块扩展的优势,你需要确保将扩展的模块放在 [TypeScript 模块](https://www.typescriptlang.org/docs/handbook/modules.html) 中。 也就是说,该文件需要包含至少一个顶级的 `import` 或 `export`,即使它只是 `export {}`。如果扩展被放在模块之外,它将覆盖原始类型,而不是扩展! ```ts // 不工作,将覆盖原始类型。 declare module 'vue' { interface ComponentCustomProperties { $translate: (key: string) => string } } ``` ```ts // 正常工作。 export {} declare module 'vue' { interface ComponentCustomProperties { $translate: (key: string) => string } } ``` ## 扩展自定义选项 {#augmenting-custom-options} 某些插件,比如 `vue-router`,提供了一些自定义的组件选项,比如 `beforeRouteEnter`: ```ts import { defineComponent } from 'vue' export default defineComponent({ beforeRouteEnter(to, from, next) { // ... } }) ``` 如果没有确切的类型标注,这个钩子函数的参数会隐式地标注为 `any` 类型。我们可以为 `ComponentCustomOptions` 接口扩展自定义的选项来支持: ```ts import { Route } from 'vue-router' declare module 'vue' { interface ComponentCustomOptions { beforeRouteEnter?(to: Route, from: Route, next: () => void): void } } ``` 现在这个 `beforeRouteEnter` 选项会被准确地标注类型。注意这只是一个例子——像 `vue-router` 这种类型完备的库应该在它们自己的类型定义中自动执行这些扩展。 这种类型扩展和全局属性扩展受到[相同的限制](#type-augmentation-placement)。 参考: - [对组件类型扩展的 TypeScript 单元测试](https://github.com/vuejs/core/blob/main/packages-private/dts-test/componentTypeExtensions.test-d.tsx) --- --- url: /api/utility-types.md --- # TypeScript 工具类型 {#utility-types} :::info 此页面仅列出了一些可能需要解释其使用方式的常用工具类型。有关导出类型的完整列表,请查看[源代码](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/index.ts#L131)。 ::: ## PropType\ {#proptype-t} 用于在用运行时 props 声明时给一个 prop 标注更复杂的类型定义。 - **示例** ```ts import type { PropType } from 'vue' interface Book { title: string author: string year: number } export default { props: { book: { // 提供一个比 `Object` 更具体的类型 type: Object as PropType, required: true } } } ``` - **参考**[指南 - 为组件 props 标注类型](/guide/typescript/options-api#typing-component-props) ## MaybeRef\ {#mayberef} - 仅在 3.3+ 版本中支持。 `T | Ref` 的别名。对于标注[组合式函数](/guide/reusability/composables.html)的参数很有用。 ## MaybeRefOrGetter\ {#maybereforgetter} - 仅在 3.3+ 版本中支持。 `T | Ref | (() => T)` 的别名。对于标注[组合式函数](/guide/reusability/composables.html)的参数很有用。 ## ExtractPropTypes\ {#extractproptypes} 从运行时的 props 选项对象中提取 props 类型。提取到的类型是面向内部的,也就是说组件接收到的是解析后的 props。这意味着 boolean 类型的 props 和带有默认值的 props 总是一个定义的值,即使它们不是必需的。 要提取面向外部的 props,即父组件允许传递的 props,请使用 [`ExtractPublicPropTypes`](#extractpublicproptypes)。 - **示例** ```ts const propsOptions = { foo: String, bar: Boolean, baz: { type: Number, required: true }, qux: { type: Number, default: 1 } } as const type Props = ExtractPropTypes // { // foo?: string, // bar: boolean, // baz: number, // qux: number // } ``` ## ExtractPublicPropTypes\ {#extractpublicproptypes} - 仅在 3.3+ 版本中支持。 从运行时的 props 选项对象中提取 prop。提取的类型是面向外部的,即父组件允许传递的 props。 - **示例** ```ts const propsOptions = { foo: String, bar: Boolean, baz: { type: Number, required: true }, qux: { type: Number, default: 1 } } as const type Props = ExtractPublicPropTypes // { // foo?: string, // bar?: boolean, // baz: number, // qux?: number // } ``` ## ComponentCustomProperties {#componentcustomproperties} 用于增强组件实例类型以支持自定义全局属性。 - **示例** ```ts import axios from 'axios' declare module 'vue' { interface ComponentCustomProperties { $http: typeof axios $translate: (key: string) => string } } ``` :::tip 类型扩展必须被放置在一个模块 `.ts` 或 `.d.ts` 文件中。查看[类型扩展指南](/guide/typescript/options-api#augmenting-global-properties)了解更多细节 ::: - **参考**[指南 - 扩展全局属性](/guide/typescript/options-api#augmenting-global-properties) ## ComponentCustomOptions {#componentcustomoptions} 用来扩展组件选项类型以支持自定义选项。 - **示例** ```ts import { Route } from 'vue-router' declare module 'vue' { interface ComponentCustomOptions { beforeRouteEnter?(to: any, from: any, next: () => void): void } } ``` :::tip 类型扩展必须被放置在一个模块 `.ts` 或 `.d.ts` 文件中。查看[类型扩展指南](/guide/typescript/options-api#augmenting-global-properties)了解更多细节。 ::: - **参考**[指南 - 扩展自定义选项](/guide/typescript/options-api#augmenting-custom-options) ## ComponentCustomProps {#componentcustomprops} 用于扩展全局可用的 TSX props,以便在 TSX 元素上使用没有在组件选项上定义过的 props。 - **示例** ```ts declare module 'vue' { interface ComponentCustomProps { hello?: string } } export {} ``` ```tsx // 现在即使没有在组件选项上定义过 hello 这个 prop 也依然能通过类型检查了 ``` :::tip 类型扩展必须被放置在一个模块 `.ts` 或 `.d.ts` 文件中。查看[类型扩展指南](/guide/typescript/options-api#augmenting-global-properties)了解更多细节。 ::: ## CSSProperties {#cssproperties} 用于扩展在样式属性绑定上允许的值的类型。 - **示例** 允许任意自定义 CSS 属性: ```ts declare module 'vue' { interface CSSProperties { [key: `--${string}`]: string } } ``` ```tsx
    ``` ```html
    ``` :::tip 类型增强必须被放置在一个模块 `.ts` 或 `.d.ts` 文件中。查看[类型增强指南](/guide/typescript/options-api#augmenting-global-properties)了解更多细节。 ::: :::info 参考 单文件组件 ` ```
    ```vue ```
    如你所见,Vue 的单文件组件是网页开发中 HTML、CSS 和 JavaScript 三种语言经典组合的自然延伸。`