```js
export default {
mounted() {
console.log(`the component is now mounted.`)
}
}
```
还有其他一些钩子,会在实例生命周期的不同阶段被调用,最常用的是
所有生命周期钩子函数的 `this` 上下文都会自动指向当前调用它的组件实例。注意:避免用箭头函数来定义生命周期钩子,因为如果这样的话你将无法在函数中通过 `this` 获取组件实例。
当调用 `onMounted` 时,Vue 会自动将回调函数注册到当前正被初始化的组件实例上。这意味着这些钩子应当在组件初始化时被**同步**注册。例如,请不要这样做:
```js
setTimeout(() => {
onMounted(() => {
// 异步注册时当前组件实例已丢失
// 这将不会正常工作
})
}, 100)
```
注意这并不意味着对 `onMounted` 的调用必须放在 `setup()` 或 `
Count is: {{ count }}
上面的示例展示了 Vue 的两个核心功能:
- **声明式渲染**:Vue 基于标准 HTML 拓展了一套模板语法,使得我们可以声明式地描述最终输出的 HTML 和 JavaScript 状态之间的关系。
- **响应性**:Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM。
你可能已经有了些疑问——先别急,在后续的文档中我们会详细介绍每一个细节。现在,请继续看下去,以确保你对 Vue 作为一个框架到底提供了什么有一个宏观的了解。
:::tip 预备知识
文档接下来的内容会假设你对 HTML、CSS 和 JavaScript 已经基本熟悉。如果你对前端开发完全陌生,最好不要直接从一个框架开始进行入门学习——最好是掌握了基础知识再回到这里。如有需要,你可以通过这些 [JavaScript](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/A_re-introduction_to_JavaScript)、[HTML](https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML) 和 [CSS](https://developer.mozilla.org/zh-CN/docs/Learn/CSS/First_steps) 概述来检验你的知识水平。如果之前有其他框架的经验会很有帮助,但也不是必须的。
:::
## 渐进式框架 {#the-progressive-framework}
Vue 是一个框架,也是一个生态。其功能覆盖了大部分前端开发常见的需求。但 Web 世界是十分多样化的,不同的开发者在 Web 上构建的东西可能在形式和规模上会有很大的不同。考虑到这一点,Vue 的设计非常注重灵活性和“可以被逐步集成”这个特点。根据你的需求场景,你可以用不同的方式使用 Vue:
- 无需构建步骤,渐进式增强静态的 HTML
- 在任何页面中作为 Web Components 嵌入
- 单页应用 (SPA)
- 全栈 / 服务端渲染 (SSR)
- Jamstack / 静态站点生成 (SSG)
- 开发桌面端、移动端、WebGL,甚至是命令行终端中的界面
如果你是初学者,可能会觉得这些概念有些复杂。别担心!理解教程和指南的内容只需要具备基础的 HTML 和 JavaScript 知识。即使你不是这些方面的专家,也能够跟得上。
如果你是有经验的开发者,希望了解如何以最合适的方式在项目中引入 Vue,或者是对上述的这些概念感到好奇,我们在[使用 Vue 的多种方式](/guide/extras/ways-of-using-vue)中讨论了有关它们的更多细节。
无论再怎么灵活,Vue 的核心知识在所有这些用例中都是通用的。即使你现在只是一个初学者,随着你的不断成长,到未来有能力实现更复杂的项目时,这一路上获得的知识依然会适用。如果你已经是一个老手,你可以根据实际场景来选择使用 Vue 的最佳方式,在各种场景下都可以保持同样的开发效率。这就是为什么我们将 Vue 称为“渐进式框架”:它是一个可以与你共同成长、适应你不同需求的框架。
## 单文件组件 {#single-file-components}
在大多数启用了构建工具的 Vue 项目中,我们可以使用一种类似 HTML 格式的文件来书写 Vue 组件,它被称为**单文件组件** (也被称为 `*.vue` 文件,英文 Single-File Components,缩写为 **SFC**)。顾名思义,Vue 的单文件组件会将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个文件里。下面我们将用单文件组件的格式重写上面的计数器示例:
```vue
Count is: {{ count }}
```
```vue
Count is: {{ count }}
```
单文件组件是 Vue 的标志性功能。如果你的用例需要进行构建,我们推荐用它来编写 Vue 组件。你可以在后续相关章节里了解更多关于[单文件组件的用法及用途](/guide/scaling-up/sfc)。但你暂时只需要知道 Vue 会帮忙处理所有这些构建工具的配置就好。
## API 风格 {#api-styles}
Vue 的组件可以按两种不同的风格书写:**选项式 API** 和**组合式 API**。
### 选项式 API (Options API) {#options-api}
使用选项式 API,我们可以用包含多个选项的对象来描述组件的逻辑,例如 `data`、`methods` 和 `mounted`。选项所定义的属性都会暴露在函数内部的 `this` 上,它会指向当前的组件实例。
```vue
Count is: {{ count }}
```
[在演练场中尝试一下](https://play.vuejs.org/#eNptkMFqxCAQhl9lkB522ZL0HNKlpa/Qo4e1ZpLIGhUdl5bgu9es2eSyIMio833zO7NP56pbRNawNkivHJ25wV9nPUGHvYiaYOYGoK7Bo5CkbgiBBOFy2AkSh2N5APmeojePCkDaaKiBt1KnZUuv3Ky0PppMsyYAjYJgigu0oEGYDsirYUAP0WULhqVrQhptF5qHQhnpcUJD+wyQaSpUd/Xp9NysVY/yT2qE0dprIS/vsds5Mg9mNVbaDofL94jZpUgJXUKBCvAy76ZUXY53CTd5tfX2k7kgnJzOCXIF0P5EImvgQ2olr++cbRE4O3+t6JxvXj0ptXVpye1tvbFY+ge/NJZt)
### 组合式 API (Composition API) {#composition-api}
通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 [`
Count is: {{ count }}
```
[在演练场中尝试一下](https://play.vuejs.org/#eNpNkMFqwzAQRH9lMYU4pNg9Bye09NxbjzrEVda2iLwS0spQjP69a+yYHnRYad7MaOfiw/tqSliciybqYDxDRE7+qsiM3gWGGQJ2r+DoyyVivEOGLrgRDkIdFCmqa1G0ms2EELllVKQdRQa9AHBZ+PLtuEm7RCKVd+ChZRjTQqwctHQHDqbvMUDyd7mKip4AGNIBRyQujzArgtW/mlqb8HRSlLcEazrUv9oiDM49xGGvXgp5uT5his5iZV1f3r4HFHvDprVbaxPhZf4XkKub/CDLaep1T7IhGRhHb6WoTADNT2KWpu/aGv24qGKvrIrr5+Z7hnneQnJu6hURvKl3ryL/ARrVkuI=)
### 该选哪一个?{#which-to-choose}
两种 API 风格都能够覆盖大部分的应用场景。它们只是同一个底层系统所提供的两套不同的接口。实际上,选项式 API 是在组合式 API 的基础上实现的!关于 Vue 的基础概念和知识在它们之间都是通用的。
选项式 API 以“组件实例”的概念为中心 (即上述例子中的 `this`),对于有面向对象语言背景的用户来说,这通常与基于类的心智模型更为一致。同时,它将响应性相关的细节抽象出来,并强制按照选项来组织代码,从而对初学者而言更为友好。
组合式 API 的核心思想是直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题。这种形式更加自由,也需要你对 Vue 的响应式系统有更深的理解才能高效使用。相应的,它的灵活性也使得组织和重用逻辑的模式变得更加强大。
在[组合式 API FAQ](/guide/extras/composition-api-faq) 章节中,你可以了解更多关于这两种 API 风格的对比以及组合式 API 所带来的潜在收益。
如果你是使用 Vue 的新手,这里是我们的大致建议:
- 在学习的过程中,推荐采用更易于自己理解的风格。再强调一下,大部分的核心概念在这两种风格之间都是通用的。熟悉了一种风格以后,你也能够很快地理解另一种风格。
- 在生产项目中:
- 当你不需要使用构建工具,或者打算主要在低复杂度的场景中使用 Vue,例如渐进增强的应用场景,推荐采用选项式 API。
- 当你打算用 Vue 构建完整的单页应用,推荐采用组合式 API + 单文件组件。
在学习阶段,你不必只固守一种风格。在接下来的文档中我们会为你提供一系列两种风格的代码供你参考,你可以随时通过左上角的 **API 风格偏好**来做切换。
## 还有其他问题? {#still-got-questions}
请查看我们的 [FAQ](/about/faq)。
## 选择你的学习路径 {#pick-your-learning-path}
不同的开发者有不同的学习方式。尽管在可能的情况下,我们推荐你通读所有内容,但你还是可以自由地选择一种自己喜欢的学习路径!
---
---
url: /guide/components/v-model.md
---
# 组件 v-model {#component-v-model}
## 基本用法 {#basic-usage}
`v-model` 可以在组件上使用以实现双向绑定。
从 Vue 3.4 开始,推荐的实现方式是使用 [`defineModel()`](/api/sfc-script-setup#definemodel) 宏:
```vue
Parent bound v-model is: {{ model }}
Increment
```
父组件可以用 `v-model` 绑定一个值:
```vue-html
```
`defineModel()` 返回的值是一个 ref。它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用:
- 它的 `.value` 和父组件的 `v-model` 的值同步;
- 当它被子组件变更了,会触发父组件绑定的值一起更新。
这意味着你也可以用 `v-model` 把这个 ref 绑定到一个原生 input 元素上,在提供相同的 `v-model` 用法的同时轻松包装原生 input 元素:
```vue
```
[演练场示例](https://play.vuejs.org/#eNqFUtFKwzAU/ZWYl06YLbK30Q10DFSYigq+5KW0t11mmoQknZPSf/cm3eqEsT0l555zuefmpKV3WsfbBuiUpjY3XDtiwTV6ziSvtTKOLNZcFKQ0qiZRnATkG6JB0BIDJen2kp5iMlfSOlLbisw8P4oeQAhFPpURxVV0zWSa9PNwEgIHtRaZA0SEpOvbeduG5q5LE0Sh2jvZ3tSqADFjFHlGSYJkmhz10zF1FseXvIo3VklcrfX9jOaq1lyAedGOoz1GpyQwnsvQ3fdTqDnTwPhQz9eQf52ob+zO1xh9NWDBbIHRgXOZqcD19PL9GXZ4H0h03whUnyHfwCrReI+97L6RBdo+0gW3j+H9uaw+7HLnQNrDUt6oV3ZBzyhmsjiz+p/dSTwJfUx2+IpD1ic+xz5enwQGXEDJJaw8Gl2I1upMzlc/hEvdOBR6SNKAjqP1J6P/o6XdL11L5h4=)
### 底层机制 {#under-the-hood}
`defineModel` 是一个便利宏。编译器将其展开为以下内容:
- 一个名为 `modelValue` 的 prop,本地 ref 的值与其同步;
- 一个名为 `update:modelValue` 的事件,当本地 ref 的值发生变更时触发。
在 3.4 版本之前,你一般会按照如下的方式来实现上述相同的子组件:
```vue
```
然后,父组件中的 `v-model="foo"` 将被编译为:
```vue-html
(foo = $event)"
/>
```
如你所见,这显得冗长得多。然而,这样写有助于理解其底层机制。
因为 `defineModel` 声明了一个 prop,你可以通过给 `defineModel` 传递选项,来声明底层 prop 的选项:
```js
// 使 v-model 必填
const model = defineModel({ required: true })
// 提供一个默认值
const model = defineModel({ default: 0 })
```
:::warning
如果为 `defineModel` prop 设置了一个 `default` 值且父组件没有为该 prop 提供任何值,会导致父组件与子组件之间不同步。在下面的示例中,父组件的 `myRef` 是 undefined,而子组件的 `model` 是 1:
**子组件:**
```js
const model = defineModel({ default: 1 })
```
**父组件:**
```js
const myRef = ref()
```
```html
```
:::
首先让我们回忆一下 `v-model` 在原生元素上的用法:
```vue-html
```
在代码背后,模板编译器会对 `v-model` 进行更冗长的等价展开。因此上面的代码其实等价于下面这段:
```vue-html
```
而当使用在一个组件上时,`v-model` 会被展开为如下的形式:
```vue-html
searchText = newValue"
/>
```
要让这个例子实际工作起来,`` 组件内部需要做两件事:
1. 将内部原生 ` ` 元素的 `value` attribute 绑定到 `modelValue` prop
2. 当原生的 `input` 事件触发时,触发一个携带了新值的 `update:modelValue` 自定义事件
这里是相应的代码:
```vue
```
现在 `v-model` 可以在这个组件上正常工作了:
```vue-html
```
[在演练场中尝试一下](https://play.vuejs.org/#eNqFkctqwzAQRX9lEAEn4Np744aWrvoD3URdiHiSGvRCHpmC8b93JDfGKYGCkJjXvTrSJF69r8aIohHtcA69p6O0vfEuELzFgZx5tz4SXIIzUFT1JpfGCmmlxe/c3uFFRU0wSQtwdqxh0dLQwHSnNJep3ilS+8PSCxCQYrC3CMDgMKgrNlB8odaOXVJ2TgdvvNp6vSwHhMZrRcgRQLs1G5+M61A/S/ErKQXUR5immwXMWW1VEKX4g3j3Mo9QfXCeKU9FtvpQmp/lM0Oi6RP/qYieebHZNvyL0acLLODNmGYSxCogxVJ6yW1c2iWz/QOnEnY48kdUpMIVGSllD8t8zVZb+PkHqPG4iw==)
另一种在组件内实现 `v-model` 的方式是使用一个可写的,同时具有 getter 和 setter 的 `computed` 属性。`get` 方法需返回 `modelValue` prop,而 `set` 方法需触发相应的事件:
```vue
```
## `v-model` 的参数 {#v-model-arguments}
组件上的 `v-model` 也可以接受一个参数:
```vue-html
```
在子组件中,我们可以通过将字符串作为第一个参数传递给 `defineModel()` 来支持相应的参数:
```vue
```
[在演练场中尝试一下](https://play.vuejs.org/#eNqFklFPwjAUhf9K05dhgiyGNzJI1PCgCWqUx77McQeFrW3aOxxZ9t+9LTAXA/q2nnN6+t12Db83ZrSvgE944jIrDTIHWJmZULI02iJrmIWctSy3umQRRaPOWhweNX0pUHiyR3FP870UZkyoTCuH7FPr3VJiAWzqSwfR/rbUKyhYatdV6VugTktTQHQjVBIfeYiEFgikpwi0YizZ3M2aplfXtklMWvD6UKf+CfrUVPBuh+AspngSd718yH+hX7iS4xihjUZYQS4VLPwJgyiI/3FLZSrafzAeBqFG4jgxeuEqGTo6OZfr0dZpRVxNuFWeEa4swL4alEQm+IQFx3tpUeiv56ChrWB41rMNZLsL+tbVXhP8zYIDuyeQzkN6HyBWb88/XgJ3ZxJ95bH/MN/B6aLyjMfYQ6VWhN3LBdqn8FdJtV66eY2g3HkoD+qTbcgLTo/jX+ra6D+449E47BOq5e039mr+gA==)
如果需要额外的 prop 选项,应该在 model 名称之后传递:
```js
const title = defineModel('title', { required: true })
```
3.4 之前的用法
```vue
```
[在演练场中尝试一下](https://play.vuejs.org/#eNp9kE1rwzAMhv+KMIW00DXsGtKyMXYc7D7vEBplM8QfOHJoCfnvk+1QsjJ2svVKevRKk3h27jAGFJWoh7NXjmBACu4kjdLOeoIJPHYwQ+ethoJLi1vq7fpi+WfQ0JI+lCstcrkYQJqzNQMBKeoRjhG4LcYHbVvsofFfQUcCXhrteix20tRl9sIuOCBkvSHkCKD+fjxN04Ka57rkOOlrMwu7SlVHKdIrBZRcWpc3ntiLO7t/nKHFThl899YN248ikYpP9pj1V60o6sG1TMwDU/q/FZRxgeIPgK4uGcQLSZGlamz6sHKd1afUxOoGeeT298A9bHCMKxBfE3mTSNjl1vud5x8qNa76)
在这种情况下,子组件应该使用 `title` prop 和 `update:title` 事件来更新父组件的值,而非默认的 `modelValue` prop 和 `update:modelValue` 事件:
```vue
```
[在演练场中尝试一下](https://play.vuejs.org/#eNqFUNFqwzAM/BVhCm6ha9hryMrGnvcFdR9Mo26B2DGuHFJC/n2yvZakDAohtuTTne5G8eHcrg8oSlFdTr5xtFe2Ma7zBF/Xz45vFi3B2XcG5K6Y9eKYVFZZHBK8xrMOLcGoLMDphrqUMC6Ypm18rzXp9SZjATxS8PZWAVBDLZYg+xfT1diC9t/BxGEctHFtlI2wKR78468q7ttzQcgoTcgVQPXzuh/HzAnTVBVcp/58qz+lMqHelEinElAwtCrufGIrHhJYBPdfEs53jkM4yEQpj8k+miYmc5DBcRKYZeXxqZXGukDZPF1dWhQHUiK3yl63YbZ97r6nIe6uoup6KbmFFfbRCnHGyI4iwyaPPnqffgGMlsEM)
## 多个 `v-model` 绑定 {#multiple-v-model-bindings}
利用刚才在 [`v-model` 的参数](#v-model-arguments)小节中学到的指定参数与事件名的技巧,我们可以在单个组件实例上创建多个 `v-model` 双向绑定。
组件上的每一个 `v-model` 都会同步不同的 prop,而无需额外的选项:
```vue-html
```
```vue
```
[在演练场中尝试一下](https://play.vuejs.org/#eNqFkstuwjAQRX/F8iZUAqKKHQpIfbAoUmnVx86bKEzANLEt26FUkf+9Y4MDSAg2UWbu9fjckVv6oNRw2wAd08wUmitLDNhGTZngtZLakpZoKIkjpZY1SdCadNK3Ab3IazhowzQ2/ES0MVFIYSwpucbvxA/qJXO5FsldlKr8qDxL8EKW7kEQAQsLtapyC1gRkq3vp217mOccwf8wwLksRSlYIoMvCNkOarmEahyODAT2J4yGgtFzhx8UDf5/r6c4NEs7CNqnpxkvbO0kcVjNhCyh5AJe/SW9pBPOV3DJGvu3dsKFaiyxf8qTW9gheQwVs4Z90BDm5oF47cF/Ht4aZC75argxUmD61g9ktJC14hXoN2U5ZmJ0TILitbyq5O889KxuoB/7xRqKnwv9jdn5HqPvGnDVWwTpNJvrFSCul2efi4DeiRigqdB9RfwAI6vGM+5tj41YIvaJL9C+hOfNxerLzHYWhImhPKh3uuBnFJ/A05XoR9zRcBTOMeGo+wcs+yse)
3.4 之前的用法
```vue
```
[在演练场中尝试一下](https://play.vuejs.org/#eNqNUc1qwzAMfhVjCk6hTdg1pGWD7bLDGIydlh1Cq7SGxDaOEjaC332yU6cdFNpLsPRJ348y8idj0qEHnvOi21lpkHWAvdmWSrZGW2Qjs1Azx2qrWyZoVMzQZwf2rWrhhKVZbHhGGivVTqsOWS0tfTeeKBGv+qjEMkJNdUaeNXigyCYjZIEKhNY0FQJVjBXHh+04nvicY/QOBM4VGUFhJHrwBWPDutV7aPKwslbU35Q8FCX/P+GJ4oB/T3hGpEU2m+ArfpnxytX2UEsF71abLhk9QxDzCzn7QCvVYeW7XuGyWSpH0eP6SyuxS75Eb/akOpn302LFYi8SiO8bJ5PK9DhFxV/j0yH8zOnzoWr6+SbhbifkMSwSsgByk1zzsoABFKZY2QNgGpiW57Pdrx2z3JCeI99Svvxh7g8muf2x)
```vue
```
[在演练场中尝试一下](https://play.vuejs.org/#eNqNkk1rg0AQhv/KIAETSJRexYYWeuqhl9JTt4clmSSC7i7rKCnif+/ObtYkELAiujPzztejQ/JqTNZ3mBRJ2e5sZWgrVNUYbQm+WrQfskE4WN1AmuXRwQmpUELh2Qv3eJBdTTAIBbDTLluhoraA4VpjXHNwL0kuV0EIYJE6q6IFcKhsSwWk7/qkUq/nq5be+aa5JztGfrmHu8t8GtoZhI2pJaGzAMrT03YYQk0YR3BnruSOZe5CXhKnC3X7TaP3WBc+ZaOc/1kk3hDJvYILRQGfQzx3Rct8GiJZJ7fA7gg/AmesNszMrUIXFpxbwCfZSh09D0Hc7tbN6sAWm4qZf6edcZgxrMHSdA3RF7PTn1l8lTIdhbXp1/CmhOeJRNHLupv4eIaXyItPdJEFD7R8NM0Ce/d/ZCTtESnzlVZXhP/vHbeZaT0tPdf59uONfx7mDVM=)
## 处理 `v-model` 修饰符 {#handling-v-model-modifiers}
在学习输入绑定时,我们知道了 `v-model` 有一些[内置的修饰符](/guide/essentials/forms#modifiers),例如 `.trim`,`.number` 和 `.lazy`。在某些场景下,你可能想要一个自定义组件的 `v-model` 支持自定义的修饰符。
我们来创建一个自定义的修饰符 `capitalize`,它会自动将 `v-model` 绑定输入的字符串值第一个字母转为大写:
```vue-html
```
通过像这样解构 `defineModel()` 的返回值,可以在子组件中访问添加到组件 `v-model` 的修饰符:
```vue{4}
```
为了能够基于修饰符选择性地调节值的读取和写入方式,我们可以给 `defineModel()` 传入 `get` 和 `set` 这两个选项。这两个选项在从模型引用中读取或设置值时会接收到当前的值,并且它们都应该返回一个经过处理的新值。下面是一个例子,展示了如何利用 `set` 选项来应用 `capitalize` (首字母大写) 修饰符:
```vue{6-8}
```
[在演练场中尝试一下](https://play.vuejs.org/#eNp9UsFu2zAM/RVClzhY5mzoLUgHdEUPG9Bt2LLTtIPh0Ik6WRIkKksa5N9LybFrFG1OkvgeyccnHsWNc+UuoliIZai9cgQBKbpP0qjWWU9wBI8NnKDxtoUJUycDdH+4tXwzaOgMl/NRLNVlMoA0tTWBoD2scE9wnSoWk8lUmuW8a8rt+EHYOl0R8gtgtVUBlHGRoK6cokqrRwxAW4RGea6mkQg9HGwEboZ+kbKWY027961doy6f86+l6ERIAXNus5wPPcVMvNB+yZOaiZFw/cKYftI/ufEM+FCNQh/+8tRrbJTB+4QUxySWqxa7SkecQn4DqAaKIWekeyAAe0fRG8h5Zb2t/A0VH6Yl2d/Oob+tAhZTeHfGg1Y1Fh/Z6ZR66o5xhRTh8OnyXyy7f6CDSw5S59/Z3WRpOl91lAL70ahN+RCsYT/zFFIk95RG/92RYr+kWPTzSVFpbf9/zTHyEWd9vN5i/e+V+EPYp5gUPzwG9DuUYsCo8htkrQm++/Ut6x5AVh01sy+APzFYHZPGjvY5mjXLHvGy2i95K5TZrMLdntCEfqgkNDuc+VLwkqQNe2v0Z7lX5VX/M+L0BFEuPdc=)
3.4 之前的用法
```vue{11-13}
```
[在演练场中尝试一下](https://play.vuejs.org/#eNp9Us1Og0AQfpUJF5ZYqV4JNTaNxyYmVi/igdCh3QR2N7tDIza8u7NLpdU0nmB+v5/ZY7Q0Jj10GGVR7iorDYFD6sxDoWRrtCU4gsUaBqitbiHm1ngqrfuV5j+Fik7ldH6R83u5GaBQlVaOoO03+Emw8BtFHCeFyucjKMNxQNiapiTkCGCzlw6kMh1BVRpJZSO/0AEe0Pa0l2oHve6AYdBmvj+/ZHO4bfUWm/Q8uSiiEb6IYM4A+XxCi2bRH9ZX3BgVGKuNYwFbrKXCZx+Jo0cPcG9l02EGL2SZ3mxKr/VW1hKty9hMniy7hjIQCSweQByHBIZCDWzGDwi20ps0Yjxx4MR73Jktc83OOPFHGKk7VZHUKkyFgsAEAqcG2Qif4WWYUml3yOp8wldlDSLISX+TvPDstAemLeGbVvvSLkncJSnpV2PQrkqHLOfmVHeNrFDcMz3w0iBQE1cUzMYBbuS2f55CPj4D6o0/I41HzMKsP+u0kLOPoZWzkx1X7j18A8s0DEY=)
添加到组件 `v-model` 的修饰符将通过 `modelModifiers` prop 提供给组件。在下面的示例中,我们创建了一个包含 `modelModifiers` prop 的组件,该 prop 默认为空对象:
```vue{11}
```
请注意,该组件的 `modelModifiers` prop 包含 `capitalize` 且值为 `true` ——因为它是在 `v-model.capitalize="myText"` 这个 `v-model` 绑定上设置的。
现在我们已经为组件配置了 prop,我们可以检查 `modelModifiers` 对象的键并编写一个处理程序来更改抛出的值。在下面的代码中,每当 ` ` 元素触发 `input` 事件时,我们都会将首字母大写。
```vue{13-15}
```
[在演练场中尝试一下](https://play.vuejs.org/#eNqFks1qg0AQgF9lkIKGpqa9iikNOefUtJfaw6KTZEHdZR1DbPDdO7saf0qgIq47//PNXL2N1uG5Ri/y4io1UtNrUspCK0Owa7aK/0osCQ5GFeCHq4nMuvlJCZCUeHEOGR5EnRNcrTS92VURXGex2qXVZ4JEsOhsAQxSbcrbDaBo9nihCHyXAaC1B3/4jVdDoXwhLHQuCPkGsD/JCmSpa4JUaEkilz9YAZ7RNHSS5REaVQPXgCay9vG0rPNToTLMw9FznXhdHYkHK04Qr4Zs3tL7g2JG8B4QbZS2LLqGXK5PkdcYwTsZrs1R6RU7lcmDRDPaM7AuWARMbf0KwbVdTNk4dyyk5f3l15r5YjRm8b+dQYF0UtkY1jo4fYDDLAByZBxWCmvAkIQ5IvdoBTcLeYCAiVbhvNwJvEk4GIK5M0xPwmwoeF6EpD60RrMVFXJXj72+ymWKwUvfXt+gfVzGB1tzcKfDZec+o/LfxsTdtlCj7bSpm3Xk4tjpD8FZ+uZMWTowu7MW7S+CWR77)
### 带参数的 `v-model` 修饰符 {#modifiers-for-v-model-with-arguments}
对于又有参数又有修饰符的 `v-model` 绑定,生成的 prop 名将是 `arg + "Modifiers"`。举例来说:
```vue-html
```
相应的声明应该是:
```js
export default {
props: ['title', 'titleModifiers'],
emits: ['update:title'],
created() {
console.log(this.titleModifiers) // { capitalize: true }
}
}
```
这里是另一个例子,展示了如何在使用多个不同参数的 `v-model` 时使用修饰符:
```vue-html
```
```vue
```
3.4 之前的用法
```vue{5,6,10,11}
```
```vue{15,16}
```
---
---
url: /guide/components/events.md
---
# 组件事件 {#component-events}
> 此章节假设你已经看过了[组件基础](/guide/essentials/component-basics)。若你还不了解组件是什么,请先阅读该章节。
## 触发与监听事件 {#emitting-and-listening-to-events}
在组件的模板表达式中,可以直接使用 `$emit` 方法触发自定义事件 (例如:在 `v-on` 的处理函数中):
```vue-html
Click Me
```
`$emit()` 方法在组件实例上也同样以 `this.$emit()` 的形式可用:
```js
export default {
methods: {
submit() {
this.$emit('someEvent')
}
}
}
```
父组件可以通过 `v-on` (缩写为 `@`) 来监听事件:
```vue-html
```
同样,组件的事件监听器也支持 `.once` 修饰符:
```vue-html
```
像组件与 prop 一样,事件的名字也提供了自动的格式转换。注意这里我们触发了一个以 camelCase 形式命名的事件,但在父组件中可以使用 kebab-case 形式来监听。与 [prop 大小写格式](/guide/components/props#prop-name-casing)一样,在模板中我们也推荐使用 kebab-case 形式来编写监听器。
:::tip
和原生 DOM 事件不一样,组件触发的事件**没有冒泡机制**。你只能监听直接子组件触发的事件。平级组件或是跨越多层嵌套的组件间通信,应使用一个外部的事件总线,或是使用一个[全局状态管理方案](/guide/scaling-up/state-management)。
:::
## 事件参数 {#event-arguments}
有时候我们会需要在触发事件时附带一个特定的值。举例来说,我们想要 `
` 组件来管理文本会缩放得多大。在这个场景下,我们可以给 `$emit` 提供一个额外的参数:
```vue-html
Increase by 1
```
然后我们在父组件中监听事件,我们可以先简单写一个内联的箭头函数作为监听器,此函数会接收到事件附带的参数:
```vue-html
count += n" />
```
或者,也可以用一个组件方法来作为事件处理函数:
```vue-html
```
该方法也会接收到事件所传递的参数:
```js
methods: {
increaseCount(n) {
this.count += n
}
}
```
```js
function increaseCount(n) {
count.value += n
}
```
:::tip
所有传入 `$emit()` 的额外参数都会被直接传向监听器。举例来说,`$emit('foo', 1, 2, 3)` 触发后,监听器函数将会收到这三个参数值。
:::
## 声明触发的事件 {#declaring-emitted-events}
组件可以显式地通过 [`defineEmits()`](/api/sfc-script-setup#defineprops-defineemits) 宏 [`emits`](/api/options-state#emits) 选项 来声明它要触发的事件:
```vue
```
我们在 `` 中使用的 `$emit` 方法不能在组件的 `
```
`defineEmits()` 宏**不能**在子函数中使用。如上所示,它必须直接放置在 `
```
如果你正在搭配 TypeScript 使用 `
```
TypeScript 用户请参考:[如何为组件所抛出事件标注类型](/guide/typescript/composition-api#typing-component-emits)
```js
export default {
emits: {
submit(payload: { email: string, password: string }) {
// 通过返回值为 `true` 还是为 `false` 来判断
// 验证是否通过
}
}
}
```
TypeScript 用户请参考:[如何为组件所抛出的事件标注类型](/guide/typescript/options-api#typing-component-emits)。
尽管事件声明是可选的,我们还是推荐你完整地声明所有要触发的事件,以此在代码中作为文档记录组件的用法。同时,事件声明能让 Vue 更好地将事件和[透传 attribute](/guide/components/attrs#v-on-listener-inheritance) 作出区分,从而避免一些由第三方代码触发的自定义 DOM 事件所导致的边界情况。
:::tip
如果一个原生事件的名字 (例如 `click`) 被定义在 `emits` 选项中,则监听器只会监听组件触发的 `click` 事件而不会再响应原生的 `click` 事件。
:::
## 事件校验 {#events-validation}
和对 props 添加类型校验的方式类似,所有触发的事件也可以使用对象形式来描述。
要为事件添加校验,那么事件可以被赋值为一个函数,接受的参数就是抛出事件时传入 `this.$emit` `emit` 的内容,返回一个布尔值来表明事件是否合法。
```vue
```
```js
export default {
emits: {
// 没有校验
click: null,
// 校验 submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
},
methods: {
submitForm(email, password) {
this.$emit('submit', { email, password })
}
}
}
```
---
---
url: /guide/essentials/component-basics.md
---
# 组件基础 {#components-basics}
组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成一个层层嵌套的树状结构:

这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。Vue 同样也能很好地配合原生 Web Component。如果你想知道 Vue 组件与原生 Web Components 之间的关系,可以[阅读此章节](/guide/extras/web-components)。
## 定义一个组件 {#defining-a-component}
当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 `.vue` 文件中,这被叫做[单文件组件](/guide/scaling-up/sfc) (简称 SFC):
```vue
You clicked me {{ count }} times.
```
```vue
You clicked me {{ count }} times.
```
当不使用构建步骤时,一个 Vue 组件以一个包含 Vue 特定选项的 JavaScript 对象来定义:
```js
export default {
data() {
return {
count: 0
}
},
template: `
You clicked me {{ count }} times.
`
}
```
```js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
},
template: `
You clicked me {{ count }} times.
`
// 也可以针对一个 DOM 内联模板:
// template: '#my-template-element'
}
```
这里的模板是一个内联的 JavaScript 字符串,Vue 将会在运行时编译它。你也可以使用 ID 选择器来指向一个元素 (通常是原生的 `` 元素),Vue 将会使用其内容作为模板来源。
上面的例子中定义了一个组件,并在一个 `.js` 文件里默认导出了它自己,但你也可以通过具名导出在一个文件中导出多个组件。
## 使用组件 {#using-a-component}
:::tip
我们会在接下来的指引中使用单文件组件语法,无论你是否使用构建步骤,组件相关的概念都是相同的。[示例](/examples/)一节中展示了两种场景中的组件使用情况。
:::
要使用一个子组件,我们需要在父组件中导入它。假设我们把计数器组件放在了一个叫做 `ButtonCounter.vue` 的文件中,这个组件将会以默认导出的形式被暴露给外部。
```vue
Here is a child component!
```
若要将导入的组件暴露给模板,我们需要在 `components` 选项上[注册](/guide/components/registration)它。这个组件将会以其注册时的名字作为模板中的标签名。
```vue
Here is a child component!
```
通过 `
{{ title }}
```
当一个值被传递给 prop 时,它将成为该组件实例上的一个属性。该属性的值可以像其他组件属性一样,在模板和组件的 `this` 上下文中访问。
```vue
{{ title }}
```
`defineProps` 是一个仅 `
```
```vue{4}
```
这声明了一个组件可能触发的所有事件,还可以对事件的参数进行[验证](/guide/components/events#validate-emitted-events)。同时,这还可以让 Vue 避免将它们作为原生事件监听器隐式地应用于子组件的根元素。
和 `defineProps` 类似,`defineEmits` 仅可用于 `
```
TypeScript 用户请参考:[为组件 emits 标注类型](/guide/typescript/composition-api#typing-component-emits)
如果你没有在使用 `
```
如果没有使用 `
```
对于每个 `components` 对象里的属性,它们的 key 名就是注册的组件名,而值就是相应组件的实现。上面的例子中使用的是 ES2015 的缩写语法,等价于:
```js
export default {
components: {
ComponentA: ComponentA
}
// ...
}
```
请注意:**局部注册的组件在后代组件中不 可用**。在这个例子中,`ComponentA` 注册后仅在当前组件可用,而在任何的子组件或更深层的子组件中都不可用。
## 组件名格式 {#component-name-casing}
在整个指引中,我们都使用 PascalCase 作为组件名的注册格式,这是因为:
1. PascalCase 是合法的 JavaScript 标识符。这使得在 JavaScript 中导入和注册组件都很容易,同时 IDE 也能提供较好的自动补全。
2. ` ` 在模板中更明显地表明了这是一个 Vue 组件,而不是原生 HTML 元素。同时也能够将 Vue 组件和自定义元素 (web components) 区分开来。
在单文件组件和内联字符串模板中,我们都推荐这样做。但是,PascalCase 的标签名在 DOM 内模板中是不可用的,详情参见 [DOM 内模板解析注意事项](/guide/essentials/component-basics#in-dom-template-parsing-caveats)。
为了方便,Vue 支持将模板中使用 kebab-case 的标签解析为使用 PascalCase 注册的组件。这意味着一个以 `MyComponent` 为名注册的组件,在模板 (或由 Vue 渲染的 HTML 元素) 中可以通过 `` 或 `` 引用。这让我们能够使用同样的 JavaScript 组件注册代码来配合不同来源的模板。
---
---
url: /guide/extras/composition-api-faq.md
---
# 组合式 API 常见问答 {#composition-api-faq}
:::tip
这个 FAQ 假定你已经有一些使用 Vue 的经验,特别是用选项式 API 使用 Vue 2 的经验。
:::
## 什么是组合式 API? {#what-is-composition-api}
组合式 API (Composition API) 是一系列 API 的集合,使我们可以使用函数而不是声明选项的方式书写 Vue 组件。它是一个概括性的术语,涵盖了以下方面的 API:
- [响应式 API](/api/reactivity-core):例如 `ref()` 和 `reactive()`,使我们可以直接创建响应式状态、计算属性和侦听器。
- [生命周期钩子](/api/composition-api-lifecycle):例如 `onMounted()` 和 `onUnmounted()`,使我们可以在组件各个生命周期阶段添加逻辑。
- [依赖注入](/api/composition-api-dependency-injection):例如 `provide()` 和 `inject()`,使我们可以在使用响应式 API 时,利用 Vue 的依赖注入系统。
组合式 API 是 Vue 3 及 [Vue 2.7](https://blog.vuejs.org/posts/vue-2-7-naruto.html) 的内置功能。对于更老的 Vue 2 版本,可以使用官方维护的插件 [`@vue/composition-api`](https://github.com/vuejs/composition-api)。在 Vue 3 中,组合式 API 基本上都会配合 [`
Count is: {{ count }}
```
虽然这套 API 的风格是基于函数的组合,但**组合式 API 并不是函数式编程**。组合式 API 是以 Vue 中数据可变的、细粒度的响应性系统为基础的,而函数式编程通常强调数据不可变。
如果你对如何通过组合式 API 使用 Vue 感兴趣,可以通过页面左侧边栏上方的开关将 API 偏好切换到组合式 API,然后重新从头阅读指引。
## 为什么要有组合式 API? {#why-composition-api}
### 更好的逻辑复用 {#better-logic-reuse}
组合式 API 最基本的优势是它使我们能够通过[组合函数](/guide/reusability/composables)来实现更加简洁高效的逻辑复用。在选项式 API 中我们主要的逻辑复用机制是 mixins,而组合式 API 解决了 [mixins 的所有缺陷](/guide/reusability/composables#vs-mixins)。
组合式 API 提供的逻辑复用能力孵化了一些非常棒的社区项目,比如 [VueUse](https://vueuse.org/),一个不断成长的工具型组合式函数集合。组合式 API 还为其他第三方状态管理库与 Vue 的响应式系统之间的集成提供了一套简洁清晰的机制,例如[不可变数据](/guide/extras/reactivity-in-depth#immutable-data)、[状态机](/guide/extras/reactivity-in-depth#state-machines)与 [RxJS](/guide/extras/reactivity-in-depth#rxjs)。
### 更灵活的代码组织 {#more-flexible-code-organization}
许多用户喜欢选项式 API 的原因是它在默认情况下就能够让人写出有组织的代码:大部分代码都自然地被放进了对应的选项里。然而,选项式 API 在单个组件的逻辑复杂到一定程度时,会面临一些无法忽视的限制。这些限制主要体现在需要处理多个**逻辑关注点**的组件中,这是我们在许多 Vue 2 的实际案例中所观察到的。
我们以 Vue CLI GUI 中的文件浏览器组件为例:这个组件承担了以下几个逻辑关注点:
- 追踪当前文件夹的状态,展示其内容
- 处理文件夹的相关操作 (打开、关闭和刷新)
- 支持创建新文件夹
- 可以切换到只展示收藏的文件夹
- 可以开启对隐藏文件夹的展示
- 处理当前工作目录中的变更
这个组件[最原始的版本](https://github.com/vuejs/vue-cli/blob/a09407dd5b9f18ace7501ddb603b95e31d6d93c0/packages/@vue/cli-ui/src/components/folder/FolderExplorer.vue#L198-L404)是由选项式 API 写成的。如果我们为相同的逻辑关注点标上一种颜色,那将会是这样:
你可以看到,处理相同逻辑关注点的代码被强制拆分在了不同的选项中,位于文件的不同部分。在一个几百行的大组件中,要读懂代码中的一个逻辑关注点,需要在文件中反复上下滚动,这并不理想。另外,如果我们想要将一个逻辑关注点抽取重构到一个可复用的工具函数中,需要从文件的多个不同部分找到所需的正确片段。
而如果[用组合式 API 重构](https://github.com/vuejs-translations/docs-zh-cn/blob/main/assets/FileExplorer.vue)这个组件,将会变成下面右边这样:

现在与同一个逻辑关注点相关的代码被归为了一组:我们无需再为了一个逻辑关注点在不同的选项块间来回滚动切换。此外,我们现在可以很轻松地将这一组代码移动到一个外部文件中,不再需要为了抽象而重新组织代码,大大降低了重构成本,这在长期维护的大型项目中非常关键。
### 更好的类型推导 {#better-type-inference}
近几年来,越来越多的开发者开始使用 [TypeScript](https://www.typescriptlang.org/) 书写更健壮可靠的代码,TypeScript 还提供了非常好的 IDE 开发支持。然而选项式 API 是在 2013 年被设计出来的,那时并没有把类型推导考虑进去,因此我们不得不做了一些[复杂到夸张的类型体操](https://github.com/vuejs/core/blob/44b95276f5c086e1d88fa3c686a5f39eb5bb7821/packages/runtime-core/src/componentPublicInstance.ts#L132-L165)才实现了对选项式 API 的类型推导。但尽管做了这么多的努力,选项式 API 的类型推导在处理 mixins 和依赖注入类型时依然不甚理想。
因此,很多想要搭配 TS 使用 Vue 的开发者采用了由 `vue-class-component` 提供的 Class API。然而,基于 Class 的 API 非常依赖 ES 装饰器,在 2019 年我们开始开发 Vue 3 时,它仍是一个仅处于 stage 2 的语言功能。我们认为基于一个不稳定的语言提案去设计框架的核心 API 风险实在太大了,因此没有继续向 Class API 的方向发展。在那之后装饰器提案果然又发生了很大的变动,在 2022 年才终于到达 stage 3。另一个问题是,基于 Class 的 API 和选项式 API 在逻辑复用和代码组织方面存在相同的限制。
相比之下,组合式 API 主要利用基本的变量和函数,它们本身就是类型友好的。用组合式 API 重写的代码可以享受到完整的类型推导,不需要书写太多类型标注。大多数时候,用 TypeScript 书写的组合式 API 代码和用 JavaScript 写都差不太多!这也让许多纯 JavaScript 用户也能从 IDE 中享受到部分类型推导功能。
### 更小的生产包体积 {#smaller-production-bundle-and-less-overhead}
搭配 `
{{ count }}
```
在模板中访问从 `setup` 返回的 [ref](/api/reactivity-core#ref) 时,它会[自动浅层解包](/guide/essentials/reactivity-fundamentals#deep-reactivity),因此你无须再在模板中为它写 `.value`。当通过 `this` 访问时也会同样如此解包。
`setup()` 自身并不含对组件实例的访问权,即在 `setup()` 中访问 `this` 会是 `undefined`。你可以在选项式 API 中访问组合式 API 暴露的值,但反过来则不行。
`setup()` 应该*同步地*返回一个对象。唯一可以使用 `async setup()` 的情况是,该组件是 [Suspense](../guide/built-ins/suspense) 组件的后裔。
## 访问 Props {#accessing-props}
`setup` 函数的第一个参数是组件的 `props`。和标准的组件一致,一个 `setup` 函数的 `props` 是响应式的,并且会在传入新的 props 时同步更新。
```js
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
```
请注意如果你解构了 `props` 对象,解构出的变量将会丢失响应性。因此我们推荐通过 `props.xxx` 的形式来使用其中的 props。
如果你确实需要解构 `props` 对象,或者需要将某个 prop 传到一个外部函数中并保持响应性,那么你可以使用 [toRefs()](./reactivity-utilities#torefs) 和 [toRef()](/api/reactivity-utilities#toref) 这两个工具函数:
```js
import { toRefs, toRef } from 'vue'
export default {
setup(props) {
// 将 `props` 转为一个其中全是 ref 的对象,然后解构
const { title } = toRefs(props)
// `title` 是一个追踪着 `props.title` 的 ref
console.log(title.value)
// 或者,将 `props` 的单个属性转为一个 ref
const title = toRef(props, 'title')
}
}
```
## Setup 上下文 {#setup-context}
传入 `setup` 函数的第二个参数是一个 **Setup 上下文**对象。上下文对象暴露了其他一些在 `setup` 中可能会用到的值:
```js
export default {
setup(props, context) {
// 透传 Attributes(非响应式的对象,等价于 $attrs)
console.log(context.attrs)
// 插槽(非响应式的对象,等价于 $slots)
console.log(context.slots)
// 触发事件(函数,等价于 $emit)
console.log(context.emit)
// 暴露公共属性(函数)
console.log(context.expose)
}
}
```
该上下文对象是非响应式的,可以安全地解构:
```js
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
```
`attrs` 和 `slots` 都是有状态的对象,它们总是会随着组件自身的更新而更新。这意味着你应当避免解构它们,并始终通过 `attrs.x` 或 `slots.x` 的形式使用其中的属性。此外还需注意,和 `props` 不同,`attrs` 和 `slots` 的属性都**不是**响应式的。如果你想要基于 `attrs` 或 `slots` 的改变来执行副作用,那么你应该在 `onBeforeUpdate` 生命周期钩子中编写相关逻辑。
### 暴露公共属性 {#exposing-public-properties}
`expose` 函数用于显式地限制该组件暴露出的属性,当父组件通过[模板引用](/guide/essentials/template-refs#ref-on-component)访问该组件的实例时,将仅能访问 `expose` 函数暴露出的内容:
```js{5,10}
export default {
setup(props, { expose }) {
// 让组件实例处于 “关闭状态”
// 即不向父组件暴露任何东西
expose()
const publicCount = ref(0)
const privateCount = ref(0)
// 有选择地暴露局部状态
expose({ count: publicCount })
}
}
```
## 与渲染函数一起使用 {#usage-with-render-functions}
`setup` 也可以返回一个[渲染函数](/guide/extras/render-function),此时在渲染函数中可以直接使用在同一作用域下声明的响应式状态:
```js{6}
import { h, ref } from 'vue'
export default {
setup() {
const count = ref(0)
return () => h('div', count.value)
}
}
```
返回一个渲染函数将会阻止我们返回其他东西。对于组件内部来说,这样没有问题,但如果我们想通过模板引用将这个组件的方法暴露给父组件,那就有问题了。
我们可以通过调用 [`expose()`](#exposing-public-properties) 解决这个问题:
```js{8-10}
import { h, ref } from 'vue'
export default {
setup(props, { expose }) {
const count = ref(0)
const increment = () => ++count.value
expose({
increment
})
return () => h('div', count.value)
}
}
```
此时父组件可以通过模板引用来访问这个 `increment` 方法。
---
---
url: /api/composition-api-dependency-injection.md
---
# 组合式 API:依赖注入 {#composition-api-dependency-injection}
## provide() {#provide}
提供一个值,可以被后代组件注入。
- **类型**
```ts
function provide(key: InjectionKey | string, value: T): void
```
- **详细信息**
`provide()` 接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。
当使用 TypeScript 时,key 可以是一个被类型断言为 `InjectionKey` 的 symbol。`InjectionKey` 是一个 Vue 提供的工具类型,继承自 `Symbol`,可以用来同步 `provide()` 和 `inject()` 之间值的类型。
与注册生命周期钩子的 API 类似,`provide()` 必须在组件的 `setup()` 阶段同步调用。
- **示例**
```vue
```
- **参考**
- [指南 - 依赖注入](/guide/components/provide-inject)
- [指南 - 为 provide/inject 标注类型](/guide/typescript/composition-api#typing-provide-inject)
## inject() {#inject}
注入一个由祖先组件或整个应用 (通过 `app.provide()`) 提供的值。
- **类型**
```ts
// 没有默认值
function inject(key: InjectionKey | string): T | undefined
// 带有默认值
function inject(key: InjectionKey | string, defaultValue: T): T
// 使用工厂函数
function inject(
key: InjectionKey | string,
defaultValue: () => T,
treatDefaultAsFactory: true
): T
```
- **详细信息**
第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,则会应用离得更近的组件所提供的值,链上更远的组件所提供的值将会被“覆盖”。如果没有能通过 key 匹配到值,`inject()` 将返回 `undefined`,除非提供了一个默认值。
第二个参数是可选的,即在没有匹配到 key 时使用的默认值。
第二个参数也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。在这种情况下,你必须将 `true` 作为第三个参数传入,表明这个函数将作为工厂函数使用,而非值本身。
与注册生命周期钩子的 API 类似,`inject()` 必须在组件的 `setup()` 阶段同步调用。
当使用 TypeScript 时,key 可以是一个类型为 `InjectionKey` 的 symbol。`InjectionKey` 是一个 Vue 提供的工具类型,继承自 `Symbol`,可以用来同步 `provide()` 和 `inject()` 之间值的类型。
- **示例**
假设有一个父组件已经提供了一些值,如前面 `provide()` 的例子中所示:
```vue
```
- **参考**
- [指南 - 依赖注入](/guide/components/provide-inject)
- [指南 - 为 provide / inject 标注类型](/guide/typescript/composition-api#typing-provide-inject)
## hasInjectionContext() {#has-injection-context}
- 仅在 3.3+ 中支持
如果 [inject()](#inject) 可以在错误的地方 (例如 `setup()` 之外) 被调用而不触发警告,则返回 `true`。此方法适用于希望在内部使用 `inject()` 而不向用户发出警告的库。
- **类型**
```ts
function hasInjectionContext(): boolean
```
---
---
url: /api/composition-api-lifecycle.md
---
# 组合式 API:生命周期钩子 {#composition-api-lifecycle-hooks}
:::info 使用方式注意
所有罗列在本页的 API 都应该在组件的 `setup()` 阶段被同步调用。相关细节请看[指南 - 生命周期钩子](/guide/essentials/lifecycle)。
:::
## onMounted() {#onmounted}
注册一个回调函数,在组件挂载完成后执行。
- **类型**
```ts
function onMounted(callback: () => void, target?: ComponentInternalInstance | null): void
```
- **详细信息**
组件在以下情况下被视为已挂载:
- 其所有同步子组件都已经被挂载 (不包含异步组件或 `` 树内的组件)。
- 其自身的 DOM 树已经创建完成并插入了父容器中。注意仅当根容器在文档中时,才可以保证组件 DOM 树也在文档中。
这个钩子通常用于执行需要访问组件所渲染的 DOM 树相关的副作用,或是在[服务端渲染应用](/guide/scaling-up/ssr)中用于确保 DOM 相关代码仅在客户端执行。
**这个钩子在服务器端渲染期间不会被调用。**
- **示例**
通过模板引用访问一个元素:
```vue
```
## onUpdated() {#onupdated}
注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。
- **类型**
```ts
function onUpdated(callback: () => void, target?: ComponentInternalInstance | null): void
```
- **详细信息**
父组件的更新钩子将在其子组件的更新钩子之后调用。
这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的,因为多个状态变更可以在同一个渲染周期中批量执行 (考虑到性能因素)。如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 [nextTick()](/api/general#nexttick) 作为替代。
**这个钩子在服务器端渲染期间不会被调用。**
:::warning
不要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环!
:::
- **示例**
访问更新后的 DOM
```vue
{{ count }}
```
## onUnmounted() {#onunmounted}
注册一个回调函数,在组件实例被卸载之后调用。
- **类型**
```ts
function onUnmounted(callback: () => void, target?: ComponentInternalInstance | null): void
```
- **详细信息**
一个组件在以下情况下被视为已卸载:
- 其所有子组件都已经被卸载。
- 所有相关的响应式作用 (渲染作用以及 `setup()` 时创建的计算属性和侦听器) 都已经停止。
可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。
**这个钩子在服务器端渲染期间不会被调用。**
- **示例**
```vue
```
## onBeforeMount() {#onbeforemount}
注册一个钩子,在组件被挂载之前被调用。
- **类型**
```ts
function onBeforeMount(callback: () => void, target?: ComponentInternalInstance | null): void
```
- **详细信息**
当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。
**这个钩子在服务器端渲染期间不会被调用。**
## onBeforeUpdate() {#onbeforeupdate}
注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。
- **类型**
```ts
function onBeforeUpdate(callback: () => void, target?: ComponentInternalInstance | null): void
```
- **详细信息**
这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。在这个钩子中更改状态也是安全的。
**这个钩子在服务器端渲染期间不会被调用。**
## onBeforeUnmount() {#onbeforeunmount}
注册一个钩子,在组件实例被卸载之前调用。
- **类型**
```ts
function onBeforeUnmount(callback: () => void, target?: ComponentInternalInstance | null): void
```
- **详细信息**
当这个钩子被调用时,组件实例依然还保有全部的功能。
**这个钩子在服务器端渲染期间不会被调用。**
## onErrorCaptured() {#onerrorcaptured}
注册一个钩子,在捕获了后代组件传递的错误时调用。
- **类型**
```ts
function onErrorCaptured(callback: ErrorCapturedHook): void
type ErrorCapturedHook = (
err: unknown,
instance: ComponentPublicInstance | null,
info: string
) => boolean | void
```
- **详细信息**
错误可以从以下几个来源中捕获:
- 组件渲染
- 事件处理器
- 生命周期钩子
- `setup()` 函数
- 侦听器
- 自定义指令钩子
- 过渡钩子
这个钩子带有三个实参:错误对象、触发该错误的组件实例,以及一个说明错误来源类型的信息字符串。
:::tip
在生产环境中,第三个参数 (`info`) 是一个缩短的代码,而不是含有完整信息的字符串。错误代码和字符串的映射可以参阅[生产环境错误代码参考](/error-reference/#runtime-errors)。
:::
你可以在 `errorCaptured()` 中更改组件状态来为用户显示一个错误状态。注意不要让错误状态再次渲染导致本次错误的内容,否则组件会陷入无限循环。
这个钩子可以通过返回 `false` 来阻止错误继续向上传递。请看下方的传递细节介绍。
**错误传递规则**
- 默认情况下,所有的错误都会被发送到应用级的 [`app.config.errorHandler`](/api/application#app-config-errorhandler) (前提是这个函数已经定义),这样这些错误都能在一个统一的地方报告给分析服务。
- 如果组件的继承链或组件链上存在多个 `errorCaptured` 钩子,对于同一个错误,这些钩子会被按从底至上的顺序一一调用。这个过程被称为“向上传递”,类似于原生 DOM 事件的冒泡机制。
- 如果 `errorCaptured` 钩子本身抛出了一个错误,那么这个错误和原来捕获到的错误都将被发送到 `app.config.errorHandler`。
- `errorCaptured` 钩子可以通过返回 `false` 来阻止错误继续向上传递。即表示“这个错误已经被处理了,应当被忽略”,它将阻止其他的 `errorCaptured` 钩子或 `app.config.errorHandler` 因这个错误而被调用。
## onRenderTracked() {#onrendertracked}
注册一个调试钩子,当组件渲染过程中追踪到响应式依赖时调用。
**这个钩子仅在开发模式下可用,且在服务器端渲染期间不会被调用。**
- **类型**
```ts
function onRenderTracked(callback: DebuggerHook): void
type DebuggerHook = (e: DebuggerEvent) => void
type DebuggerEvent = {
effect: ReactiveEffect
target: object
type: TrackOpTypes /* 'get' | 'has' | 'iterate' */
key: any
}
```
- **参考**[深入响应式系统](/guide/extras/reactivity-in-depth)
## onRenderTriggered() {#onrendertriggered}
注册一个调试钩子,当响应式依赖的变更触发了组件渲染时调用。
**这个钩子仅在开发模式下可用,且在服务器端渲染期间不会被调用。**
- **类型**
```ts
function onRenderTriggered(callback: DebuggerHook): void
type DebuggerHook = (e: DebuggerEvent) => void
type DebuggerEvent = {
effect: ReactiveEffect
target: object
type: TriggerOpTypes /* 'set' | 'add' | 'delete' | 'clear' */
key: any
newValue?: any
oldValue?: any
oldTarget?: Map | Set
}
```
- **参考**[深入响应式系统](/guide/extras/reactivity-in-depth)
## onActivated() {#onactivated}
注册一个回调函数,若组件实例是 [``](/api/built-in-components#keepalive) 缓存树的一部分,当组件被插入到 DOM 中时调用。
**这个钩子在服务器端渲染期间不会被调用。**
- **类型**
```ts
function onActivated(callback: () => void, target?: ComponentInternalInstance | null): void
```
- **参考**[指南 - 缓存实例的生命周期](/guide/built-ins/keep-alive#lifecycle-of-cached-instance)
## onDeactivated() {#ondeactivated}
注册一个回调函数,若组件实例是 [``](/api/built-in-components#keepalive) 缓存树的一部分,当组件从 DOM 中被移除时调用。
**这个钩子在服务器端渲染期间不会被调用。**
- **类型**
```ts
function onDeactivated(callback: () => void, target?: ComponentInternalInstance | null): void
```
- **参考**[指南 - 缓存实例的生命周期](/guide/built-ins/keep-alive#lifecycle-of-cached-instance)
## onServerPrefetch() {#onserverprefetch}
注册一个异步函数,在组件实例在服务器上被渲染之前调用。
- **类型**
```ts
function onServerPrefetch(callback: () => Promise): void
```
- **详细信息**
如果这个钩子返回了一个 Promise,服务端渲染会在渲染该组件前等待该 Promise 完成。
这个钩子仅会在服务端渲染中执行,可以用于执行一些仅存在于服务端的数据抓取过程。
- **示例**
```vue
```
- **参考**[服务端渲染](/guide/scaling-up/ssr)
---
---
url: /api/composition-api-helpers.md
---
# 组合式 API:辅助 {#composition-api-helpers}
## useAttrs() {#useattrs}
从 [Setup 上下文](/api/composition-api-setup#setup-context)中返回 `attrs` 对象,其中包含当前组件的[透传 attributes](/guide/components/attrs#fallthrough-attributes)。这是用于 `
```
- **参考**
- [指南 - 模板引用](/guide/essentials/template-refs)
- [指南 - 为模板引用标注类型](/guide/typescript/composition-api#typing-template-refs)
- [指南 - 为组件模板引用标注类型](/guide/typescript/composition-api#typing-component-template-refs)
## useId() {#useid}
用于为无障碍属性或表单元素生成每个应用内唯一的 ID。
- **类型**
```ts
function useId(): string
```
- **示例**
```vue
```
- **详细信息**
`useId()` 生成的每个 ID 在每个应用内都是唯一的。它可以用于为表单元素和无障碍属性生成 ID。在同一个组件中多次调用会生成不同的 ID;同一个组件的多个实例调用 `useId()` 也会生成不同的 ID。
`useId()` 生成的 ID 在服务器端和客户端渲染之间是稳定的,因此可以安全地在 SSR 应用中使用,不会导致激活不匹配。
如果同一页面上有多个 Vue 应用实例,可以通过 [`app.config.idPrefix`](/api/application#app-config-idprefix) 为每个应用提供一个 ID 前缀,以避免 ID 冲突。
---
---
url: /guide/reusability/composables.md
---
# 组合式函数 {#composables}
:::tip
此章节假设你已经对组合式 API 有了基本的了解。如果你只学习过选项式 API,你可以使用左侧边栏上方的切换按钮将 API 风格切换为组合式 API 后,重新阅读[响应性基础](/guide/essentials/reactivity-fundamentals)和[生命周期钩子](/guide/essentials/lifecycle)两个章节。
:::
## 什么是“组合式函数”? {#what-is-a-composable}
在 Vue 应用的概念中,“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用**有状态逻辑**的函数。
当构建前端应用时,我们常常需要复用公共任务的逻辑。例如为了在不同地方格式化时间,我们可能会抽取一个可复用的日期格式化函数。这个函数封装了**无状态的逻辑**:它在接收一些输入后立刻返回所期望的输出。复用无状态逻辑的库有很多,比如你可能已经用过的 [lodash](https://lodash.com/) 或是 [date-fns](https://date-fns.org/)。
相比之下,有状态逻辑负责管理会随时间而变化的状态。一个简单的例子是跟踪当前鼠标在页面中的位置。在实际应用中,也可能是像触摸手势或与数据库的连接状态这样的更复杂的逻辑。
## 鼠标跟踪器示例 {#mouse-tracker-example}
如果我们要直接在组件中使用组合式 API 实现鼠标跟踪功能,它会是这样的:
```vue
Mouse position is at: {{ x }}, {{ y }}
```
但是,如果我们想在多个组件中复用这个相同的逻辑呢?我们可以把这个逻辑以一个组合式函数的形式提取到外部文件中:
```js
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
// 被组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// 一个组合式函数也可以挂靠在所属组件的生命周期上
// 来启动和卸载副作用
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 通过返回值暴露所管理的状态
return { x, y }
}
```
下面是它在组件中使用的方式:
```vue
Mouse position is at: {{ x }}, {{ y }}
```
Mouse position is at: {{ x }}, {{ y }}
[在演练场中尝试一下](https://play.vuejs.org/#eNqNkj1rwzAQhv/KocUOGKVzSAIdurVjoQUvJj4XlfgkJNmxMfrvPcmJkkKHLrbu69H7SlrEszFyHFDsxN6drDIeHPrBHGtSvdHWwwKDwzfNHwjQWd1DIbd9jOW3K2qq6aTJxb6pgpl7Dnmg3NS0365YBnLgsTfnxiNHACvUaKe80gTKQeN3sDAIQqjignEhIvKYqMRta1acFVrsKtDEQPLYxuU7cV8Msmg2mdTilIa6gU5p27tYWKKq1c3ENphaPrGFW25+yMXsHWFaFlfiiOSvFIBJjs15QJ5JeWmaL/xYS/Mfpc9YYrPxl52ULOpwhIuiVl9k07Yvsf9VOY+EtizSWfR6xKK6itgkvQ/+fyNs6v4XJXIsPwVL+WprCiL8AEUxw5s=)
如你所见,核心逻辑完全一致,我们做的只是把它移到一个外部函数中去,并返回需要暴露的状态。和在组件中一样,你也可以在组合式函数中使用所有的[组合式 API](/api/#composition-api)。现在,`useMouse()` 的功能可以在任何组件中轻易复用了。
更酷的是,你还可以嵌套多个组合式函数:一个组合式函数可以调用一个或多个其他的组合式函数。这使得我们可以像使用多个组件组合成整个应用一样,用多个较小且逻辑独立的单元来组合形成复杂的逻辑。实际上,这正是为什么我们决定将实现了这一设计模式的 API 集合命名为组合式 API。
举例来说,我们可以将添加和清除 DOM 事件监听器的逻辑也封装进一个组合式函数中:
```js
// event.js
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(target, event, callback) {
// 如果你想的话,
// 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素
onMounted(() => target.addEventListener(event, callback))
onUnmounted(() => target.removeEventListener(event, callback))
}
```
有了它,之前的 `useMouse()` 组合式函数可以被简化为:
```js{3,9-12}
// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'
export function useMouse() {
const x = ref(0)
const y = ref(0)
useEventListener(window, 'mousemove', (event) => {
x.value = event.pageX
y.value = event.pageY
})
return { x, y }
}
```
:::tip
每一个调用 `useMouse()` 的组件实例会创建其独有的 `x`、`y` 状态拷贝,因此他们不会互相影响。如果你想要在组件之间共享状态,请阅读[状态管理](/guide/scaling-up/state-management)这一章。
:::
## 异步状态示例 {#async-state-example}
`useMouse()` 组合式函数没有接收任何参数,因此让我们再来看一个需要接收一个参数的组合式函数示例。在做异步数据请求时,我们常常需要处理不同的状态:加载中、加载成功和加载失败。
```vue
Oops! Error encountered: {{ error.message }}
Loading...
```
如果在每个需要获取数据的组件中都要重复这种模式,那就太繁琐了。让我们把它抽取成一个组合式函数:
```js
// fetch.js
import { ref } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
fetch(url)
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
return { data, error }
}
```
现在我们在组件里只需要:
```vue
```
### 接收响应式状态 {#accepting-reactive-state}
`useFetch()` 接收一个静态 URL 字符串作为输入——因此它只会执行一次 fetch 并且就此结束。如果我们想要在 URL 改变时重新 fetch 呢?为了实现这一点,我们需要将响应式状态传入组合式函数,并让它基于传入的状态来创建执行操作的侦听器。
举例来说,`useFetch()` 应该能够接收一个 ref:
```js
const url = ref('/initial-url')
const { data, error } = useFetch(url)
// 这将会重新触发 fetch
url.value = '/new-url'
```
或者接收一个 [getter 函数](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/get#description):
```js
// 当 props.id 改变时重新 fetch
const { data, error } = useFetch(() => `/posts/${props.id}`)
```
我们可以用 [`watchEffect()`](/api/reactivity-core.html#watcheffect) 和 [`toValue()`](/api/reactivity-utilities.html#tovalue) API 来重构我们现有的实现:
```js{8,13}
// fetch.js
import { ref, watchEffect, toValue } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const fetchData = () => {
// reset state before fetching..
data.value = null
error.value = null
fetch(toValue(url))
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
}
watchEffect(() => {
fetchData()
})
return { data, error }
}
```
`toValue()` 是一个在 3.3 版本中新增的 API。它的设计目的是将 ref 或 getter 规范化为值。如果参数是 ref,它会返回 ref 的值;如果参数是函数,它会调用函数并返回其返回值。否则,它会原样返回参数。它的工作方式类似于 [`unref()`](/api/reactivity-utilities.html#unref),但对函数有特殊处理。
注意 `toValue(url)` 是在 `watchEffect` 回调函数的**内部**调用的。这确保了在 `toValue()` 规范化期间访问的任何响应式依赖项都会被侦听器跟踪。
这个版本的 `useFetch()` 现在能接收静态 URL 字符串、ref 和 getter,使其更加灵活。watch effect 会立即运行,并且会跟踪 `toValue(url)` 期间访问的任何依赖项。如果没有跟踪到依赖项 (例如 url 已经是字符串),则 effect 只会运行一次;否则,它将在跟踪到的任何依赖项更改时重新运行。
这是[更新后的 `useFetch()`](https://play.vuejs.org/#eNp9Vdtu20YQ/ZUpUUA0qpAOjL4YktCbC7Rom8BN8sSHrMihtfZql9iLZEHgv2dml6SpxMiDIWkuZ+acmR2fs1+7rjgEzG6zlaut7Dw49KHbVFruO2M9nMFiu4Ta7LvgsYEeWmv2sKCkxSwoOPwTfb2b/EU5mopHR5GVro12HrbC4UerYA2Lnfeduy3LR2d0p0SNO6MatIU/dbI2DRZUtPSmMa4kgJQuG8qkjvLF28XVaAwRb2wxz69gvZkK/UQ5xUGogBQ/ZpyhEV4sAa01lnpeTwRyApsFWvT2RO6Eea40THBMgfq6NLwlS1/pVZnUJB3ph8c98fNIvwD+MaKBzkQut2xYbYP3RsPhTWvsusokSA0/Vxn8UitZP7GFSX/+8Sz7z1W2OZ9BQt+vypQXS1R+1cgDQciW4iMrimR0wu8270znfoC7SBaJWdAeLTa3QFgxuNijc+IBIy5PPyYOjU19RDEI954/Z/UptKTy6VvqA5XD1AwLTTl/0Aco4s5lV51F5sG+VJJ+v4qxYbmkfiiKYvSvyknPbJnNtoyW+HJpj4Icd22LtV+CN5/ikC4XuNL4HFPaoGsvie3FIqSJp1WIzabl00HxkoyetEVfufhv1kAu3EnX8z0CKEtKofcGzhMb2CItAELL1SPlFMV1pwVj+GROc/vWPoc26oDgdxhfSArlLnbWaBOcOoEzIP3CgbeifqLXLRyICaDBDnVD+3KC7emCSyQ4sifspOx61Hh4Qy/d8BsaOEdkYb1sZS2FoiJKnIC6FbqhsaTVZfk8gDgK6cHLPZowFGUzAQTNWl/BUSrFbzRYHXmSdeAp28RMsI0fyFDaUJg9Spd0SbERZcvZDBRleCPdQMCPh8ARwdRRnBCTjGz5WkT0i0GlSMqixTR6VKyHmmWEHIfV+naSOETyRx8vEYwMv7pa8dJU+hU9Kz2t86ReqjcgaTzCe3oGpEOeD4uyJOcjTXe+obScHwaAi82lo9dC/q/wuyINjrwbuC5uZrS4WAQeyTN9ftOXIVwy537iecoX92kR4q/F1UvqIMsSbq6vo5XF6ekCeEcTauVDFJpuQESvMv53IBXadx3r4KqMrt0w0kwoZY5/R5u3AZejvd5h/fSK/dE9s63K3vN7tQesssnnhX1An9x3//+Hz/R9cu5NExRFf8d5zyIF7jGF/RZ0Q23P4mK3f8XLRmfhg7t79qjdSIobjXLE+Cqju/b7d6i/tHtT3MQ8VrH/Ahstp5A=),为了便于演示,添加了人为延迟和随机错误。
## 约定和最佳实践 {#conventions-and-best-practices}
### 命名 {#naming}
组合式函数约定用驼峰命名法命名,并以“use”作为开头。
### 输入参数 {#input-arguments}
即便不依赖于 ref 或 getter 的响应性,组合式函数也可以接收它们作为参数。如果你正在编写一个可能被其他开发者使用的组合式函数,最好处理一下输入参数是 ref 或 getter 而非原始值的情况。可以利用 [`toValue()`](/api/reactivity-utilities#tovalue) 工具函数来实现:
```js
import { toValue } from 'vue'
function useFeature(maybeRefOrGetter) {
// 如果 maybeRefOrGetter 是一个 ref 或 getter,
// 将返回它的规范化值。
// 否则原样返回。
const value = toValue(maybeRefOrGetter)
}
```
如果你的组合式函数在输入参数是 ref 或 getter 的情况下创建了响应式 effect,为了让它能够被正确追踪,请确保要么使用 `watch()` 显式地监视 ref 或 getter,要么在 `watchEffect()` 中调用 `toValue()`。
[前面讨论过的 useFetch() 实现](#accepting-reactive-state)提供了一个接受 ref、getter 或普通值作为输入参数的组合式函数的具体示例。
### 返回值 {#return-values}
你可能已经注意到了,我们一直在组合式函数中使用 `ref()` 而不是 `reactive()`。我们推荐的约定是组合式函数始终返回一个包含多个 ref 的普通的非响应式对象,这样该对象在组件中被解构为 ref 之后仍可以保持响应性:
```js
// x 和 y 是两个 ref
const { x, y } = useMouse()
```
从组合式函数返回一个响应式对象会导致在对象解构过程中丢失与组合式函数内状态的响应性连接。与之相反,ref 则可以维持这一响应性连接。
如果你更希望以对象属性的形式来使用组合式函数中返回的状态,你可以将返回的对象用 `reactive()` 包装一次,这样其中的 ref 会被自动解包,例如:
```js
const mouse = reactive(useMouse())
// mouse.x 链接到了原来的 x ref
console.log(mouse.x)
```
```vue-html
Mouse position is at: {{ mouse.x }}, {{ mouse.y }}
```
### 副作用 {#side-effects}
在组合式函数中的确可以执行副作用 (例如:添加 DOM 事件监听器或者请求数据),但请注意以下规则:
- 如果你的应用用到了[服务端渲染](/guide/scaling-up/ssr) (SSR),请确保在组件挂载后才调用的生命周期钩子中执行 DOM 相关的副作用,例如:`onMounted()`。这些钩子仅会在浏览器中被调用,因此可以确保能访问到 DOM。
- 确保在 `onUnmounted()` 时清理副作用。举例来说,如果一个组合式函数设置了一个事件监听器,它就应该在 `onUnmounted()` 中被移除 (就像我们在 `useMouse()` 示例中看到的一样)。当然也可以像之前的 `useEventListener()` 示例那样,使用一个组合式函数来自动帮你做这些事。
### 使用限制 {#usage-restrictions}
组合式函数只能在 `
```
在某种程度上,你可以将这些提取出的组合式函数看作是可以相互通信的组件范围内的服务。
## 在选项式 API 中使用组合式函数 {#using-composables-in-options-api}
如果你正在使用选项式 API,组合式函数必须在 `setup()` 中调用。且其返回的绑定必须在 `setup()` 中返回,以便暴露给 `this` 及其模板:
```js
import { useMouse } from './mouse.js'
import { useFetch } from './fetch.js'
export default {
setup() {
const { x, y } = useMouse()
const { data, error } = useFetch('...')
return { x, y, data, error }
},
mounted() {
// setup() 暴露的属性可以在通过 `this` 访问到
console.log(this.x)
}
// ...其他选项
}
```
## 与其他模式的比较 {#comparisons-with-other-techniques}
### 和 Mixin 的对比 {#vs-mixins}
Vue 2 的用户可能会对 [mixins](/api/options-composition#mixins) 选项比较熟悉。它也让我们能够把组件逻辑提取到可复用的单元里。然而 mixins 有三个主要的短板:
1. **不清晰的数据来源**:当使用了多个 mixin 时,实例上的数据属性来自哪个 mixin 变得不清晰,这使追溯实现和理解组件行为变得困难。这也是我们推荐在组合式函数中使用 ref + 解构模式的理由:让属性的来源在消费组件时一目了然。
2. **命名空间冲突**:多个来自不同作者的 mixin 可能会注册相同的属性名,造成命名冲突。若使用组合式函数,你可以通过在解构变量时对变量进行重命名来避免相同的键名。
3. **隐式的跨 mixin 交流**:多个 mixin 需要依赖共享的属性名来进行相互作用,这使得它们隐性地耦合在一起。而一个组合式函数的返回值可以作为另一个组合式函数的参数被传入,像普通函数那样。
基于上述理由,我们不再推荐在 Vue 3 中继续使用 mixin。保留该功能只是为了项目迁移的需求和照顾熟悉它的用户。
### 和无渲染组件的对比 {#vs-renderless-components}
在组件插槽一章中,我们讨论过了基于作用域插槽的[无渲染组件](/guide/components/slots#renderless-components)。我们甚至用它实现了一样的鼠标追踪器示例。
组合式函数相对于无渲染组件的主要优势是:组合式函数不会产生额外的组件实例开销。当在整个应用中使用时,由无渲染组件产生的额外组件实例会带来无法忽视的性能开销。
我们推荐在纯逻辑复用时使用组合式函数,在需要同时复用逻辑和视图布局时使用无渲染组件。
### 和 React Hooks 的对比 {#vs-react-hooks}
如果你有 React 的开发经验,你可能注意到组合式函数和自定义 React hooks 非常相似。组合式 API 的一部分灵感正来自于 React hooks,Vue 的组合式函数也的确在逻辑组合能力上与 React hooks 相近。然而,Vue 的组合式函数是基于 Vue 细粒度的响应性系统,这和 React hooks 的执行模型有本质上的不同。这一话题在[组合式 API 的常见问题](/guide/extras/composition-api-faq#comparison-with-react-hooks)中有更细致的讨论。
## 延伸阅读 {#further-reading}
- [深入响应性原理](/guide/extras/reactivity-in-depth):理解 Vue 响应性系统的底层细节。
- [状态管理](/guide/scaling-up/state-management):多个组件间共享状态的管理模式。
- [测试组合式函数](/guide/scaling-up/testing#testing-composables):组合式函数的单元测试技巧。
- [VueUse](https://vueuse.org/):一个日益增长的 Vue 组合式函数集合。源代码本身就是一份不错的学习资料。
---
---
url: /api/options-composition.md
---
# 组合选项 {#options-composition}
## provide {#provide}
用于提供可以被后代组件注入的值。
- **类型**
```ts
interface ComponentOptions {
provide?: object | ((this: ComponentPublicInstance) => object)
}
```
- **详细信息**
`provide` 和 [`inject`](#inject) 通常成对一起使用,使一个祖先组件作为其后代组件的依赖注入方,无论这个组件的层级有多深都可以注入成功,只要他们处于同一条组件链上。
这个 `provide` 选项应当是一个对象或是返回一个对象的函数。这个对象包含了可注入其后代组件的属性。你可以在这个对象中使用 Symbol 类型的值作为 key。
- **示例**
基本使用方式:
```js
const s = Symbol()
export default {
provide: {
foo: 'foo',
[s]: 'bar'
}
}
```
使用函数可以提供其组件中的状态:
```js
export default {
data() {
return {
msg: 'foo'
}
}
provide() {
return {
msg: this.msg
}
}
}
```
请注意,针对上面这个例子,所供给的 `msg` 将**不会**是响应式的。请查看[和响应式数据配合使用](/guide/components/provide-inject#working-with-reactivity)一节获取更多细节。
- **参考**[依赖注入](/guide/components/provide-inject)
## inject {#inject}
用于声明要通过从上层提供方匹配并注入进当前组件的属性。
- **类型**
```ts
interface ComponentOptions {
inject?: ArrayInjectOptions | ObjectInjectOptions
}
type ArrayInjectOptions = string[]
type ObjectInjectOptions = {
[key: string | symbol]:
| string
| symbol
| { from?: string | symbol; default?: any }
}
```
- **详细信息**
该 `inject` 选项应该是以下两种之一:
- 一个字符串数组
- 一个对象,其 key 名就是在当前组件中的本地绑定名称,而它的值应该是以下两种之一:
- 匹配可用注入的 key (string 或者 Symbol)
- 一个对象
- 它的 `from` 属性是一个 key (string 或者 Symbol),用于匹配可用的注入
- 它的 `default` 属性用作候补值。和 props 的默认值类似,如果它是一个对象,那么应该使用一个工厂函数来创建,以避免多个组件共享同一个对象。
如果没有供给相匹配的属性、也没有提供默认值,那么注入的属性将为 `undefined`。
请注意,注入绑定并非响应式的。这是有意为之的一个设计。如果要注入的值是一个响应式对象,那么这个对象上的属性将会保留响应性。请看[配合响应性](/guide/components/provide-inject#working-with-reactivity)一节获取更多细节。
- **示例**
基本使用方式:
```js
export default {
inject: ['foo'],
created() {
console.log(this.foo)
}
}
```
使用注入的值作为 props 的默认值:
```js
const Child = {
inject: ['foo'],
props: {
bar: {
default() {
return this.foo
}
}
}
}
```
使用注入的值作为 data:
```js
const Child = {
inject: ['foo'],
data() {
return {
bar: this.foo
}
}
}
```
注入项可以选择是否带有默认值:
```js
const Child = {
inject: {
foo: { default: 'foo' }
}
}
```
如果需要从不同名字的属性中注入,请使用 `from` 指明来源属性。
```js
const Child = {
inject: {
foo: {
from: 'bar',
default: 'foo'
}
}
}
```
和 props 默认值类似,对于非原始数据类型的值,你需要使用工厂函数:
```js
const Child = {
inject: {
foo: {
from: 'bar',
default: () => [1, 2, 3]
}
}
}
```
- **参考**[依赖注入](/guide/components/provide-inject)
## mixins {#mixins}
一个包含组件选项对象的数组,这些选项都将被混入到当前组件的实例中。
- **类型**
```ts
interface ComponentOptions {
mixins?: ComponentOptions[]
}
```
- **详细信息**
`mixins` 选项接受一个 mixin 对象数组。这些 mixin 对象可以像普通的实例对象一样包含实例选项,它们将使用一定的选项合并逻辑与最终的选项进行合并。举例来说,如果你的 mixin 包含了一个 `created` 钩子,而组件自身也有一个,那么这两个函数都会被调用。
Mixin 钩子的调用顺序与提供它们的选项顺序相同,且会在组件自身的钩子前被调用。
:::warning 不再推荐
在 Vue 2 中,mixins 是创建可重用组件逻辑的主要方式。尽管在 Vue 3 中保留了 mixins 支持,但对于组件间的逻辑复用,[使用组合式 API 的组合式函数](/guide/reusability/composables)是现在更推荐的方式。
:::
- **示例**
```js
const mixin = {
created() {
console.log(1)
}
}
createApp({
created() {
console.log(2)
},
mixins: [mixin]
})
// => 1
// => 2
```
## extends {#extends}
要继承的“基类”组件。
- **类型**
```ts
interface ComponentOptions {
extends?: ComponentOptions
}
```
- **详细信息**
使一个组件可以继承另一个组件的组件选项。
从实现角度来看,`extends` 几乎和 `mixins` 相同。通过 `extends` 指定的组件将会当作第一个 mixin 来处理。
然而,`extends` 和 `mixins` 表达的是不同的目标。`mixins` 选项基本用于组合功能,而 `extends` 则一般更关注继承关系。
同 `mixins` 一样,所有选项 (`setup()` 除外) 都将使用相关的策略进行合并。
- **示例**
```js
const CompA = { ... }
const CompB = {
extends: CompA,
...
}
```
:::warning 不建议用于组合式 API
`extends` 是为选项式 API 设计的,不会处理 `setup()` 钩子的合并。
在组合式 API 中,逻辑复用的首选模式是“组合”而不是“继承”。如果一个组件中的逻辑需要复用,考虑将相关逻辑提取到[组合式函数](/guide/reusability/composables#composables)中。
如果你仍然想要通过组合式 API 来“继承”一个组件,可以在继承组件的 `setup()` 中调用基类组件的 `setup()`:
```js
import Base from './Base.js'
export default {
extends: Base,
setup(props, ctx) {
return {
...Base.setup(props, ctx),
// 本地绑定
}
}
}
```
:::
---
---
url: /api/compile-time-flags.md
---
# 编译时标志 {#compile-time-flags}
:::tip
编译时标志仅在使用 Vue 的 `esm-bundler` 构建版本时生效 (即 `vue/dist/vue.esm-bundler.js`)。
:::
当以带有构建步骤的方式使用 Vue 时,可以配置一些编译时标志以启用/禁用特定的功能。使用编译时标志的好处是,以这种方式禁用的功能可以通过 tree-shaking 从最终的打包结果中移除。
即使没有显式地配置这些标志,Vue 也会正常工作。然而,建议始终对它们进行配置,以便在可能的情况下正确地删除相关功能。
请参考[配置指南](#configuration-guides)来了解如何根据你的构建工具进行配置。
## `__VUE_OPTIONS_API__` {#VUE_OPTIONS_API}
- **默认值:**`true`
启用/禁用选项式 API 支持。禁用此功能将减小打包结果的体积,但如果第三方库依赖选项式 API,则可能影响兼容性。
## `__VUE_PROD_DEVTOOLS__` {#VUE_PROD_DEVTOOLS}
- **默认值:**`false`
在生产环境中启用/禁用开发者工具支持。启用会在打包结果中包含更多代码,因此建议仅在调试时启用此功能。
## `__VUE_PROD_HYDRATION_MISMATCH_DETAILS__` {#VUE_PROD_HYDRATION_MISMATCH_DETAILS}
- **默认值:**`false`
启用/禁用生产环境构建下激活 (hydration) 不匹配的详细警告。启用会在打包结果中包含更多代码,因此建议仅在调试时启用此功能。
- 仅在 3.4+ 中可用
## 配置指南 {#configuration-guides}
### Vite {#vite}
`@vitejs/plugin-vue` 会自动为这些标志提供默认值。要更改默认值,请使用 Vite 的 [`define` 配置项](https://vitejs.dev/config/shared-options.html#define):
```js
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
define: {
// 启用生产环境构建下激活不匹配的详细警告
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'true'
}
})
```
### vue-cli {#vue-cli}
`@vue/cli-service` 自动为其中一些标志提供默认值。要配置/修改这些值:
```js
// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.plugin('define').tap((definitions) => {
Object.assign(definitions[0], {
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false'
})
return definitions
})
}
}
```
### webpack {#webpack}
应该使用 webpack 的 [DefinePlugin](https://webpack.js.org/plugins/define-plugin/) 定义这些标志:
```js
// webpack.config.js
module.exports = {
// ...
plugins: [
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false'
})
]
}
```
### Rollup {#rollup}
应该使用 [@rollup/plugin-replace](https://github.com/rollup/plugins/tree/master/packages/replace) 定义这些标志:
```js
// rollup.config.js
import replace from '@rollup/plugin-replace'
export default {
plugins: [
replace({
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false'
})
]
}
```
---
---
url: /about/translation.md
---
# 翻译说明
该中文文档翻译由 [@ShenQingchuan](https://github.com/ShenQingchuan) 个人发起,随后作为 Vue 官方认可的中文翻译仓库,以团队的形式进行官方维护。最新的代码仓库链接是:https://github.com/vuejs-translations/docs-zh-cn
## 翻译须知
请移步至官方仓库的 [wiki 页面](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E7%BF%BB%E8%AF%91%E9%A1%BB%E7%9F%A5)查阅。
## 协作指南
请移步至官方仓库的 [wiki 页面](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E5%8D%8F%E4%BD%9C%E6%8C%87%E5%8D%97)查阅。
> 编写文档是一种换位思考的练习。我们并不是在描述客观现实,那是源代码已经做到了的。我们的工作是帮助塑造用户与 Vue 生态系统之间的关系。
原版翻译说明,仅供备忘和归档
## 基本原则
翻译工作追求的无外乎 “信、达、雅” 三个字,因此我们总结了以下原则:
1. **忠实原文,通俗易懂**,保证正确是最基本的要求。此外,还应该尽可能将一些特定概念降维,使得入门级读者也能够流畅阅读。
2. **中文词汇优先,特殊概念次之**:要尽可能地将文档中的英语单词译作读者好理解的词汇。
同时用词应尽可能地从前端开发领域已有的词汇中衍生。我们认为作为 Vue 文档的译者应承担这样一种职责:避免创建一套独立于标准 JavaScript 环境之外、Vue 专属的语境。
但也有例外的情况,某些英文单词我们倾向于选择不翻译、用原词。开发者常常与一部分英语单词打交道,许多英语单词甚至作为了开发框架或操作系统的专有名词,直接抛出这个单词也的确能够帮助用户更好的理解、锁定所讲的是什么概念。
3. **更符合中文的表述方式**:我们必须正视英语和中文本身的差异与不同,由于表达方式和语法结构的区别,常常一个结构复杂的多重定语从句很难逐字逐词地直译成中文,翻译出的句子应符合母语者的叙述习惯,即尽可能避免英语式的倒装(哪怕讲述方式与作者原文有较大区别),表述尽可能口语化。最好的方式应该是将视线从单个句子中移出来,结合上下文先进行理解再用中文的习惯性表达将其重新复述出来。
## 格式规范
### 提交规范
可以参考 [这个网站](https://www.conventionalcommits.org/) 了解提交信息的既定书写格式:
```text
():
^-------------^ ^-------^
| |
| +-> 主题。总结 commit 内容,用现在时书写。
|
+-------> 目的: chore, docs, feat, fix, refactor, style, 或 test。 为可选项。
// 以下是 body 部分,这部分是可选的:
hash: (对应到官方英文文档的某次更新 commit hash)
time: (由 `new Date().toLocaleString()` 生成的时间戳)
```
- 如果你贡献提交的目的并不是与官方英文文档同步内容相关,为 `chore` 或其他类型,body 部分可以省略。
- body 部分的信息只是为了在特定情况下方便溯源。
#### 释义
- feat: (新功能,面向用户)
- fix: (bug 修复,面向用户)
- docs: (编辑文档)
- style: (格式,如全角半角;对生产环境没有影响)
- refactor: (比如重命名变量)
- test: (加入缺少的测试,对生产环境没有影响)
- chore: (更新依赖等,对生产环境没有影响)
### 文档格式规范
#### 译注写法
1. 在原文需要加译者注的位置添加角标:
```html
... [[1]](#footnote-1) ... [[2]](#footnote-2) ...
```
2. 在文章最末尾加入译者注的内容,格式如下:
```html
__译者注__
[1] ... [2] ...
[3] ...
```
#### 标点符号
- 逗号、句号、分号、冒号、叹号、问号,统一使用全角字符:,。;:!?
- 破折号使用:——
- 引号统一使用 “ ” 和 ‘ ’
- 括号统一使用全角括号 ()
- 非注释部分的代码除外,保留英文标点符号。
#### 内联代码或代码关键字
- 务必用反引号(即英文输入法下,按键盘上 Tab 键上方的那个键)将内容括起来。
- 包括代码注释中出现代码或代码关键字时,也要括起来。
#### 空格的使用
- 英文单词和英文单词之间要有一个空格
`something in English`
- 中文和英文单词之间要有一个空格
`中文当中有 something 是英文`
- 英文单词和标点符号之间没有空格
`这里是一句中文,something 又是英文`
#### 链接、斜体、粗体与行内代码等
对于 Markdown 中上述的行内简单样式,为了保证 Vitepress 中良好的渲染效果,我们提倡在文档中使用如下的格式:
```markdown
这是一个 [链接](https://github.com/vitejs/vite) 指向 Vite 官方仓库
这是一个 **加粗** 的文字
这是一个 _斜体_ 的文字
这是一个 _斜体_ 的文字
这是一个 `code` 行内代码
假如后面就是标点符号 `code`:
```
你可能已经注意到,默认情况下,在两端我们都加上了空格。
**此处的某些规则可能暂时和旧有的 [Vue.js 中文文档的风格](https://github.com/vuejs/cn.vuejs.org/wiki) 不太一致**,如果你曾参与过 Vue 中文文档相关工作,可能与你的习惯有一定区别。
这是为了保证文档视图中不会出现字符靠太近而黏合的问题。
关于文档中的链接,针对以下两种 Markdown 书写:
```markdown
Vite 支持了一套 [通用插件 API](./api-plugin) 扩展了 Rollup 的插件接口
Vite 支持了一套[通用插件 API](./api-plugin)扩展了 Rollup 的插件接口
```
Vitepress 和 Vuepress 中对以上两种写法的渲染视觉效果为:
**链接前后带空格**

**链接前后不带空格**

不带空格的形式 与 带空格相比,没有那么突出。
同样这类情况还包括 Markdown 中的斜体字:
```markdown
这是一个_斜体_尝试
这是一个*斜体*尝试
这是一个 *斜体* 尝试
```
下面是效果,不带空格的情况看上去中文字体的笔画之间会接在一起,变得很拥挤,观感较差。

#### 关于加粗和斜体格式的约定
根据 [GitHub Flavored Markdown Spec](https://github.github.com/gfm/#emphasis-and-strong-emphasis),用成对的星号或下划线都可以用来代表加粗或斜体,但是使用下划线的时候存在更多的特殊条件限制,例如:
> `5*6*78` → `56 78
` https://github.github.com/gfm/#example-346
>
> `5_6_78` → `5_6_78
` https://github.github.com/gfm/#example-351
经过讨论,考虑到 GFM 的规范以及中文的特殊情况,决定:
- 中文翻译统一使用星号来标注加粗和斜体,而不是使用下划线,同时尊重英文版自身的用法。
- 仍然不能正确渲染的地方,允许适当调整包含或不包含加粗或斜体部分两侧的标点符号。参见 [这个例子](https://github.com/vuejs/composition-api-rfc/pull/30/files)。
- 仍然不能正确渲染的地方,手动使用 `` 或 `` 标记。
## 术语翻译参考
| 英文 | 建议翻译 | 备注 |
| --- | --- | --- |
| property | 属性 | 组件的属性(数据、计算属性等) |
| attribute | _不翻译_ | 特指 HTML 元素上的属性 |
| getter | _一般不翻译_ | 计算属性中作计算函数 |
| setter | _一般不翻译_ | 计算属性中作设置函数 |
| prop | _不翻译_ | |
| ref | _不翻译_ | |
| feature/functionality | 功能 | |
| directive | 指令 | |
| mixin | 混入 | |
| listen/listener | 监听/监听器 | |
| observe/observer | 侦听/侦听器 | |
| watch/watcher | 侦听/侦听器 | |
| normalize (HTML code, ...) | 规范化 | |
| standardize | 标准化 | |
| fire/trigger (事件) | 触发 | |
| emit (某个值或事件) | 抛出 | |
| queue (v.) | 把……加入队列 | |
| workaround (n.) | 变通办法 | |
| workaround (v.) | 绕过 | |
| convention | 约定 | |
| parse | 解析 | |
| stringify | 字符串化 | |
| side effect | 副作用 | |
| declarative | 声明式 | |
| imperative | 命令式 | |
| handler | 处理函数 | |
| you | 你 (而不用 “您”) | |
| computed | 计算属性 | |
| computed property | 计算属性 | |
| guard | 守卫 | |
| hook | 钩子 | |
| selector | 选择器 | |
| truthy | 真值 | 需加 MDN 的解释作为译注 |
| falsy | 假值 | 需加 MDN 的解释作为译注 |
| mutate/mutation | 变更 | |
| immutable | 不可变 | |
| mutable | 可变 | |
- MDN - `truthy` → https://developer.mozilla.org/en-US/docs/Glossary/Truthy
- MDN - `falsy` → https://developer.mozilla.org/en-US/docs/Glossary/Falsy
## 工作流
### 更新内容同步策略
此中文文档由 [印记中文](https://docschina.org/) 团队进行翻译,它们也是 Vite 官方中文文档背后的翻译维护团队。
[QC-L](https://github.com/QC-L) 曾在 Vue 文档的讨论区提出过这套 [中英文档同步工作流](https://github.com/vuejs/docs-next-zh-cn/discussions/522#discussioncomment-779521),这也是 Vite 官方中文文档正在使用的一套工作流。
- 保留英文文档的原始 commit 记录,以保证可以对后续的更新进行再翻译、合并
- 由于 Vue 文档以 Markdown 书写,每一行成一个自然段。因此在 Markdown 文档中原则上应该保证中英文行号一一对应,以保证后续更新时位置不发生错乱
- 由机器人每日定时从英文文档仓库同步新的提交,并生成 Pull Request 交由翻译团队 Review、翻译并最终合入中文文档
### 锚点链接的统一化
:::tip 插件支持
我们提供了一个包含此项功能的 [Vue 官方文档翻译助手插件](https://marketplace.visualstudio.com/items?itemName=shenqingchuan.vue-docs-tr-helper),你可以在 VSCode 中安装,并遵照 README 的指引来使用。
:::
在 Markdown 文档中 `[title](link)` 形式的链接非常常用,而 Vue 文档中大量使用了这一语法,用来作章节的跳转。
链接中有时还会带有锚点(以 `#` 作前缀)用来定位到页面的对应位置,例如 `[props 大小写格式](/guide/components/props.html#prop-name-casing)`。
但是在 VitePress 中,由于锚点是对应 Markdown 内容中的 “标题行” 的,因此若改动了英文内容的标题行,别处引用此处的锚点就是失效了:
```markdown
## Props name casing
## Props 大小写格式
[props 大小写格式](/guide/components/props.html#prop-name-casing)
```
若将链接中的锚点也改为中文内容的确可以暂时解决问题,但若后续该标题有改动,又需要修改所有引用了该锚点的地方,可维护性较差。
因此我们提供了一种特殊的锚点标记:
```markdown
## Props 大小写格式 {#props-name-casing}
```
我们会为 VitePress 提供处理这个标记的逻辑,保证它不会在页面上显示出来。
但也有需要注意的例外情况:若按上面的方式为一篇文章的所有标题行都生成了标记,但文章中出现了两个相同的标记,比如 “类和 CSS 样式” 章节中的 “绑定对象” 小节,可以为其加上数字标记,保证其在文章中的唯一性。
此外,由于文章的总标题也被加上了锚点标记,导致在开发环境下,浏览器的标签页上会看到标记。但在构建发布时,我们运行了一个脚本,为文档的 frontmatter 中添加了不含标记的 `title`,因此读者将不会看到该标记。
---
---
url: /api/custom-elements.md
---
# 自定义元素 API {#custom-elements-api}
## defineCustomElement() {#definecustomelement}
此方法接受的参数与 [`defineComponent`](#definecomponent) 相同,但返回一个原生[自定义元素](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_custom_elements)类构造函数。
- **类型**
```ts
function defineCustomElement(
component:
| (ComponentOptions & CustomElementsOptions)
| ComponentOptions['setup'],
options?: CustomElementsOptions
): {
new (props?: object): HTMLElement
}
interface CustomElementsOptions {
styles?: string[]
// 以下选项在 3.5+ 版本中支持
configureApp?: (app: App) => void
shadowRoot?: boolean
nonce?: string
}
```
> 类型为简化版,便于阅读。
- **详情**
除了常规的组件选项,`defineCustomElement()` 还支持一系列特定于自定义元素的选项:
- **`styles`**:一个内联 CSS 字符串数组,用于提供应注入元素 shadow root 的 CSS。
- **`configureApp`** :一个函数,可用于配置自定义元素的 Vue 应用实例。
- **`shadowRoot`** :`boolean`,默认为 `true`。设置为 `false` 以在不带 shadow root 的情况下渲染自定义元素。这意味着自定义元素单文件组件中的 `
## 介绍 {#introduction}
除了 Vue 内置的一系列指令 (比如 `v-model` 或 `v-show`) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。
我们已经介绍了两种在 Vue 中重用代码的方式:[组件](/guide/essentials/component-basics)和[组合式函数](./composables)。组件是主要的构建模块,而组合式函数则侧重于有状态的逻辑。另一方面,自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑。
一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。下面是一个自定义指令的例子,当 Vue 将元素插入到 DOM 中后,该指令会将一个 class 添加到元素中:
```vue
This sentence is important!
```
```js
const highlight = {
mounted: (el) => el.classList.add('is-highlight')
}
export default {
directives: {
// 在模板中启用 v-highlight
highlight
}
}
```
```vue-html
This sentence is important!
```
This sentence is important!
在 `
```
```js
const focus = {
mounted: (el) => el.focus()
}
export default {
directives: {
// 在模板中启用 v-focus
focus
}
}
```
```vue-html
```
该指令比 `autofocus` 属性更有用,因为它不仅在页面加载时有效,而且在 Vue 动态插入元素时也有效!
建议尽可能使用 `v-bind` 等内置指令声明模板,因为它们更高效,对服务端渲染也更友好。
## 指令钩子 {#directive-hooks}
一个指令的定义对象可以提供几种钩子函数 (都是可选的):
```js
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode) {}
}
```
### 钩子参数 {#hook-arguments}
指令的钩子会传递以下几种参数:
- `el`:指令绑定到的元素。这可以用于直接操作 DOM。
- `binding`:一个对象,包含以下属性。
- `value`:传递给指令的值。例如在 `v-my-directive="1 + 1"` 中,值是 `2`。
- `oldValue`:之前的值,仅在 `beforeUpdate` 和 `updated` 中可用。无论值是否更改,它都可用。
- `arg`:传递给指令的参数 (如果有的话)。例如在 `v-my-directive:foo` 中,参数是 `"foo"`。
- `modifiers`:一个包含修饰符的对象 (如果有的话)。例如在 `v-my-directive.foo.bar` 中,修饰符对象是 `{ foo: true, bar: true }`。
- `instance`:使用该指令的组件实例。
- `dir`:指令的定义对象。
- `vnode`:代表绑定元素的底层 VNode。
- `prevVnode`:代表之前的渲染中指令所绑定元素的 VNode。仅在 `beforeUpdate` 和 `updated` 钩子中可用。
举例来说,像下面这样使用指令:
```vue-html
```
`binding` 参数会是一个这样的对象:
```js
{
arg: 'foo',
modifiers: { bar: true },
value: /* `baz` 的值 */,
oldValue: /* 上一次更新时 `baz` 的值 */
}
```
和内置指令类似,自定义指令的参数也可以是动态的。举例来说:
```vue-html
```
这里指令的参数会基于组件的 `arg` 数据属性响应式地更新。
:::tip Note
除了 `el` 外,其他参数都是只读的,不要更改它们。若你需要在不同的钩子间共享信息,推荐通过元素的 [dataset](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset) attribute 实现。
:::
## 简化形式 {#function-shorthand}
对于自定义指令来说,一个很常见的情况是仅仅需要在 `mounted` 和 `updated` 上实现相同的行为,除此之外并不需要其他钩子。这种情况下我们可以直接用一个函数来定义指令,如下所示:
```vue-html
```
```js
app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})
```
## 对象字面量 {#object-literals}
如果你的指令需要多个值,你可以向它传递一个 JavaScript 对象字面量。别忘了,指令也可以接收任何合法的 JavaScript 表达式。
```vue-html
```
```js
app.directive('demo', (el, binding) => {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
```
## 在组件上使用 {#usage-on-components}
:::warning 不推荐
不推荐在组件上使用自定义指令。当组件具有多个根节点时可能会出现预期外的行为。
:::
当在组件上使用自定义指令时,它会始终应用于组件的根节点,和[透传 attributes](/guide/components/attrs) 类似。
```vue-html
```
```vue-html
My component content
```
需要注意的是组件可能含有多个根节点。当应用到一个多根组件时,指令将会被忽略且抛出一个警告。和 attribute 不同,指令不能通过 `v-bind="$attrs"` 来传递给一个不同的元素。
---
---
url: /api/custom-renderer.md
---
# 自定义渲染器 API {#custom-renderer-api}
## createRenderer() {#createrenderer}
创建一个自定义渲染器。通过提供平台特定的节点创建以及更改 API,你可以在非 DOM 环境中也享受到 Vue 核心运行时的特性。
- **类型**
```ts
function createRenderer
(
options: RendererOptions
): Renderer
interface Renderer {
render: RootRenderFunction
createApp: CreateAppFunction
}
interface RendererOptions {
patchProp(
el: HostElement,
key: string,
prevValue: any,
nextValue: any,
namespace?: ElementNamespace,
parentComponent?: ComponentInternalInstance | null,
): void
insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
remove(el: HostNode): void
createElement(
type: string,
namespace?: ElementNamespace,
isCustomizedBuiltIn?: string,
vnodeProps?: (VNodeProps & { [key: string]: any }) | null,
): HostElement
createText(text: string): HostNode
createComment(text: string): HostNode
setText(node: HostNode, text: string): void
setElementText(node: HostElement, text: string): void
parentNode(node: HostNode): HostElement | null
nextSibling(node: HostNode): HostNode | null
querySelector?(selector: string): HostElement | null
setScopeId?(el: HostElement, id: string): void
cloneNode?(node: HostNode): HostNode
insertStaticContent?(
content: string,
parent: HostElement,
anchor: HostNode | null,
namespace: ElementNamespace,
start?: HostNode | null,
end?: HostNode | null,
): [HostNode, HostNode]
}
```
- **示例**
```js
import { createRenderer } from '@vue/runtime-core'
const { render, createApp } = createRenderer({
patchProp,
insert,
remove,
createElement
// ...
})
// `render` 是底层 API
// `createApp` 返回一个应用实例
export { render, createApp }
// 重新导出 Vue 的核心 API
export * from '@vue/runtime-core'
```
Vue 自身的 `@vue/runtime-dom` 也是[利用这套 API 实现的](https://github.com/vuejs/core/blob/main/packages/runtime-dom/src/index.ts)。要想了解一个简单一些的实现,请参考 [`@vue/runtime-test`](https://github.com/vuejs/core/blob/main/packages/runtime-test/src/index.ts),这是一个 Vue 自己做单元测试的私有包。
---
---
url: /guide/essentials/forms.md
---
# 表单输入绑定 {#form-input-bindings}
在前端处理表单时,我们常常需要将表单输入框的内容同步给 JavaScript 中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦:
```vue-html
text = event.target.value">
```
`v-model` 指令帮我们简化了这一步骤:
```vue-html
```
另外,`v-model` 还可以用于各种不同类型的输入,`