vue3-API

参考:https://cn.vuejs.org/api/application.html#create-app
源地址:https://gitee.com/gaohaixiang192/study-node/blob/master/API-VUE3.md

全局 API

应用实例

createApp() > 创建一个应用实例。
  • 类型
function createApp(rootComponent: Component, rootProps?: object): App
  • 详细信息
    第一个参数是根组件。第二个参数可选,它是要传递给根组件的 props。

  • 示例
    可以直接内联根组件:

import { createApp } from 'vue'

const app = createApp({
  /* root component options */
})

也可以使用从别处导入的组件:

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
createSSRApp()

以 SSR 激活模式创建一个应用实例。用法与 createApp() 完全相同。

app.mount()

将应用实例挂载在一个容器元素中。

  • 类型
interface App {
  mount(rootContainer: Element | string): ComponentPublicInstance
}
  • 详细信息
    参数可以是一个实际的 DOM 元素或一个 CSS 选择器 (使用第一个匹配到的元素)。返回根组件的实例。
    如果该组件有模板或定义了渲染函数,它将替换容器内所有现存的 DOM 节点。否则在运行时编译器可用的情况下,容器元素的 innerHTML 将被用作模板。
    在 SSR 激活模式下,它将激活容器内现有的 DOM 节点。如果出现了激活不匹配,那么现有的 DOM 节点将会被修改以匹配客户端的实际渲染结果。
    对于每个应用实例,mount() 仅能调用一次。

  • 示例

import { createApp } from 'vue'
const app = createApp(/* ... */)

app.mount('#app')

也可以挂载到一个实际的 DOM 元素。

app.mount(document.body.firstChild)
app.unmount()

卸载一个已挂载的应用实例。卸载一个应用会触发该应用组件树内所有组件的卸载生命周期钩子。

  • 类型
interface App {
  unmount(): void
}
app.provide()

提供一个值,可以在应用中的所有后代组件中注入使用。

  • 类型
interface App {
  provide<T>(key: InjectionKey<T> | symbol | string, value: T): this
}
  • 详细信息
    第一个参数应当是注入的 key,第二个参数则是提供的值。返回应用实例本身。

  • 示例

import { createApp } from 'vue'

const app = createApp(/* ... */)

app.provide('message', 'hello')

在应用的某个组件中:

export default {
  inject: ['message'],
  created() {
    console.log(this.message) // 'hello'
  }
}
app.component()

如果同时传递一个组件名字符串及其定义,则注册一个全局组件;如果只传递一个名字,则会返回用该名字注册组件 (如果存在的话)。

  • 类型
interface App {
  component(name: string): Component | undefined
  component(name: string, component: Component): this
}
  • 示例
import { createApp } from 'vue'

const app = createApp({})

// 注册一个选项对象
app.component('my-component', {
  /* ... */
})

// 得到一个已注册的组件
const MyComponent = app.component('my-component')
app.directive()

如果同时传递一个名字和一个指令定义,则注册一个全局指令;
如果只传递一个名字,则会返回用该名字注册的指令 (如果存在的话)。

  • 类型
interface App {
  directive(name: string): Directive | undefined
  directive(name: string, directive: Directive): this
}
  • 示例
import { createApp } from 'vue'

const app = createApp({
  /* ... */
})

// 注册(对象形式的指令)
app.directive('my-directive', {
  /* 自定义指令钩子 */
})

// 注册(函数形式的指令)
app.directive('my-directive', () => {
  /* ... */
})

// 得到一个已注册的指令
const myDirective = app.directive('my-directive')
app.use()

安装一个插件。

  • 类型
interface App {
  use(plugin: Plugin, ...options: any[]): this
}
  • 详细信息
    第一个参数应是插件本身,可选的第二个参数是要传递给插件的选项。
    插件可以是一个带 install() 方法的对象,亦或直接是一个将被用作 install() 方法的函数。
    插件选项 (app.use() 的第二个参数) 将会传递给插件的 install() 方法。
    若 app.use() 对同一个插件多次调用,该插件只会被安装一次。

  • 示例

import { createApp } from 'vue'
import MyPlugin from './plugins/MyPlugin'

const app = createApp({
  /* ... */
})

app.use(MyPlugin)
app.mixin()

应用一个全局 mixin (适用于该应用程序的范围)。一个全局的 mixin 会作用于应用中的每个组件实例。
不推荐
Mixins 在 Vue 3 支持主要是为了向后兼容,因为生态中有许多库使用到。
在新的应用中应尽量避免使用 mixin,特别是全局 mixin。
若要进行逻辑复用,推荐用组合式函数来替代。

  • 类型
interface App {
  mixin(mixin: ComponentOptions): this
}
app.version

提供当前应用所使用的 Vue 版本号。这在插件中很有用,因为可能需要根据不同的 Vue 版本执行不同的逻辑。

  • 类型
interface App {
  version: string
}

在一个插件中对版本作判断:

export default {
  install(app) {
    const version = Number(app.version.split('.')[0])
    if (version < 3) {
      console.warn('This plugin requires Vue 3')
    }
  }
}
app.config

每个应用实例都会暴露一个 config 对象,其中包含了对这个应用的配置设定。
你可以在挂载应用前更改这些属性 (下面列举了每个属性的对应文档)。

import { createApp } from 'vue'

const app = createApp(/* ... */)

console.log(app.config)
app.config.errorHandler

用于为应用内抛出的未捕获错误指定一个全局处理函数。

  • 类型
interface AppConfig {
  errorHandler?: (
    err: unknown,
    instance: ComponentPublicInstance | null,
    // `info` 是一个 Vue 特定的错误信息
    // 例如:错误是在哪个生命周期的钩子上抛出的
    info: string
  ) => void
}
  • 详细信息
    错误处理器接收三个参数:错误对象、触发该错误的组件实例和一个指出错误来源类型信息的字符串。
    它可以从下面这些来源中捕获错误:
组件渲染器
事件处理器
生命周期钩子
setup() 函数
侦听器
自定义指令钩子
过渡 (Transition) 钩子
  • 示例
app.config.errorHandler = (err, instance, info) => {
  // 处理错误,例如:报告给一个服务
}
app.config.warnHandler

用于为 Vue 的运行时警告指定一个自定义处理函数。

  • 类型
interface AppConfig {
  warnHandler?: (
    msg: string,
    instance: ComponentPublicInstance | null,
    trace: string
  ) => void
}
  • 详细信息
    警告处理器将接受警告信息作为其第一个参数,来源组件实例为第二个参数,以及组件追踪字符串作为第三个参数。
    这可以用户过滤筛选特定的警告信息,降低控制台输出的冗余。所有的 Vue 警告都需要在开发阶段得到解决,
    因此仅建议在调试期间选取部分特定警告,并且应该在调试完成之后立刻移除。
TIP
警告仅会在开发阶段显示,因此在生产环境中,这条配置将被忽略。
  • 示例
app.config.warnHandler = (msg, instance, trace) => {
  // `trace` is the component hierarchy trace
}
app.config.performance

设置此项为 true 可以在浏览器开发工具的“性能/时间线”页中启用对组件初始化、编译、渲染和修补的性能表现追踪。
仅在开发模式和支持 performance.mark API 的浏览器中工作。

app.config.compilerOptions

配置运行时编译器的选项。设置在此对象上的值将会在浏览器内进行模板编译时使用,
并会影响到所配置应用的所有组件。另外你也可以通过 compilerOptions 选项在每个组件的基础上覆盖这些选项。

  重要
  此配置项仅在完整构建版本,即可以在浏览器中编译模板的 vue.js 文件中可用。
  如果你用的是带构建的项目配置,且使用的是仅含运行时的 Vue 文件版本,
  那么编译器选项必须通过构建工具的相关配置传递给 @vue/compiler-dom。

  vue-loader:通过 compilerOptions loader 的选项传递。并请阅读如何在 vue-cli 中配置它。
  vite:通过 @vitejs/plugin-vue 的选项传递。
app.config.compilerOptions.isCustomElement

用于指定一个检查方法来识别原生自定义元素。

  • 类型 (tag: string) => boolean

  • 详细信息

  如果该标签需要当作原生自定义元素则应返回 true。对匹配到的标签,Vue 会将其渲染为原生元素而非将其视为一个 Vue 组件来解析。
  原生 HTML 和 SVG 标签不需要在此函数中进行匹配,Vue 的解析器会自动识别它们。
  • 示例
// 将所有标签前缀为 `ion-` 的标签视为自定义元素
app.config.compilerOptions.isCustomElement = (tag) => {
  return tag.startsWith('ion-')
}
app.config.compilerOptions.whitespace

用于调整模板中空格的处理行为。

  • 类型 ‘condense’ | ‘preserve’
    默认 ‘condense’

  • 详细信息

  Vue 移除/缩短了模板中的空格以求更高效的模板输出。默认的策略是“缩短”,表现行为如下:
  元素中开头和结尾的空格字符将被缩短为一个空格。
  包含换行的元素之间的空白字符会被删除。
  文本节点中连续的空白字符被缩短成一个空格。
  设置该选项为 'preserve' 则会禁用 (2) 和 (3) 两项。
  • 示例
app.config.compilerOptions.whitespace = 'preserve'
app.config.compilerOptions.delimiters

用于调整模板内文本插值的分隔符。

  • 类型 [string, string]
    默认 [‘{{’, ‘}}’]

  • 详细信息
    此项通常是为了避免与同样使用 mustache 语法的服务器端框架发生冲突。

  • 示例

// 分隔符改为ES6模板字符串样式
app.config.compilerOptions.delimiters = ['${', '}']
app.config.compilerOptions.comments

用于调整是否移除模板中的 HTML 注释。

  • 类型 boolean
    默认 false

  • 详细信息
    默认情况下,Vue 会在生产环境移除所有注释,设置该项为 true 会强制 Vue 在生产环境也保留注释。
    在开发过程中,注释是始终被保留的。这个选项通常在 Vue 与其他依赖 HTML 注释的库一起使用时使用。

  • 示例

app.config.compilerOptions.comments = true
app.config.globalProperties

一个用于注册能够被应用内所有组件实例访问到的全局属性的对象。

  • 类型
interface AppConfig {
  globalProperties: Record<string, any>
}
  • 详细信息
    这是对 Vue 2 中 Vue.prototype 使用方式的一种替代,此写法在 Vue 3 已经不存在了。与任何全局的东西一样,应该谨慎使用。
    如果全局属性与组件自己的属性冲突,组件自己的属性将具有更高的优先级。

用法

app.config.globalProperties.msg = 'hello'

这使得 msg 在应用的任意组件模板上都可用,并且也可以通过任意组件实例的 this 访问到:

export default {
  mounted() {
    console.log(this.msg) // 'hello'
  }
}
app.config.optionMergeStrategies

一个用于定义自定义组件选项的合并策略的对象。

  • 类型
interface AppConfig {
  optionMergeStrategies: Record<string, OptionMergeFunction>
}

type OptionMergeFunction = (to: unknown, from: unknown) => any
  • 详细信息
  一些插件或库对自定义组件选项添加了支持 (通过注入全局 mixin)。
  这些选项在有多个不同来源时可能需要特殊的合并策略 (例如 mixin 或组件继承)。
  可以在 app.config.optionMergeStrategies 对象上以选项的名称作为 key,
  可以为一个自定义选项注册分配一个合并策略函数。
  合并策略函数分别接受在父实例和子实例上定义的该选项的值作为第一和第二个参数。
  • 示例
const app = createApp({
  // option from self
  msg: 'Vue',
  // option from a mixin
  mixins: [
    {
      msg: 'Hello '
    }
  ],
  mounted() {
    // 在 this.$options 上暴露被合并的选项
    console.log(this.$options.msg)
  }
})

// 为  `msg` 定义一个合并策略函数
app.config.optionMergeStrategies.msg = (parent, child) => {
  return (parent || '') + (child || '')
}

app.mount('#app')
// 打印 'Hello Vue'

通用

version

暴露当前所使用的 Vue 版本。

  • 类型 string
  • 示例
import { version } from 'vue'
console.log(version)
nextTick()

等待下一次 DOM 更新刷新的工具方法。

  • 类型
function nextTick(callback?: () => void): Promise<void>
  • 详细信息
  当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,
  而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。
  这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。
  nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。
  你可以传递一个回调函数作为参数,或者 await 返回的 Promise。
  • 示例
<script>
import { nextTick } from 'vue'

export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    async increment() {
      this.count++

      // DOM 还未更新
      console.log(document.getElementById('counter').textContent) // 0

      await nextTick()
      // DOM 此时已经更新
      console.log(document.getElementById('counter').textContent) // 1
    }
  }
}
</script>

<template>
  <button id="counter" @click="increment">{{ count }}</button>
</template>
defineComponent()
  • 在定义 Vue 组件时提供类型推导的辅助函数。

  • 类型

function defineComponent(
  component: ComponentOptions | ComponentOptions['setup']
): ComponentConstructor
  • 详细信息
  第一个参数是一个组件选项对象。返回值将是该选项对象本身,
  因为该函数实际上在运行时没有任何操作,仅用于提供类型推导。
  注意返回值的类型有一点特别:它会是一个构造函数类型,
  它的实例类型是根据选项推断出的组件实例类型。
  这是为了能让该返回值在 TSX 中用作标签时提供类型推导支持。
  你可以像这样从 defineComponent() 的返回类型中提取出一个组件的实例类型 (与其选项中的 this 的类型等价):
const Foo = defineComponent(/* ... */)
type FooInstance = InstanceType<typeof Foo>
webpack Treeshaking 的注意事项

因为 defineComponent() 是一个函数调用,所以它可能被某些构建工具认为会产生副作用,
如 webpack。即使一个组件从未被使用,也有可能不被 tree-shake。
为了告诉 webpack 这个函数调用可以被安全地 tree-shake,我们可以在函数调用之前添加一个 /#PURE/ 形式的注释:

export default /*#__PURE__*/ defineComponent(/* ... */)

请注意,如果你的项目中使用的是 Vite,就不需要这么做,
因为 Rollup (Vite 背后在生产环境使用的打包器) 可以智能地确定 defineComponent() 实际上并没有副作用,所以无需手动注释。

defineAsyncComponent()

定义一个异步组件,它在运行时是懒加载的。参数可以是一个异步加载函数,或是对加载行为进行更具体定制的一个选项对象。

  • 类型
function defineAsyncComponent(
  source: AsyncComponentLoader | AsyncComponentOptions
): Component

type AsyncComponentLoader = () => Promise<Component>

interface AsyncComponentOptions {
  loader: AsyncComponentLoader
  loadingComponent?: Component
  errorComponent?: Component
  delay?: number
  timeout?: number
  suspensible?: boolean
  onError?: (
    error: Error,
    retry: () => void,
    fail: () => void,
    attempts: number
  ) => any
}
defineCustomElement()

这个方法和 defineComponent 接受的参数相同,不同的是会返回一个原生自定义元素类的构造器。

  • 类型
function defineCustomElement(
  component:
    | (ComponentOptions & { styles?: string[] })
    | ComponentOptions['setup']
): {
  new (props?: object): HTMLElement
}
  • 详细信息
  除了常规的组件选项,defineCustomElement() 还支持一个特别的选项 styles,
  它应该是一个内联 CSS 字符串的数组,所提供的 CSS 会被注入到该元素的 shadow root 上。
  返回值是一个可以通过 customElements.define() 注册的自定义元素构造器。
  • 示例
import { defineCustomElement } from 'vue'

const MyVueElement = defineCustomElement({
  /* 组件选项 */
})

// 注册自定义元素
customElements.define('my-vue-element', MyVueElement)

另外请注意在使用单文件组件时 defineCustomElement() 需要特殊的配置。

组合式 API

setup()

组合式 API:setup()

setup() 钩子是在组件中使用组合式 API 的入口,通常只在以下情况下使用:
需要在非单文件组件中使用组合式 API 时。
需要在基于选项式 API 的组件中集成基于组合式 API 的代码时。
其他情况下,都应优先使用

基本使用

我们可以使用响应式 API 来声明响应式的状态,
在 setup() 函数中返回的对象会暴露给模板和组件实例。
其它的选项也可以通过组件实例来获取 setup() 暴露的属性:

<script>
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)

    // 返回值会暴露给模板和其他的选项式 API 钩子
    return {
      count
    }
  },

  mounted() {
    console.log(this.count) // 0
  }
}
</script>

<template>
  <button @click="count++">{{ count }}</button>
</template>

请注意在模板中访问从 setup 返回的 ref 时,它会自动浅层解包,
因此你无须再在模板中为它写 .value。当通过 this 访问时也会同样如此解包。

  TIP
  setup() 自身并不含对组件实例的访问权,即在 setup() 中访问 this 会是 undefined。
  你可以在选项式 API 中访问组合式 API 暴露的值,但反过来则不行。
访问 Props

setup 函数的第一个参数是组件的 props。和标准的组件一致,
一个 setup 函数的 props 是响应式的,并且会在传入新的 props 时同步更新。

export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}

请注意如果你解构了 props 对象,解构出的变量将会丢失响应性。因此我们推荐通过 props.xxx 的形式来使用其中的 props。
如果你确实需要解构 props 对象,或者需要将某个 prop 传到一个外部函数中并保持响应性,
那么你可以使用 toRefs() 和 toRef() 这两个工具函数:

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 函数的第二个参数是一个 Setup 上下文对象。上下文对象暴露了其他一些在 setup 中可能会用到的值:

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)
  }
}

该上下文对象是非响应式的,可以安全地解构:

export default {
  setup(props, { attrs, slots, emit, expose }) {
    ...
  }
}

attrs 和 slots 都是有状态的对象,它们总是会随着组件自身的更新而更新。
这意味着你应当避免解构它们,并始终通过 attrs.x 或 slots.x 的形式使用其中的属性。
此外还需注意,和 props 不同,attrs 和 slots 的属性都不是响应式的。
如果你想要基于 attrs 或 slots 的改变来执行副作用,那么你应该在 onBeforeUpdate 生命周期钩子中编写相关逻辑。

暴露公共属性

expose 函数用于显式地限制该组件暴露出的属性,当父组件通过模板引用访问该组件的实例时,将仅能访问 expose 函数暴露出的内容:

export default {
  setup(props, { expose }) {
    // 让组件实例处于 “关闭状态”
    // 即不向父组件暴露任何东西
    expose()

    const publicCount = ref(0)
    const privateCount = ref(0)
    // 有选择地暴露局部状态
    expose({ count: publicCount })
  }
}
与渲染函数一起使用

setup 也可以返回一个渲染函数,此时在渲染函数中可以直接使用在同一作用域下声明的响应式状态:

import { h, ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return () => h('div', count.value)
  }
}

返回一个渲染函数将会阻止我们返回其他东西。对于组件内部来说,这样没有问题,
但如果我们想通过模板引用将这个组件的方法暴露给父组件,那就有问题了。
我们可以通过调用 expose() 解决这个问题:

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 方法。

响应式: 核心

响应式 API:核心
ref()

接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。

  • 类型
function ref<T>(value: T): Ref<UnwrapRef<T>>

interface Ref<T> {
  value: T
}
  • 详细信息
    ref 对象是可更改的,也就是说你可以为 .value 赋予新的值。
    它也是响应式的,即所有对 .value 的操作都将被追踪,并且写操作会触发与之相关的副作用。
    如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。
    这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。
    若要避免这种深层次的转换,请使用 shallowRef() 来替代。

  • 示例

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
computed ()

接受一个 getter 函数,返回一个只读的响应式 ref 对象。
该 ref 通过 .value 暴露 getter 函数的返回值。
它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。

  • 类型
// 只读
function computed<T>(
  getter: () => T,
  // 查看下方的 "计算属性调试" 链接
  debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>

// 可写的
function computed<T>(
  options: {
    get: () => T
    set: (value: T) => void
  },
  debuggerOptions?: DebuggerOptions
): Ref<T>
  • 示例
    创建一个只读的计算属性 ref:
const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 错误

创建一个可写的计算属性 ref:

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

调试:

const plusOne = computed(() => count.value + 1, {
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})
reactive()

返回一个对象的响应式代理。

  • 类型
function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
  • 详细信息
  响应式转换是“深层”的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性。
  值得注意的是,当访问到某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包。
  若要避免深层响应式转换,只想保留对这个对象顶层次访问的响应性,请使用 shallowReactive() 作替代。
  返回的对象以及其中嵌套的对象都会通过 ES Proxy 包裹,因此不等于源对象,建议只使用响应式代理,避免使用原始对象。
  • 示例
    创建一个响应式对象:
const obj = reactive({ count: 0 })
obj.count++

ref 的解包:

const count = ref(1)
const obj = reactive({ count })

// ref 会被解包
console.log(obj.count === count.value) // true

// 会更新 `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2

// 也会更新 `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3
  • 注意当访问到某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包:
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)

const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)

将一个 ref 赋值给为一个 reactive 属性时,该 ref 会被自动解包:

const count = ref(1)
const obj = reactive({})

obj.count = count

console.log(obj.count) // 1
console.log(obj.count === count.value) // true
readonly()

接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

  • 类型
function readonly<T extends object>(
  target: T
): DeepReadonly<UnwrapNestedRefs<T>>
  • 详细信息
    只读代理是深层的:对任何嵌套属性的访问都将是只读的。
    它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。
    要避免深层级的转换行为,请使用 shallowReadonly() 作替代。

  • 示例

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // 用来做响应性追踪
  console.log(copy.count)
})

// 更改源属性会触发其依赖的侦听器
original.count++

// 更改该只读副本将会失败,并会得到一个警告
copy.count++ // warning!
watchEffect()

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

  • 类型
function watchEffect(
  effect: (onCleanup: OnCleanup) => void,
  options?: WatchEffectOptions
): StopHandle

type OnCleanup = (cleanupFn: () => void) => void

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

type StopHandle = () => void
  • 详细信息
  第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,
  用来注册清理回调。清理回调会在该副作用下一次执行前被调用,
  可以用来清理无效的副作用,例如等待中的异步请求 (参见下面的示例)。
  第二个参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。
  默认情况下,侦听器将在组件渲染之前执行。设置 flush: 'post' 将会使侦听器延迟到组件渲染之后再执行。
  详见回调的触发时机。在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。
  这可以通过设置 flush: 'sync' 来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。
  返回值是一个用来停止该副作用的函数。
  • 示例
const count = ref(0)

watchEffect(() => console.log(count.value))
// -> 输出 0

count.value++
// -> 输出 1

副作用清除:

watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(id.value)
  // `cancel` 会在 `id` 更改时调用
  // 以便取消之前
  // 未完成的请求
  onCleanup(cancel)
  data.value = await response
})

停止侦听器:

const stop = watchEffect(() => {})

// 当不再需要此侦听器时:
stop()

选项:

watchEffect(() => {}, {
  flush: 'post',
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})
watchPostEffect()

watchEffect() 使用 flush: ‘post’ 选项时的别名。

watchSyncEffect()

watchEffect() 使用 flush: ‘sync’ 选项时的别名。

watch()

侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。

  • 类型
// 侦听单个来源
function watch<T>(
  source: WatchSource<T>,
  callback: WatchCallback<T>,
  options?: WatchOptions
): StopHandle

// 侦听多个来源
function watch<T>(
  sources: WatchSource<T>[],
  callback: WatchCallback<T[]>,
  options?: WatchOptions
): StopHandle

type WatchCallback<T> = (
  value: T,
  oldValue: T,
  onCleanup: (cleanupFn: () => void) => void
) => void

type WatchSource<T> =
  | Ref<T> // ref
  | (() => T) // getter
  | T extends object
  ? T
  : never // 响应式对象

interface WatchOptions extends WatchEffectOptions {
  immediate?: boolean // 默认:false
  deep?: boolean // 默认:false
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}
  • 详细信息
    watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。
    第一个参数是侦听器的源。这个来源可以是以下几种:
  一个函数,返回一个值
  一个 ref
  一个响应式对象
  - ...或是由以上类型的值组成的数组

第二个参数是在发生变化时要调用的回调函数。
这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。
该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。
当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。

第三个可选的参数是一个对象,支持以下这些选项:

  immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined。
  deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器。
  flush:调整回调函数的刷新时机。参考回调的刷新时机及 watchEffect()。
  onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器。

与 watchEffect() 相比,watch() 使我们可以:

  懒执行副作用;
  更加明确是应该由哪个状态触发侦听器重新执行;
  可以访问所侦听状态的前一个值和当前值。
  • 示例
    侦听一个 getter 函数:
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

侦听一个 ref:

const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})

当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。
如果你想让回调在深层级变更时也能触发,你需要使用 { deep: true } 强制侦听器进入深层级模式。
在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。

const state = reactive({ count: 0 })
watch(
  () => state,
  (newValue, oldValue) => {
    // newValue === oldValue
  },
  { deep: true }
)

当直接侦听一个响应式对象时,侦听器会自动启用深层模式:

const state = reactive({ count: 0 })
watch(state, () => {
  /* 深层级变更状态所触发的回调 */
})

watch() 和 watchEffect() 享有相同的刷新时机和调试选项:

watch(source, callback, {
  flush: 'post',
  onTrack(e) {
    debugger
  }
})

响应式: 工具

响应式 API:工具函数
isRef()

检查某个值是否为 ref。

  • 类型
function isRef<T>(r: Ref<T> | unknown): r is Ref<T>

请注意,返回值是一个类型判定 (type predicate),这意味着 isRef 可以被用作类型守卫:

let foo: unknown
if (isRef(foo)) {
-   // foo 的类型被收窄为了 Ref<unknown>
  foo.value
}
unref()

如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。

  • 类型
function unref<T>(ref: T | Ref<T>): T
  • 示例
function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x)
-   // unwrapped 现在保证为 number 类型
}
toRef()

基于响应式对象上的一个属性,创建一个对应的 ref。
这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

  • 类型
function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
  defaultValue?: T[K]
): ToRef<T[K]>

type ToRef<T> = T extends Ref ? T : Ref<T>
  • 示例
const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo')

// 更改该 ref 会更新源属性
fooRef.value++
console.log(state.foo) // 2

// 更改源属性也会更新该 ref
state.foo++
console.log(fooRef.value) // 3

请注意,这不同于:

const fooRef = ref(state.foo)

上面这个 ref 不会和 state.foo 保持同步,因为这个 ref() 接收到的是一个纯数值。

toRef() 这个函数在你想把一个 prop 的 ref 传递给一个组合式函数时会很有用:

<script setup>
import { toRef } from 'vue'

const props = defineProps(/* ... */)

// 将 `props.foo` 转换为 ref,然后传入
// 一个组合式函数
useSomeFeature(toRef(props, 'foo'))
</script>

当 toRef 与组件 props 结合使用时,关于禁止对 props 做出更改的限制依然有效。
尝试将新的值传递给 ref 等效于尝试直接更改 props,这是不允许的。
在这种场景下,你可能可以考虑使用带有 get 和 set 的 computed 替代。详情请见在组件上使用 v-model 指南。
即使源属性当前不存在,toRef() 也会返回一个可用的 ref。
这让它在处理可选 props 的时候格外实用,相比之下 toRefs 就不会为可选 props 创建对应的 refs。

toRefs()

将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

  • 类型
function toRefs<T extends object>(
  object: T
): {
  [K in keyof T]: ToRef<T[K]>
}

type ToRef = T extends Ref ? T : Ref<T>
  • 示例
const state = reactive({
  foo: 1,
  bar: 2
})

const stateAsRefs = toRefs(state)
/*
- stateAsRefs 的类型:{
  foo: Ref<number>,
  bar: Ref<number>
}
*/

// 这个 ref 和源属性已经“链接上了”
state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3

当从组合式函数中返回响应式对象时,toRefs 相当有用。使用它,消费者组件可以解构/展开返回的对象而不会失去响应性:

function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  })

  // ...基于状态的操作逻辑

  // 在返回时都转为 ref
  return toRefs(state)
}

// 可以解构而不会失去响应性
const { foo, bar } = useFeatureX()

toRefs 在调用时只会为源对象上可以枚举的属性创建 ref。如果要为可能还不存在的属性创建 ref,请改用 toRef。

isProxy()

检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理。

  • 类型
function isProxy(value: unknown): boolean
isReactive()

检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。

  • 类型
function isReactive(value: unknown): boolean
isReadonly()

检查一个对象是否是由 readonly() 或 shallowReadonly() 创建的代理。

  • 类型
function isReadonly(value: unknown): boolean

响应式: 进阶

响应式 API:进阶
shallowRef()

ref() 的浅层作用形式。

  • 类型
function shallowRef<T>(value: T): ShallowRef<T>

interface ShallowRef<T> {
  value: T
}
  • 详细信息
    和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,
    并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。
    shallowRef() 常常用于对大型数据结构的性能优化或是与外部的状态管理系统集成。

  • 示例

const state = shallowRef({ count: 1 })

// 不会触发更改
state.value.count = 2

// 会触发更改
state.value = { count: 2 }
triggerRef()

强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。

  • 类型
function triggerRef(ref: ShallowRef): void
  • 示例
const shallow = shallowRef({
  greet: 'Hello, world'
})

// 触发该副作用第一次应该会打印 "Hello, world"
watchEffect(() => {
  console.log(shallow.value.greet)
})

// 这次变更不应触发副作用,因为这个 ref 是浅层的
shallow.value.greet = 'Hello, universe'

// 打印 "Hello, universe"
triggerRef(shallow)
customRef()

创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。

  • 类型
function customRef<T>(factory: CustomRefFactory<T>): Ref<T>

type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T
  set: (value: T) => void
}
  • 详细信息
    customRef() 预期接收一个工厂函数作为参数,
    这个工厂函数接受 track 和 trigger 两个函数作为参数,
    并返回一个带有 get 和 set 方法的对象。
    一般来说,track() 应该在 get() 方法中调用,
    而 trigger() 应该在 set() 中调用。然而事实上,
    你对何时调用、是否应该调用他们有完全的控制权。

  • 示例
    创建一个防抖 ref,即只在最近一次 set 调用后的一段固定间隔后再调用:

import { customRef } from 'vue'

export function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

在组件中使用:

<script setup>
import { useDebouncedRef } from './debouncedRef'
const text = useDebouncedRef('hello')
</script>

<template>
  <input v-model="text" />
</template>
shallowReactive()

reactive() 的浅层作用形式。

  • 类型
function shallowReactive<T extends object>(target: T): T
  • 详细信息
    和 reactive() 不同,这里没有深层级的转换:一个浅层响应式对象里只有根级别的属性是响应式的。
    属性的值会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。
    谨慎使用
    浅层数据结构应该只用于组件中的根级状态。请避免将其嵌套在深层次的响应式对象中,
    因为它创建的树具有不一致的响应行为,这可能很难理解和调试。

  • 示例

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 更改状态自身的属性是响应式的
state.foo++

// ...但下层嵌套对象不会被转为响应式
isReactive(state.nested) // false

// 不是响应式的
state.nested.bar++
shallowReadonly()

readonly() 的浅层作用形式

  • 类型
function shallowReadonly<T extends object>(target: T): Readonly<T>
  • 详细信息
    和 readonly() 不同,这里没有深层级的转换:只有根层级的属性变为了只读。
    属性的值都会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。
    谨慎使用
    浅层数据结构应该只用于组件中的根级状态。请避免将其嵌套在深层次的响应式对象中,
    因为它创建的树具有不一致的响应行为,这可能很难理解和调试。

  • 示例

const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 更改状态自身的属性会失败
state.foo++

// ...但可以更改下层嵌套对象
isReadonly(state.nested) // false

// 这是可以通过的
state.nested.bar++
toRaw()

根据一个 Vue 创建的代理返回其原始对象。

  • 类型
function toRaw<T>(proxy: T): T
  • 详细信息
    toRaw() 可以返回由 reactive()、readonly()、shallowReactive() 或者
    shallowReadonly() 创建的代理对应的原始对象。
    这是一个可以用于临时读取而不引起代理访问/跟踪开销,
    或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。

  • 示例

const foo = {}
const reactiveFoo = reactive(foo)

console.log(toRaw(reactiveFoo) === foo) // true
markRaw()

将一个对象标记为不可被转为代理。返回该对象本身。

  • 类型
function markRaw<T extends object>(value: T): T
  • 示例
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false

// 也适用于嵌套在其他响应性对象
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false

谨慎使用
markRaw() 和类似 shallowReactive() 这样的浅层式 API 使你可以有选择地避开默认的深度响应/只读转换,
并在状态关系谱中嵌入原始的、非代理的对象。它们可能出于各种各样的原因被使用:
有些值不应该是响应式的,例如复杂的第三方类实例或 Vue 组件对象。
当呈现带有不可变数据源的大型列表时,跳过代理转换可以提高性能。
这应该是一种进阶需求,因为只在根层访问能到原始值,所以如果把一个嵌套的、没有标记的原始对象设置成一个响应式对象,
然后再次访问它,你获取到的是代理的版本。这可能会导致对象身份风险,
即执行一个依赖于对象身份的操作,但却同时使用了同一对象的原始版本和代理版本:

const foo = markRaw({
  nested: {}
})

const bar = reactive({
  // 尽管 `foo` 被标记为了原始对象,但 foo.nested 却没有
  nested: foo.nested
})

console.log(foo.nested === bar.nested) // false

识别风险一般是很罕见的。然而,要正确使用这些 API,同时安全地避免这样的风险,需要你对响应性系统的工作方式有充分的了解。

effectScope()

创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),
这样捕获到的副作用可以一起处理。对于该 API 的使用细节,请查阅对应的 RFC。

  • 类型
function effectScope(detached?: boolean): EffectScope

interface EffectScope {
  run<T>(fn: () => T): T | undefined // 如果作用域不活跃就为 undefined
  stop(): void
}
  • 示例
const scope = effectScope()

scope.run(() => {
  const doubled = computed(() => counter.value * 2)

  watch(doubled, () => console.log(doubled.value))

  watchEffect(() => console.log('Count: ', doubled.value))
})

// 处理掉当前作用域内的所有 effect
scope.stop()
getCurrentScope()

如果有的话,返回当前活跃的 effect 作用域。

  • 类型
function getCurrentScope(): EffectScope | undefined
onScopeDispose()

在当前活跃的 effect 作用域上注册一个处理回调函数。
当相关的 effect 作用域停止时会调用这个回调函数。
这个方法可以作为可复用的组合式函数中 onUnmounted 的替代品,
它并不与组件耦合,因为每一个 Vue 组件的 setup() 函数也是在一个 effect 作用域中调用的。

  • 类型
function onScopeDispose(fn: () => void): void

生命周期钩子

组合式 API:生命周期钩子

使用方式注意
所有罗列在本页的 API 都应该在组件的 setup() 阶段被同步调用。相关细节请看指南 - 生命周期钩子。

onMounted()

注册一个回调函数,在组件挂载完成后执行。

  • 类型
function onMounted(callback: () => void): void
  • 详细信息
  组件在以下情况下被视为已挂载:
  其所有同步子组件都已经被挂载 (不包含异步组件或 <Suspense> 树内的组件)。
  其自身的 DOM 树已经创建完成并插入了父容器中。
  注意仅当根容器在文档中时,才可以保证组件 DOM 树也在文档中。
  这个钩子通常用于执行需要访问组件所渲染的 DOM 树相关的副作用,
  或是在服务端渲染应用中用于确保 DOM 相关代码仅在客户端执行。
  这个钩子在服务器端渲染期间不会被调用。
  • 示例
    通过模板引用访问一个元素:
<script setup>
import { ref, onMounted } from 'vue'

const el = ref()

onMounted(() => {
  el.value // <div>
})
</script>

<template>
  <div ref="el"></div>
</template>
onUpdated()

注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。

  • 类型
function onUpdated(callback: () => void): void
  • 详细信息
  父组件的更新钩子将在其子组件的更新钩子之后调用。
  这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的。
  如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。
  这个钩子在服务器端渲染期间不会被调用。

WARNING
不要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环!

  • 示例
    访问更新后的 DOM
<script setup>
import { ref, onUpdated } from 'vue'

const count = ref(0)

onUpdated(() => {
  // 文本内容应该与当前的 `count.value` 一致
  console.log(document.getElementById('count').textContent)
})
</script>

<template>
  <button id="count" @click="count++">{{ count }}</button>
</template>
onUnmounted()

注册一个回调函数,在组件实例被卸载之后调用。

  • 类型
function onUnmounted(callback: () => void): void
  • 详细信息
  一个组件在以下情况下被视为已卸载:
  其所有子组件都已经被卸载。
  所有相关的响应式作用 (渲染作用以及 setup() 时创建的计算属性和侦听器) 都已经停止。
  可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。
  这个钩子在服务器端渲染期间不会被调用。
  • 示例
<script setup>
import { onMounted, onUnmounted } from 'vue'

let intervalId
onMounted(() => {
  intervalId = setInterval(() => {
    // ...
  })
})

onUnmounted(() => clearInterval(intervalId))
</script>
onBeforeMount()

注册一个钩子,在组件被挂载之前被调用。

  • 类型
function onBeforeMount(callback: () => void): void
  • 详细信息
    当这个钩子被调用时,组件已经完成了其响应式状态的设置,
    但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。
    这个钩子在服务器端渲染期间不会被调用。
onBeforeUpdate()

注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。

  • 类型
function onBeforeUpdate(callback: () => void): void
  • 详细信息
    这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。在这个钩子中更改状态也是安全的。
    这个钩子在服务器端渲染期间不会被调用。
onBeforeUnmount()

注册一个钩子,在组件实例被卸载之前调用。

  • 类型
function onBeforeUnmount(callback: () => void): void
  • 详细信息
    当这个钩子被调用时,组件实例依然还保有全部的功能。
    这个钩子在服务器端渲染期间不会被调用。
onErrorCaptured()

注册一个钩子,在捕获了后代组件传递的错误时调用。

  • 类型
function onErrorCaptured(callback: ErrorCapturedHook): void
type ErrorCapturedHook = (
  err: unknown,
  instance: ComponentPublicInstance | null,
  info: string
) => boolean | void
  • 详细信息
    错误可以从以下几个来源中捕获:
  组件渲染
  事件处理器
  生命周期钩子
  setup() 函数
  侦听器
  自定义指令钩子
  过渡钩子

这个钩子带有三个实参:错误对象、触发该错误的组件实例,
以及一个说明错误来源类型的信息字符串。
你可以在 errorCaptured() 中更改组件状态来为用户显示一个错误状态。
注意不要让错误状态再次渲染导致本次错误的内容,否则组件会陷入无限循环。
这个钩子可以通过返回 false 来阻止错误继续向上传递。请看下方的传递细节介绍。

错误传递规则

默认情况下,所有的错误都会被发送到应用级的 app.config.errorHandler (前提是这个函数已经定义),
这样这些错误都能在一个统一的地方报告给分析服务。
如果组件的继承链或组件链上存在多个 errorCaptured 钩子,对于同一个错误,
这些钩子会被按从底至上的顺序一一调用。这个过程被称为“向上传递”,类似于原生 DOM 事件的冒泡机制。
如果 errorCaptured 钩子本身抛出了一个错误,
那么这个错误和原来捕获到的错误都将被发送到 app.config.errorHandler。
errorCaptured 钩子可以通过返回 false 来阻止错误继续向上传递。
即表示“这个错误已经被处理了,应当被忽略”,
它将阻止其他的 errorCaptured 钩子或 app.config.errorHandler 因这个错误而被调用。

onRenderTracked()

注册一个调试钩子,当组件渲染过程中追踪到响应式依赖时调用。
这个钩子仅在开发模式下可用,且在服务器端渲染期间不会被调用。

  • 类型
function onRenderTracked(callback: DebuggerHook): void

type DebuggerHook = (e: DebuggerEvent) => void

type DebuggerEvent = {
  effect: ReactiveEffect
  target: object
  type: TrackOpTypes /* 'get' | 'has' | 'iterate' */
  key: any
}
onRenderTriggered()

注册一个调试钩子,当响应式依赖的变更触发了组件渲染时调用。
这个钩子仅在开发模式下可用,且在服务器端渲染期间不会被调用。

  • 类型
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<any, any> | Set<any>
}
onActivated()

注册一个回调函数,若组件实例是 缓存树的一部分,当组件被插入到 DOM 中时调用。
这个钩子在服务器端渲染期间不会被调用。

  • 类型
function onActivated(callback: () => void): void
onDeactivated()

注册一个回调函数,若组件实例是 缓存树的一部分,当组件从 DOM 中被移除时调用。
这个钩子在服务器端渲染期间不会被调用。

  • 类型
function onDeactivated(callback: () => void): void
onServerPrefetch()

注册一个异步函数,在组件实例在服务器上被渲染之前调用。

  • 类型
function onServerPrefetch(callback: () => Promise<any>): void
  • 详细信息
    如果这个钩子返回了一个 Promise,服务端渲染会在渲染该组件前等待该 Promise 完成。
    这个钩子仅会在服务端渲染中执行,可以用于执行一些仅存在于服务端的数据抓取过程。

  • 示例

<script setup>
import { ref, onServerPrefetch, onMounted } from 'vue'

const data = ref(null)

onServerPrefetch(async () => {
  // 组件作为初始请求的一部分被渲染
  // 在服务器上预抓取数据,因为它比在客户端上更快。
  data.value = await fetchOnServer(/* ... */)
})

onMounted(async () => {
  if (!data.value) {
    // 如果数据在挂载时为空值,这意味着该组件
    // 是在客户端动态渲染的。将转而执行
    // 另一个客户端侧的抓取请求
    data.value = await fetchOnClient(/* ... */)
  }
})
</script>

依赖注入

组合式 API:依赖注入
provide()

提供一个值,可以被后代组件注入。

  • 类型
function provide<T>(key: InjectionKey<T> | string, value: T): void
  • 详细信息
    provide() 接受两个参数:第一个参数是要注入的 key,
    可以是一个字符串或者一个 symbol,第二个参数是要注入的值。
    当使用 TypeScript 时,key 可以是一个被类型断言为 InjectionKey 的 symbol。
    InjectionKey 是一个 Vue 提供的工具类型,继承自 Symbol,可以用来同步 provide() 和 inject() 之间值的类型。
    与注册生命周期钩子的 API 类似,provide() 必须在组件的 setup() 阶段同步调用。

  • 示例

<script setup>
import { ref, provide } from 'vue'
import { fooSymbol } from './injectionSymbols'

// 提供静态值
provide('foo', 'bar')

// 提供响应式的值
const count = ref(0)
provide('count', count)

// 提供时将 Symbol 作为 key
provide(fooSymbol, count)
</script>

参考:

指南 - 依赖注入

  • 指南 - 为 provide/inject 标注类型
inject()

注入一个由祖先组件或整个应用 (通过 app.provide()) 提供的值。

  • 类型
// 没有默认值
function inject<T>(key: InjectionKey<T> | string): T | undefined

// 带有默认值
function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T

// 使用工厂函数
function inject<T>(
  key: InjectionKey<T> | string,
  defaultValue: () => T,
  treatDefaultAsFactory: true
): T
  • 详细信息
    第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。
    如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。
    如果没有能通过 key 匹配到值,inject() 将返回 undefined,除非提供了一个默认值。
    第二个参数是可选的,即在没有匹配到 key 时使用的默认值。它也可以是一个工厂函数,
    用来返回某些创建起来比较复杂的值。如果默认值本身就是一个函数,
    那么你必须将 false 作为第三个参数传入,表明这个函数就是默认值,而不是一个工厂函数。
    与注册生命周期钩子的 API 类似,
    inject() 必须在组件的 setup() 阶段同步调用。
    当使用 TypeScript 时,key 可以是一个类型为 InjectionKey 的 symbol。
    InjectionKey 是一个 Vue 提供的工具类型,继承自 Symbol,可以用来同步 provide() 和 inject() 之间值的类型。

  • 示例
    假设有一个父组件已经提供了一些值,如前面 provide() 的例子中所示:

<script setup>
import { inject } from 'vue'
import { fooSymbol } from './injectionSymbols'

// 注入值的默认方式
const foo = inject('foo')

// 注入响应式的值
const count = inject('count')

- // 通过 Symbol 类型的 key 注入
const foo2 = inject(fooSymbol)

// 注入一个值,若为空则使用提供的默认值
const bar = inject('foo', 'default value')

// 注入一个值,若为空则使用提供的工厂函数
const baz = inject('foo', () => new Map())

// 注入时为了表明提供的默认值是个函数,需要传入第三个参数
const fn = inject('function', () => {}, false)
</script>

选项式 API

状态选项

状态选项
data

用于声明组件初始响应式状态的函数。

  • 类型
interface ComponentOptions {
  data?(
    this: ComponentPublicInstance,
    vm: ComponentPublicInstance
  ): object
}
  • 详细信息
  该函数应当返回一个普通 JavaScript 对象,Vue 会将它转换为响应式对象。
  实例创建后,可以通过 this.$data 访问该响应式对象。
  组件实例也代理了该数据对象上所有的属性,因此 this.a 等价于 this.$data.a。
  所有会用到的顶层数据属性都应该提前在这个对象中声明。
  虽然理论上可以向 this.$data 添加新属性,但并不推荐这么做。
  如果一个属性的值在一开始还获取不到,应当先用 undefined 或是 null 值来占位,让 Vue 知道这个属性是存在的。
  以 _ 或 $ 开头的属性将不会被组件实例代理,因为它们可能和 Vue 的内置属性、API 方法冲突。
  你必须以 this.$data._property 的方式访问它们。
  不推荐返回一个可能改变自身状态的对象,如浏览器 API 原生对象或是带原型的类实例等。
  理想情况下,返回的对象应是一个纯粹代表组件状态的普通对象。
  • 示例
export default {
  data() {
    return { a: 1 }
  },
  created() {
    console.log(this.a) // 1
    console.log(this.$data) // { a: 1 }
  }
}

注意,如果你为 data 属性使用了一个箭头函数,则 this 将不会指向该组件实例,不过你仍然可以通过该函数的第一个参数来访问实例:

data: (vm) => ({ a: vm.myProp })
props

用于声明一个组件的 props。

  • 类型
interface ComponentOptions {
  props?: ArrayPropsOptions | ObjectPropsOptions
}

type ArrayPropsOptions = string[]

type ObjectPropsOptions = { [key: string]: Prop }

type Prop<T = any> = PropOptions<T> | PropType<T> | null

interface PropOptions<T> {
  type?: PropType<T>
  required?: boolean
  default?: T | ((rawProps: object) => T)
  validator?: (value: unknown) => boolean
}

type PropType<T> = { new (): T } | { new (): T }[]
  • 详细信息
  在 Vue 中,所有的组件 props 都需要被显式声明。组件 props 可以通过两种方式声明:

使用字符串数组的简易形式。
  使用对象的完整形式。该对象的每个属性键是对应 prop 的名称,值则是该 prop 应具有的类型的构造函数,或是更高级的选项。
  在基于对象的语法中,每个 prop 可以进一步定义如下选项:
  type:可以是下列原生构造函数之一:String、Number、Boolean、Array、Object、Date、Function、Symbol、任何自定义构造函数,
    或由上述内容组成的数组。在开发模式中,Vue 会检查一个 prop 的值是否匹配其声明的类型,如果不匹配则会抛出警告。详见 Prop 校验。
  还要注意,一个 Boolean 类型的 prop 会影响它在开发或生产模式下的值转换行为。详见 Boolean 类型转换。
  default:为该 prop 指定一个当其没有被传入或值为 undefined 时的默认值。
    对象或数组的默认值必须从一个工厂函数返回。工厂函数也接收原始 prop 对象作为参数。
  required:定义该 prop 是否必需传入。在非生产环境中,如果 required 值为真值且 prop 未被传入,一个控制台警告将会被抛出。
  validator:将 prop 值作为唯一参数传入的自定义验证函数。在开发模式下,如果该函数返回一个假值 (即验证失败),一个控制台警告将会被抛出。
  • 示例
    简易声明:
export default {
  props: ['size', 'myMessage']
}

对象声明,带有验证:

export default {
  props: {
    // 类型检查
    height: Number,
    // 类型检查 + 其他验证
    age: {
      type: Number,
      default: 0,
      required: true,
      validator: (value) => {
        return value >= 0
      }
    }
  }
}
computed

用于声明要在组件实例上暴露的计算属性。

  • 类型
interface ComponentOptions {
  computed?: {
    [key: string]: ComputedGetter<any> | WritableComputedOptions<any>
  }
}

type ComputedGetter<T> = (
  this: ComponentPublicInstance,
  vm: ComponentPublicInstance
) => T

type ComputedSetter<T> = (
  this: ComponentPublicInstance,
  value: T
) => void

type WritableComputedOptions<T> = {
  get: ComputedGetter<T>
  set: ComputedSetter<T>
}
  • 详细信息
    该选项接收一个对象,其中键是计算属性的名称,值是一个计算属性 getter,
    或一个具有 get 和 set 方法的对象 (用于声明可写的计算属性)。
    所有的 getters 和 setters 会将它们的 this 上下文自动绑定为组件实例。
    注意,如果你为一个计算属性使用了箭头函数,则 this 不会指向该组件实例,
    不过你仍然可以通过该函数的第一个参数来访问实例:
export default {
  computed: {
    aDouble: (vm) => vm.a * 2
  }
}
  • 示例
export default {
  data() {
    return { a: 1 }
  },
  computed: {
    // 只读
    aDouble() {
      return this.a * 2
    },
    // 可写
    aPlus: {
      get() {
        return this.a + 1
      },
      set(v) {
        this.a = v - 1
      }
    }
  },
  created() {
    console.log(this.aDouble) // => 2
    console.log(this.aPlus) // => 2

    this.aPlus = 3
    console.log(this.a) // => 2
    console.log(this.aDouble) // => 4
  }
}
methods

用于声明要混入到组件实例中的方法。

  • 类型
interface ComponentOptions {
  methods?: {
    [key: string]: (this: ComponentPublicInstance, ...args: any[]) => any
  }
}
  • 详细信息
    声明的方法可以直接通过组件实例访问,或者在模板语法表达式中使用。
    所有的方法都会将它们的 this 上下文自动绑定为组件实例,即使在传递时也如此。
    在声明方法时避免使用箭头函数,因为它们不能通过 this 访问组件实例。

  • 示例

export default {
  data() {
    return { a: 1 }
  },
  methods: {
    plus() {
      this.a++
    }
  },
  created() {
    this.plus()
    console.log(this.a) // => 2
  }
}
watch

用于声明在数据更改时调用的侦听回调。

  • 类型
interface ComponentOptions {
  watch?: {
    [key: string]: WatchOptionItem | WatchOptionItem[]
  }
}

type WatchOptionItem = string | WatchCallback | ObjectWatchOptionItem

type WatchCallback<T> = (
  value: T,
  oldValue: T,
  onCleanup: (cleanupFn: () => void) => void
) => void

type ObjectWatchOptionItem = {
  handler: WatchCallback | string
  immediate?: boolean // default: false
  deep?: boolean // default: false
  flush?: 'pre' | 'post' | 'sync' // default: 'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}
  • 详细信息
  watch 选项期望接受一个对象,
  其中键是需要侦听的响应式组件实例属性 (例如,通过 data 或 computed 声明的属性)——值是相应的回调函数。
  该回调函数接受被侦听源的新值和旧值。
  除了一个根级属性,键名也可以是一个简单的由点分隔的路径,例如 a.b.c。注意,
  这种用法不支持复杂表达式——仅支持由点分隔的路径。如果你需要侦听复杂的数据源,可以使用命令式的 $watch() API。
  值也可以是一个方法名称的字符串 (通过 methods 声明),
  或包含额外选项的对象。当使用对象语法时,回调函数应被声明在 handler 中。额外的选项包含:
    immediate:在侦听器创建时立即触发回调。第一次调用时,旧值将为 undefined。
    deep:如果源是对象或数组,则强制深度遍历源,以便在深度变更时触发回调。详见深层侦听器。
    flush:调整回调的刷新时机。详见回调的触发时机及 watchEffect()。
    onTrack / onTrigger:调试侦听器的依赖关系。详见侦听器调试。
  声明侦听器回调时避免使用箭头函数,因为它们将无法通过 this 访问组件实例。
  • 示例
export default {
  data() {
    return {
      a: 1,
      b: 2,
      c: {
        d: 4
      },
      e: 5,
      f: 6
    }
  },
  watch: {
    // 侦听根级属性
    a(val, oldVal) {
      console.log(`new: ${val}, old: ${oldVal}`)
    },
    // 字符串方法名称
    b: 'someMethod',
    // 该回调将会在被侦听的对象的属性改变时调动,无论其被嵌套多深
    c: {
      handler(val, oldVal) {
        console.log('c changed')
      },
      deep: true
    },
    // 侦听单个嵌套属性:
    'c.d': function (val, oldVal) {
      // do something
    },
    // 该回调将会在侦听开始之后立即调用
    e: {
      handler(val, oldVal) {
        console.log('e changed')
      },
      immediate: true
    },
    // 你可以传入回调数组,它们将会被逐一调用
    f: [
      'handle1',
      function handle2(val, oldVal) {
        console.log('handle2 triggered')
      },
      {
        handler: function handle3(val, oldVal) {
          console.log('handle3 triggered')
        }
        /* ... */
      }
    ]
  },
  methods: {
    someMethod() {
      console.log('b changed')
    },
    handle1() {
      console.log('handle 1 triggered')
    }
  },
  created() {
    this.a = 3 // => new: 3, old: 1
  }
}
emits

用于声明由组件触发的自定义事件。

  • 类型
interface ComponentOptions {
  emits?: ArrayEmitsOptions | ObjectEmitsOptions
}

type ArrayEmitsOptions = string[]

type ObjectEmitsOptions = { [key: string]: EmitValidator | null }

type EmitValidator = (...args: unknown[]) => boolean
  • 详细信息
  可以以两种形式声明触发的事件:
  使用字符串数组的简易形式。
  使用对象的完整形式。该对象的每个属性键是事件的名称,值是 null 或一个验证函数。
  验证函数会接收到传递给组件的 $emit 调用的额外参数。例如,如果 this.$emit('foo', 1) 被调用,
  foo 相应的验证函数将接受参数 1。验证函数应返回布尔值,以表明事件参数是否通过了验证。
  注意,emits 选项会影响一个监听器被解析为组件事件监听器,还是原生 DOM 事件监听器。
  被声明为组件事件的监听器不会被透传到组件的根元素上,且将从组件的 $attrs 对象中移除。详见透传 Attributes。
  • 示例
    数组语法:
export default {
  emits: ['check'],
  created() {
    this.$emit('check')
  }
}

对象语法:

export default {
  emits: {
    // 没有验证函数
    click: null,

    // 具有验证函数
    submit: (payload) => {
      if (payload.email && payload.password) {
        return true
      } else {
        console.warn(`Invalid submit event payload!`)
        return false
      }
    }
  }
}
expose

用于声明当组件实例被父组件通过模板引用访问时暴露的公共属性。

  • 类型
interface ComponentOptions {
  expose?: string[]
}
  • 详细信息
    默认情况下,当通过 p a r e n t 、 parent、 parentroot 或模板引用访问时,组件实例将向父组件暴露所有的实例属性。
    这可能不是我们希望看到的,因为组件很可能拥有一些应保持私有的内部状态或方法,以避免紧耦合。
    expose 选项值应当是一个包含要暴露的属性名称字符串的数组。当使用 expose 时,只有显式列出的属性将在组件实例上暴露。
    expose 仅影响用户定义的属性——它不会过滤掉内置的组件实例属性。

  • 示例

export default {
  // 只有 `publicMethod` 在公共实例上可用
  expose: ['publicMethod'],
  methods: {
    publicMethod() {
      // ...
    },
    privateMethod() {
      // ...
    }
  }
}

渲染选项

渲染选项
template

用于声明组件的字符串模板。

  • 类型
interface ComponentOptions {
  template?: string
}
  • 详细信息
    通过 template 选项提供的模板将会在运行时即时编译。
    这仅在使用了包含模板编译器的 Vue 构建版本的情况下支持。
    文件名中带有 runtime 的 Vue 构建版本未包含模板编译器,
    例如 vue.runtime.esm-bundler.js。请查阅构建文件指南了解不同构建版本之间的详细区别。
如果该字符串以

如果 render 选项也同时存在于该组件中,template 将被忽略。
如果应用的根组件不含任何 template 或 render 选项,Vue 将会尝试使用所挂载元素的 innerHTML 来作为模板。
安全性注意
务必只使用可以信任的模板来源。不要直接将用户提供的内容用作模板。查看安全指南了解更多细节。

render

用于编程式地创建组件虚拟 DOM 树的函数。

  • 类型
interface ComponentOptions {
  render?(this: ComponentPublicInstance) => VNodeChild
}

type VNodeChild = VNodeChildAtom | VNodeArrayChildren

type VNodeChildAtom =
  | VNode
  | string
  | number
  | boolean
  | null
  | undefined
  | void

type VNodeArrayChildren = (VNodeArrayChildren | VNodeChildAtom)[]
  • 详细信息
    render 是字符串模板的一种替代,可以使你利用 JavaScript 的丰富表达力来完全编程式地声明组件最终的渲染输出。
    预编译的模板,例如单文件组件中的模板,会在构建时被编译为 render 选项。
    如果一个组件中同时存在 render 和 template,则 render 将具有更高的优先级。
compilerOptions

用于配置组件模板的运行时编译器选项。

  • 类型
interface ComponentOptions {
  compilerOptions?: {
    isCustomElement?: (tag: string) => boolean
    whitespace?: 'condense' | 'preserve' // 默认:'condense'
    delimiters?: [string, string] // 默认:['{{', '}}']
    comments?: boolean // 默认:false
  }
}
  • 详细信息
    这个配置选项仅在使用完整构建版本 (即可以在浏览器中编译模板的 vue.js 文件) 时才有效。
    它支持与应用级的 app.config.compilerOptions 相同的选项,并针对当前组件有更高的优先级。

生命周期选项

生命周期选项
beforeCreate

在组件实例初始化完成之后立即调用。

  • 类型
interface ComponentOptions {
  beforeCreate?(this: ComponentPublicInstance): void
}
  • 详细信息
    会在实例初始化完成、props 解析之后、data() 和 computed 等选项处理之前立即调用。
    注意,组合式 API 中的 setup() 钩子会在所有选项式 API 钩子之前调用,beforeCreate() 也不例外。
created

在组件实例处理完所有与状态相关的选项后调用。

  • 类型
interface ComponentOptions {
  created?(this: ComponentPublicInstance): void
}
  • 详细信息
    当这个钩子被调用时,以下内容已经设置完成:响应式数据、计算属性、方法和侦听器。
    然而,此时挂载阶段还未开始,因此 $el 属性仍不可用。
beforeMount

在组件被挂载之前调用。

  • 类型
interface ComponentOptions {
  beforeMount?(this: ComponentPublicInstance): void
}
  • 详细信息

当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。

这个钩子在服务端渲染时不会被调用。

mounted

在组件被挂载之后调用。

  • 类型

interface ComponentOptions {
mounted?(this: ComponentPublicInstance): void
}

  • 详细信息
  组件在以下情况下被视为已挂载:
  所有同步子组件都已经被挂载。(不包含异步组件或 <Suspense> 树内的组件)
  其自身的 DOM 树已经创建完成并插入了父容器中。
  注意仅当根容器在文档中时,才可以保证组件 DOM 树也在文档中。
  这个钩子通常用于执行需要访问组件所渲染的 DOM 树相关的副作用,
  或是在服务端渲染应用中用于确保 DOM 相关代码仅在客户端被调用。
  这个钩子在服务端渲染时不会被调用。
beforeUpdate

在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用。

  • 类型
interface ComponentOptions {
  beforeUpdate?(this: ComponentPublicInstance): void
}
  • 详细信息
    这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。在这个钩子中更改状态也是安全的。
    这个钩子在服务端渲染时不会被调用。
updated

在组件因为一个响应式状态变更而更新其 DOM 树之后调用。

  • 类型
interface ComponentOptions {
  updated?(this: ComponentPublicInstance): void
}
  • 详细信息
    父组件的更新钩子将在其子组件的更新钩子之后调用。
    这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的。
    如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。
    这个钩子在服务端渲染时不会被调用。
    WARNING
    不要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环!
beforeUnmount

在一个组件实例被卸载之前调用。

  • 类型
interface ComponentOptions {
  beforeUnmount?(this: ComponentPublicInstance): void
}
  • 详细信息
    当这个钩子被调用时,组件实例依然还保有全部的功能。
    这个钩子在服务端渲染时不会被调用。
unmounted

在一个组件实例被卸载之后调用。

  • 类型
interface ComponentOptions {
  unmounted?(this: ComponentPublicInstance): void
}
  • 详细信息
    一个组件在以下情况下被视为已卸载:
    其所有子组件都已经被卸载。
    所有相关的响应式作用 (渲染作用以及 setup() 时创建的计算属性和侦听器) 都已经停止。
    可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。
    这个钩子在服务端渲染时不会被调用。
errorCaptured

在捕获了后代组件传递的错误时调用。

  • 类型
interface ComponentOptions {
  errorCaptured?(
    this: ComponentPublicInstance,
    err: unknown,
    instance: ComponentPublicInstance | null,
    info: string
  ): boolean | void
}
  • 详细信息
    错误可以从以下几个来源中捕获:
  组件渲染
  事件处理器
  生命周期钩子
  setup() 函数
  侦听器
  自定义指令钩子
  过渡钩子
  这个钩子带有三个实参:错误对象、触发该错误的组件实例,以及一个说明错误来源类型的信息字符串。
  你可以在 errorCaptured() 中更改组件状态来为用户显示一个错误状态。
  然而重要的是,不要让错误状态渲染为导致本次错误的内容,否则组件就会进入无限的渲染循环中。
  这个钩子可以通过返回 false 来阻止错误继续向上传递。请看下方的传递细节介绍。

  错误传递规则
  默认情况下,所有的错误都会被发送到应用级的 app.config.errorHandler (前提是这个函数已经定义),
  这样这些错误都能在一个统一的地方报告给分析服务。
  如果组件的继承链或组件链上存在多个 errorCaptured 钩子,对于同一个错误,
  这些钩子会被按从底至上的顺序一一调用。这个过程被称为“向上传递”,类似于原生 DOM 事件的冒泡机制。
  如果 errorCaptured 钩子本身抛出了一个错误,
  那么这个错误和原来捕获到的错误都将被发送到 app.config.errorHandler。
  errorCaptured 钩子可以通过返回 false 来阻止错误继续向上传递。
  即表示“这个错误已经被处理了,应当被忽略”,它将阻止其他的 errorCaptured 钩子或 app.config.errorHandler 因这个错误而被调用。
renderTracked

在一个响应式依赖被组件的渲染作用追踪后调用。
这个钩子仅在开发模式下可用,且在服务器端渲染期间不会被调用。

  • 类型
interface ComponentOptions {
  renderTracked?(this: ComponentPublicInstance, e: DebuggerEvent): void
}

type DebuggerEvent = {
  effect: ReactiveEffect
  target: object
  type: TrackOpTypes /* 'get' | 'has' | 'iterate' */
  key: any
}
renderTriggered

在一个响应式依赖被组件触发了重新渲染之后调用。
这个钩子仅在开发模式下可用,且在服务器端渲染期间不会被调用。

  • 类型
interface ComponentOptions {
  renderTriggered?(this: ComponentPublicInstance, e: DebuggerEvent): void
}

type DebuggerEvent = {
  effect: ReactiveEffect
  target: object
  type: TriggerOpTypes /* 'set' | 'add' | 'delete' | 'clear' */
  key: any
  newValue?: any
  oldValue?: any
  oldTarget?: Map<any, any> | Set<any>
}
activated

若组件实例是 缓存树的一部分,当组件被插入到 DOM 中时调用。
这个钩子在服务端渲染时不会被调用。

  • 类型
interface ComponentOptions {
  activated?(this: ComponentPublicInstance): void
}
deactivated

若组件实例是 缓存树的一部分,当组件从 DOM 中被移除时调用。
这个钩子在服务端渲染时不会被调用。

  • 类型
interface ComponentOptions {
  deactivated?(this: ComponentPublicInstance): void
}
serverPrefetch

当组件实例在服务器上被渲染之前要完成的异步函数。

  • 类型
interface ComponentOptions {
  serverPrefetch?(this: ComponentPublicInstance): Promise<any>
}
  • 详细信息
    如果这个钩子返回了一个 Promise,服务端渲染会在渲染该组件前等待该 Promise 完成。
    这个钩子仅会在服务端渲染中执行,可以用于执行一些仅在服务端才有的数据抓取过程。

  • 示例

export default {
  data() {
    return {
      data: null
    }
  },
  async serverPrefetch() {
    // 组件会作为初次请求的一部分被渲染
    // 会在服务端预抓取数据,因为这比客户端更快
    this.data = await fetchOnServer(/* ... */)
  },
  async mounted() {
    if (!this.data) {
      // 如果数据在挂载时是 null,这意味着这个组件
      // 是在客户端动态渲染的,请另外执行一个
      // 客户端请求作为替代
      this.data = await fetchOnClient(/* ... */)
    }
  }
}

组合选项

组合选项
provide

用于提供可以被后代组件注入的值。

  • 类型
interface ComponentOptions {
  provide?: object | ((this: ComponentPublicInstance) => object)
}
  • 详细信息
    provide 和 inject 通常成对一起使用,使一个祖先组件作为其后代组件的依赖注入方,
    无论这个组件的层级有多深都可以注入成功,只要他们处于同一条组件链上。
    这个 provide 选项应当是一个对象或是返回一个对象的函数。
    这个对象包含了可注入其后代组件的属性。你可以在这个对象中使用 Symbol 类型的值作为 key。

  • 示例
    基本使用方式:

const s = Symbol()

export default {
  provide: {
    foo: 'foo',
    [s]: 'bar'
  }
}

使用函数可以提供其组件中的状态:

export default {
  data() {
    return {
      msg: 'foo'
    }
  }
  provide() {
    return {
      msg: this.msg
    }
  }
}

请注意,针对上面这个例子,所供给的 msg 将不会是响应式的。请查看和响应式数据配合使用一节获取更多细节。

inject

用于声明要通过从上层提供方匹配并注入进当前组件的属性。

  • 类型
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。

  请注意,注入绑定并非响应式的。这是有意为之的一个设计。如果要注入的值是一个响应式对象,
  那么这个对象上的属性将会保留响应性。请看配合响应性一节获取更多细节。
  • 示例
    基本使用方式:
export default {
  inject: ['foo'],
  created() {
    console.log(this.foo)
  }
}

使用注入的值作为 props 的默认值:

const Child = {
  inject: ['foo'],
  props: {
    bar: {
      default() {
        return this.foo
      }
    }
  }
}

使用注入的值作为 data:

const Child = {
  inject: ['foo'],
  data() {
    return {
      bar: this.foo
    }
  }
}

注入项可以选择是否带有默认值:

const Child = {
  inject: {
    foo: { default: 'foo' }
  }
}

如果需要从不同名字的属性中注入,请使用 from 指明来源属性。

const Child = {
  inject: {
    foo: {
      from: 'bar',
      default: 'foo'
    }
  }
}
  • 和 props 默认值类似,对于非原始数据类型的值,你需要使用工厂函数:
const Child = {
  inject: {
    foo: {
      from: 'bar',
      default: () => [1, 2, 3]
    }
  }
}
mixins

一个包含组件选项对象的数组,这些选项都将被混入到当前组件的实例中。

  • 类型
interface ComponentOptions {
  mixins?: ComponentOptions[]
}
  • 详细信息
    mixins 选项接受一个 mixin 对象数组。这些 mixin 对象可以像普通的实例对象一样包含实例选项,
    它们将使用一定的选项合并逻辑与最终的选项进行合并。举例来说,
    如果你的 mixin 包含了一个 created 钩子,而组件自身也有一个,那么这两个函数都会被调用。
    Mixin 钩子的调用顺序与提供它们的选项顺序相同,且会在组件自身的钩子前被调用。

    不再推荐
    在 Vue 2 中,mixins 是创建可重用组件逻辑的主要方式。
    尽管在 Vue 3 中保留了 mixins 支持,但对于组件间的逻辑复用,Composition API 是现在更推荐的方式。

  • 示例:

const mixin = {
  created() {
    console.log(1)
  }
}

createApp({
  created() {
    console.log(2)
  },
  mixins: [mixin]
})

// => 1
// => 2
extends

要继承的“基类”组件。

  • 类型
interface ComponentOptions {
  extends?: ComponentOptions
}
  • 详细信息
    使一个组件可以继承另一个组件的组件选项。
    从实现角度来看,extends 几乎和 mixins 相同。通过 extends 指定的组件将会当作第一个 mixin 来处理。
    然而,extends 和 mixins 表达的是不同的目标。mixins 选项基本用于组合功能,而 extends 则一般更关注继承关系。
    同 mixins 一样,所有选项都将使用相关的策略进行合并。

  • 示例:

const CompA = { ... }

const CompB = {
  extends: CompA,
  ...
}

其他杂项

其他杂项选项
name

用于显式声明组件展示时的名称。

  • 类型
interface ComponentOptions {
  name?: string
}
  • 详细信息
  组件的名字有以下用途:
  在组件自己的模板中递归引用自己时
  在 Vue 开发者工具中的组件树显示时
  在组件抛出的警告追踪栈信息中显示时
  当你在使用单文件组件时,组件已经会根据其文件名推导出其名称。
  举例来说,一个名为 MyComponent.vue 的文件会推导出显示名称为“MyComponent”。
  另一种场景是当一个组件通过 app.component 被全局注册时,
  这个全局 ID 就自动被设为了其名称。
  使用 name 选项使你可以覆盖推导出的名称,或是在没有推导出名字时显式提供一个。
  (例如没有使用构建工具时,或是一个内联的非 SFC 式的组件)
  有一种场景下 name 必须是已显式声明的:即 <KeepAlive> 通过其 include / exclude prop 来匹配其需要缓存的组件时。

  TIP
  在 3.2.34 或以上的版本中,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项,
  即使是在配合 <KeepAlive> 使用时也无需再手动声明。
inheritAttrs

用于控制是否启用默认的组件 attribute 透传行为。

  • 类型
interface ComponentOptions {
  inheritAttrs?: boolean // 默认值:true
}
  • 详细信息
  默认情况下,父组件传递的,但没有被子组件解析为 props 的 attributes 绑定会被“透传”。
  这意味着当我们有一个单根节点的子组件时,这些绑定会被作为一个常规的 HTML attribute 应用在子组件的根节点元素上。
  当你编写的组件想要在一个目标元素或其他组件外面包一层时,可能并不期望这样的行为。
  我们可以通过设置 inheritAttrs 为 false 来禁用这个默认行为。
  这些 attributes 可以通过 $attrs 这个实例属性来访问,并且可以通过 v-bind 来显式绑定在一个非根节点的元素上。
  • 示例
<script>
export default {
  inheritAttrs: false,
  props: ['label', 'value'],
  emits: ['input']
}
</script>

<template>
  <label>
    {{ label }}
    <input
      v-bind="$attrs"
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    />
  </label>
</template>
components

一个对象,用于注册对当前组件实例可用的组件。

  • 类型
interface ComponentOptions {
  components?: { [key: string]: Component }
}
  • 示例
import Foo from './Foo.vue'
import Bar from './Bar.vue'

export default {
  components: {
    // 简写
    Foo,
    // 注册为一个不同的名称
    RenamedBar: Bar
  }
}
directives

一个对象,用于注册对当前组件实例可用的指令。

  • 类型
interface ComponentOptions {
  directives?: { [key: string]: Directive }
}
  • 示例
export default {
  directives: {
    // 在模板中启用 v-focus
    focus: {
      mounted(el) {
        el.focus()
      }
    }
  }
}

template

这个列表中的指令都在当前组件实例中可用。

组件实例

组件实例

本页文档描述了组件公共实例 (即 this) 上暴露的内置属性和方法,
本页罗列的所有属性,除了 $data 下的嵌套属性之外,都是只读的。

$data

从 data 选项函数中返回的对象,会被组件赋为响应式。组件实例将会代理对其数据对象的属性访问。

  • 类型
interface ComponentPublicInstance {
  $data: object
}
$props

表示组件当前已解析的 props 对象。

  • 类型
interface ComponentPublicInstance {
  $props: object
}
  • 详细信息
    这里只包含通过 props 选项声明的 props。组件实例将会代理对其 props 对象上属性的访问。
$el

该组件实例管理的 DOM 根节点。

  • 类型
interface ComponentPublicInstance {
  $el: Node | undefined
}
  • 详细信息
  $el 直到组件挂载完成 (mounted) 之前都会是 undefined。
  对于单一根元素的组件,$el 将会指向该根元素。
  对于以文本节点为根的组件,$el 将会指向该文本节点。
  对于以多个元素为根的组件,$el 将是一个仅作占位符的 DOM 节点,
  Vue 使用它来跟踪组件在 DOM 中的位置 (文本节点或 SSR 激活模式下的注释节点)。

  TIP
  为保持一致性,我们推荐使用模板引用来直接访问元素而不是依赖 $el。
$options

已解析的用于实例化当前组件的组件选项。

  • 类型
interface ComponentPublicInstance {
  $options: ComponentOptions
}
  • 详细信息
    这个 $options 对象暴露了当前组件的已解析选项,并且会是以下几种可能来源的合并结果:
    全局 mixin
    组件 extends 的基组件
    组件级 mixin
    它通常用于支持自定义组件选项:
const app = createApp({
  customOption: 'foo',
  created() {
    console.log(this.$options.customOption) // => 'foo'
  }
})
$parent

当前组件可能存在的父组件实例,如果当前组件是顶层组件,则为 null。

  • 类型
interface ComponentPublicInstance {
  $parent: ComponentPublicInstance | null
}
$root

当前组件树的根组件实例。如果当前实例没有父组件,那么这个值就是它自己。

  • 类型
interface ComponentPublicInstance {
  $root: ComponentPublicInstance
}
$slots

一个表示父组件所传入插槽的对象。

  • 类型
interface ComponentPublicInstance {
  $slots: { [name: string]: Slot }
}

type Slot = (...args: any[]) => VNode[]
  • 详细信息
    通常用于手写渲染函数,但也可用于检测是否存在插槽。
    每一个插槽都在 this. s l o t s 上暴露为一个函数,返回一个 v n o d e 数组,同时 k e y 名对应着插槽名。默认插槽暴露为 t h i s . slots 上暴露为一个函数,返回一个 vnode 数组, 同时 key 名对应着插槽名。默认插槽暴露为 this. slots上暴露为一个函数,返回一个vnode数组,同时key名对应着插槽名。默认插槽暴露为this.slots.default。
    如果插槽是一个作用域插槽,传递给该插槽函数的参数可以作为插槽的 prop 提供给插槽。
$refs

一个包含 DOM 元素和组件实例的对象,通过模板引用注册。

  • 类型
interface ComponentPublicInstance {
  $refs: { [name: string]: Element | ComponentPublicInstance | null }
}
$attrs

一个包含了组件所有透传 attributes 的对象。

  • 类型
interface ComponentPublicInstance {
  $attrs: object
}
  • 详细信息
    透传 Attributes 是指由父组件传入,
    且没有被子组件声明为 props 或是组件自定义事件的 attributes 和事件处理函数。
    默认情况下,若是单一根节点组件,$attrs 中的所有属性都是直接自动继承自组件的根元素。
    而多根节点组件则不会如此,同时你也可以通过配置 inheritAttrs 选项来显式地关闭该行为。
$watch()

用于命令式地创建侦听器的 API。

  • 类型
interface ComponentPublicInstance {
  $watch(
    source: string | (() => any),
    callback: WatchCallback,
    options?: WatchOptions
  ): StopHandle
}

type WatchCallback<T> = (
  value: T,
  oldValue: T,
  onCleanup: (cleanupFn: () => void) => void
) => void

interface WatchOptions {
  immediate?: boolean // default: false
  deep?: boolean // default: false
  flush?: 'pre' | 'post' | 'sync' // default: 'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

type StopHandle = () => void
  • 详细信息
  第一个参数是侦听来源。可以是一个组件的属性名的字符串,一个简单的由点分隔的路径字符串,或是一个 getter 函数。
  第二个参数是回调函数。它接收的参数分别是侦听来源的新值、旧值。
  immediate:指定在侦听器创建时是否立即触发回调。在第一次调用时旧值为 undefined。
  deep:指定在侦听来源是一个对象时,是否强制深度遍历,这样回调函数就会在深层级发生变更时被触发。详见深层侦听器。
  flush:指定回调函数的刷新时机。详见回调刷新时机及 watchEffect()。
  onTrack / onTrigger:调试侦听器的依赖,详见侦听器调试。
  • 示例
    侦听一个属性名:
this.$watch('a', (newVal, oldVal) => {})

侦听一个由 . 分隔的路径:

this.$watch('a.b', (newVal, oldVal) => {})

对更复杂表达式使用 getter 函数:

this.$watch(
  // 每一次这个 `this.a + this.b` 表达式生成一个
  // 不同的结果,处理函数都会被调用
  // 这就好像我们在侦听一个计算属性
  // 而不定义计算属性本身。
  () => this.a + this.b,
  (newVal, oldVal) => {}
)

停止该侦听器:

const unwatch = this.$watch('a', cb)
$emit()

在当前组件触发一个自定义事件。任何额外的参数都会传递给事件监听器的回调函数。

  • 类型
interface ComponentPublicInstance {
  $emit(event: string, ...args: any[]): void
}
  • 示例
export default {
  created() {
    // 仅触发事件
    this.$emit('foo')
    // 带有额外的参数
    this.$emit('bar', 1, 2, 3)
  }
}
$forceUpdate()

强制该组件重新渲染。

  • 类型
interface ComponentPublicInstance {
  $forceUpdate(): void
}
  • 详细信息
    鉴于 Vue 的全自动响应性系统,这个功能应该很少会被用到。
    唯一可能需要它的情况是,你使用高阶响应式 API 显式创建了一个非响应式的组件状态。
$nextTick()

绑定在实例上的 nextTick() 函数。

  • 类型
interface ComponentPublicInstance {
  $nextTick(callback?: (this: ComponentPublicInstance) => void): Promise<void>
}
  • 详细信息
    和全局版本的 nextTick() 的唯一区别就是组件传递给 this.$nextTick() 的回调函数会带上 this 上下文,其绑定了当前组件实例。

内置内容

指令

内置指令
v-text

更新元素的文本内容。
期望的绑定值类型:string

  • 详细信息
    v-text 通过设置元素的 textContent 属性来工作,因此它将覆盖元素中所有现有的内容。
    如果你需要更新 textContent 的部分,应该使用 mustache interpolations 代替。

  • 示例
    template

{{msg}}

v-html

更新元素的 innerHTML。
期望的绑定值类型:string

  • 详细信息
  v-html 的内容直接作为普通 HTML 插入—— Vue 模板语法是不会被解析的。
  如果你发现自己正打算用 v-html 来编写模板,不如重新想想怎么使用组件来代替。

  安全说明
  在你的站点上动态渲染任意的 HTML 是非常危险的,因为它很容易导致 XSS 攻击。
  请只对可信内容使用 HTML 插值,绝不要将用户提供的内容作为插值
  在单文件组件,scoped 样式将不会作用于 v-html 里的内容,因为 HTML 内容不会被 Vue 的模板编译器解析。
  如果你想让 v-html 的内容也支持 scoped CSS,
  你可以使用 CSS modules 或使用一个额外的全局 <style> 元素,手动设置类似 BEM 的作用域策略。
  • 示例:
    template
v-show

基于表达式值的真假性,来改变元素的可见性。
期望的绑定值类型:any

  • 详细信息
    v-show 通过设置内联样式的 display CSS 属性来工作,
    当元素可见时将使用初始 display 值。当条件改变时,也会触发过渡效果。
v-if

基于表达式值的真假性,来条件性地渲染元素或者模板片段。
期望的绑定值类型:any

  • 详细信息
    当 v-if 元素被触发,元素及其所包含的指令/组件都会销毁和重构。如果初始条件是假,那么其内部的内容根本都不会被渲染。
    可用于 表示仅包含文本或多个元素的条件块。
    当条件改变时会触发过渡效果。
    当同时使用时,v-if 比 v-for 优先级更高。我们并不推荐在一元素上同时使用这两个指令 — 查看列表渲染指南详情。
v-else

表示 v-if 或 v-if / v-else-if 链式调用的“else 块”。
无需传入表达式

  • 详细信息
    限定:上一个兄弟元素必须有 v-if 或 v-else-if。
    可用于 表示仅包含文本或多个元素的条件块。

  • 示例

<div v-if="Math.random() > 0.5">
  Now you see me
</div>
<div v-else>
  Now you don't
</div>
v-else-if

表示 v-if 的“else if 块”。可以进行链式调用。
期望的绑定值类型:any

  • 详细信息
    限定:上一个兄弟元素必须有 v-if 或 v-else-if。
    可用于 表示仅包含文本或多个元素的条件块。

  • 示例

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>
v-for

基于原始数据多次渲染元素或模板块。
期望的绑定值类型:Array | Object | number | string | Iterable

  • 详细信息
    指令值必须使用特殊语法 alias in expression 为正在迭代的元素提供一个别名:
<div v-for="item in items">
  {{ item.text }}
</div>

或者,你也可以为索引指定别名 (如果用在对象,则是键值):

<div v-for="(item, index) in items"></div>
<div v-for="(value, key) in object"></div>
<div v-for="(value, name, index) in object"></div>

v-for 的默认方式是尝试就地更新元素而不移动它们。
要强制其重新排序元素,你需要用特殊 attribute key 来提供一个排序提示:

<div v-for="item in items" :key="item.id">
  {{ item.text }}
</div>

v-for 也可以用于 Iterable Protocol 的实现,包括原生 Map 和 Set。

v-on

给元素绑定事件监听器。
缩写:@

  • 期望的绑定值类型:Function | Inline Statement | Object (不带参数)
    参数:event (使用对象语法则为可选项)
    修饰符:
.stop ——调用 event.stopPropagation()。
.prevent ——调用 event.preventDefault()。
.capture ——在捕获模式添加事件监听器。
.self ——只有事件从元素本身发出才触发处理函数。
.{keyAlias} ——只在某些按键下触发处理函数。
.once ——最多触发一次处理函数。
.left ——只在鼠标左键事件触发处理函数。
.right ——只在鼠标右键事件触发处理函数。
.middle ——只在鼠标中键事件触发处理函数。
.passive ——通过 { passive: true } 附加一个 DOM 事件。
  • 详细信息
    事件类型由参数来指定。表达式可以是一个方法名,一个内联声明,如果有修饰符则可省略。
    当用于普通元素,只监听原生 DOM 事件。当用于自定义元素组件,则监听子组件触发的自定义事件。
    当监听原生 DOM 事件时,方法接收原生事件作为唯一参数。如果使用内联声明,
    声明可以访问一个特殊的 $event 变量:v-on:click=“handle(‘ok’, $event)”。
    v-on 还支持绑定不带参数的事件/监听器对的对象。请注意,当使用对象语法时,不支持任何修饰符。

  • 示例:

<!-- 方法处理函数 -->
<button v-on:click="doThis"></button>

<!-- 动态事件 -->
<button v-on:[event]="doThis"></button>

<!-- 内联声明 -->
<button v-on:click="doThat('hello', $event)"></button>

<!-- 缩写 -->
<button @click="doThis"></button>

<!-- 使用缩写的动态事件 -->
<button @[event]="doThis"></button>

<!-- 停止传播 -->
<button @click.stop="doThis"></button>

<!-- 阻止默认事件 -->
<button @click.prevent="doThis"></button>

<!-- 不带表达式地阻止默认事件 -->
<form @submit.prevent></form>

<!-- 链式调用修饰符 -->
<button @click.stop.prevent="doThis"></button>

<!-- 按键用于 keyAlias 修饰符-->
<input @keyup.enter="onEnter" />

<!-- 点击事件将最多触发一次 -->
<button v-on:click.once="doThis"></button>

<!-- 对象语法 -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>

监听子组件的自定义事件 (当子组件的“my-event”事件被触发,处理函数将被调用):

<MyComponent @my-event="handleThis" />

<!-- 内联声明 -->
<MyComponent @my-event="handleThis(123, $event)" />
v-bind
  动态的绑定一个或多个 attribute,也可以是组件的 prop。
  缩写:: 或者 . (当使用 .prop 修饰符)
  期望:any (带参数) | Object (不带参数)
  参数:attrOrProp (可选的)

  修饰符:
  .camel ——将短横线命名的 attribute 转变为驼峰式命名。
  .prop ——强制绑定为 DOM property。3.2+
  .attr ——强制绑定为 DOM attribute。3.2+
  
  用途:
  当用于绑定 class 或 style attribute,v-bind 支持额外的值类型如数组或对象。详见下方的指南链接。
  在处理绑定时,Vue 默认会利用 in 操作符来检查该元素上是否定义了和绑定的 key 同名的 DOM property。
  如果存在同名的 property,则 Vue 会把作为 DOM property 赋值,而不是作为 attribute 设置。
  这个行为在大多数情况都符合期望的绑定值类型,
  但是你也可以显式用 .prop 和 .attr 修饰符来强制绑定方式。有时这是必要的,特别是在和自定义元素打交道时。
  当用于组件 props 绑定时,所绑定的 props 必须在子组件中已被正确声明。
  当不带参数使用时,可以用于绑定一个包含了多个 attribute 名称-绑定值对的对象。
  • 示例:
<!-- 绑定 attribute -->
<img v-bind:src="imageSrc" />

<!-- 动态 attribute 名 -->
<button v-bind:[key]="value"></button>

<!-- 缩写 -->
<img :src="imageSrc" />

<!-- 缩写形式的动态 attribute 名 -->
<button :[key]="value"></button>

<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName" />

<!-- class 绑定 -->
<div :class="{ red: isRed }"></div>
<div :class="[classA, classB]"></div>
<div :class="[classA, { classB: isB, classC: isC }]"></div>

<!-- style 绑定 -->
<div :style="{ fontSize: size + 'px' }"></div>
<div :style="[styleObjectA, styleObjectB]"></div>

<!-- 绑定对象形式的 attribute -->
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>

<!-- prop 绑定。“prop” 必须在子组件中已声明。 -->
<MyComponent :prop="someThing" />

<!-- 传递子父组件共有的 prop -->
<MyComponent v-bind="$props" />

<!-- XLink -->
<svg><a :xlink:special="foo"></a></svg>
.prop 修饰符也有专门的缩写,.:

template
<div :someProperty.prop="someObject"></div>

<!-- 等同于 -->
<div .someProperty="someObject"></div>

当在 DOM 内模板使用 .camel 修饰符,可以驼峰化 v-bind attribute 的名称,例如 SVG viewBox attribute:

<svg :view-box.camel="viewBox"></svg>

如果使用字符串模板或使用构建步骤预编译模板,则不需要 .camel。

v-model
在表单输入元素或组件上创建双向绑定。

- 期望的绑定值类型:根据表单输入元素或组件输出的值而变化

仅限:

<input>
<select>
<textarea>
components
修饰符:

.lazy ——监听 change 事件而不是 input
.number ——将输入的合法符串转为数字
.trim ——移除输入内容两端空格
参考:

表单输入绑定
组件事件 - 配合 v-model 使用
v-slot

用于声明具名插槽或是期望接收 props 的作用域插槽。
缩写:#
期望的绑定值类型:能够合法在函数参数位置使用的 JavaScript 表达式。
支持解构语法。绑定值是可选的——只有在给作用域插槽传递 props 才需要。
参数:插槽名 (可选,默认是 default)
仅限:

components (用于带有 prop 的单个默认插槽)

  • 示例:
<!-- 具名插槽 -->
<BaseLayout>
  <template v-slot:header>
    Header content
  </template>

  <template v-slot:default>
    Default slot content
  </template>

  <template v-slot:footer>
    Footer content
  </template>
</BaseLayout>

<!-- 接收 prop 的具名插槽 -->
<InfiniteScroll>
  <template v-slot:item="slotProps">
    <div class="item">
      {{ slotProps.item.text }}
    </div>
  </template>
</InfiniteScroll>

<!-- 接收 prop 的默认插槽,并解构 -->
<Mouse v-slot="{ x, y }">
  Mouse position: {{ x }}, {{ y }}
</Mouse>

组件 - 插槽

v-pre

跳过该元素及其所有子元素的编译。
无需传入

  • 详细信息
    元素内具有 v-pre,所有 Vue 模板语法都会被保留并按原样渲染。最常见的用例就是显示原始双大括号标签及内容。

  • 示例:

<span v-pre>{{ this will not be compiled }}</span>
v-once

仅渲染元素和组件一次,并跳过之后的更新。
无需传入

  • 详细信息
    在随后的重新渲染,元素/组件及其所有子项将被当作静态内容并跳过渲染。这可以用来优化更新时的性能。
<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>
<!-- 带有子元素的元素 -->
<div v-once>
  <h1>comment</h1>
  <p>{{msg}}</p>
</div>
<!-- 组件 -->
<MyComponent v-once :comment="msg" />
<!-- `v-for` 指令 -->
<ul>
  <li v-for="i in list" v-once>{{i}}</li>
</ul>

从 3.2 起,你也可以搭配 v-memo 的无效条件来缓存部分模板。
数据绑定语法 - 插值

v-memo
  • 期望的绑定值类型:any[]

  • 详细信息
    缓存一个模板的子树。在元素和组件上都可以使用。为了实现缓存,
    该指令需要传入一个固定长度的依赖值数组进行比较。
    如果数组里的每个值都与最后一次的渲染相同,那么整个子树的更新将被跳过。举例来说:

<div v-memo="[valueA, valueB]">
  ...
</div>

当组件重新渲染,如果 valueA 和 valueB 都保持不变,这个

及其子项的所有更新都将被跳过。
实际上,甚至虚拟 DOM 的 vnode 创建也将被跳过,因为缓存的子树副本可以被重新使用。
正确指定缓存数组很重要,否则应该生效的更新可能被跳过。
v-memo 传入空依赖数组 (v-memo=“[]”) 将与 v-once 效果相同。

与 v-for 一起使用
v-memo 仅用于性能至上场景中的微小优化,应该很少需要。
最常见的情况可能是有助于渲染海量 v-for 列表 (长度超过 1000 的情况):

<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
  <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
  <p>...more child nodes</p>
</div>

当组件的 selected 状态改变,默认会重新创建大量的 vnode,尽管绝大部分都跟之前是一模一样的。
v-memo 用在这里本质上是在说“只有当该项的被选中状态改变时才需要更新”。
这使得每个选中状态没有变的项能完全重用之前的 vnode 并跳过差异比较。
注意这里 memo 依赖数组中并不需要包含 item.id,因为 Vue 也会根据 item 的 :key 进行判断。

警告
当搭配 v-for 使用 v-memo,确保两者都绑定在同一个元素上。v-memo 不能用在 v-for 内部。

v-memo 也能被用于在一些默认优化失败的边际情况下,手动避免子组件出现不需要的更新。
但是一样的,开发者需要负责指定正确的依赖数组以免跳过必要的更新。

v-cloak

用于隐藏尚未完成编译的 DOM 模板。
无需传入

  • 详细信息
    该指令只在没有构建步骤的环境下需要使用。
    当使用直接在 DOM 中书写的模板时,可能会出现一种叫做“未编译模板闪现”的情况:
    用户可能先看到的是还没编译完成的双大括号标签,直到挂载的组件将它们替换为实际渲染的内容。
    v-cloak 会保留在所绑定的元素上,直到相关组件实例被挂载后才移除。
    配合像 [v-cloak] { display: none } 这样的 CSS 规则,它可以在组件编译完毕前隐藏原始模板。

  • 示例:

[v-cloak] {
  display: none;
}
template
<div v-cloak>
  {{ message }}
</div>

直到编译完成前,

将不可见。

组件

内置组件

组件注册和使用
内置组件无需注册便可以直接在模板中使用。它们也是 tree-shakeable 的:仅在使用时才会包含在构建中。

在渲染函数中使用它们时,需要显式导入。例如:

import { h, Transition } from 'vue'

h(Transition, {
  /* props */
})

为单个元素或组件提供动画过渡效果。
Props

interface TransitionProps {
  /**
   * 用于自动生成过渡 CSS class 名。
   * 例如 `name: 'fade'` 将自动扩展为 `.fade-enter`、
   * `.fade-enter-active` 等。
   */
  name?: string
  /**
   * 是否应用 CSS 过渡 class。
   * 默认:true
   */
  css?: boolean
  /**
-    * 指定要等待的过渡事件类型
   * 来确定过渡结束的时间。
   * 默认情况下会自动检测
-    * 持续时间较长的类型。
   */
  type?: 'transition' | 'animation'
  /**
   * 显式指定过渡的持续时间。
   * 默认情况下是等待过渡效果的根元素的第一个 `transitionend`
   * 或`animationend`事件。
   */
  duration?: number | { enter: number; leave: number }
  /**
   * 控制离开/进入过渡的时序。
   * 默认情况下是同时的。
   */
  mode?: 'in-out' | 'out-in' | 'default'
  /**
   * 是否对初始渲染使用过渡。
   * 默认:false
   */
  appear?: boolean

  /**
   * 用于自定义过渡 class 的 prop。
   * 在模板中使用短横线命名,例如:enter-from-class="xxx"
   */
  enterFromClass?: string
  enterActiveClass?: string
  enterToClass?: string
  appearFromClass?: string
  appearActiveClass?: string
  appearToClass?: string
  leaveFromClass?: string
  leaveActiveClass?: string
  leaveToClass?: string
}

事件

@before-enter
@before-leave
@enter
@leave
@appear
@after-enter
@after-leave
@after-appear
@enter-cancelled
@leave-cancelled (v-show only)
@appear-cancelled
  • 示例
<Transition>
  <div v-if="ok">toggled content</div>
</Transition>
动态组件,初始渲染时带有过渡模式 + 动画出现:

template
<Transition name="fade" mode="out-in" appear>
  <component :is="view"></component>
</Transition>
监听过渡事件:

template
<Transition @after-enter="onTransitionComplete">
  <div v-show="ok">toggled content</div>
</Transition>

为列表中的多个元素或组件提供过渡效果。
Props
拥有与 除了 mode 以外所有的 props,并增加了两个额外的 props:

interface TransitionGroupProps extends Omit<TransitionProps, 'mode'> {
  /**
   * 如果未定义,则渲染为片段 (fragment)。
   */
  tag?: string
  /**
   * 用于自定义过渡期间被应用的 CSS class。
   * 在模板中使用 kebab-case,例如 move-class="xxx"
   */
  moveClass?: string
}

事件
抛出与 相同的事件。

  • 详细信息
  默认情况下,<TransitionGroup> 不会渲染一个容器 DOM 元素,但是可以通过 tag prop 启用。
  注意,每个 <transition-group> 的子节点必须有独立的 key,动画才能正常工作。
  <TransitionGroup> 支持通过 CSS transform 控制移动效果。
  当一个子节点在屏幕上的位置在更新之后发生变化时,
  它会被添加一个使其位移的 CSS class (基于 name attribute 推导,
  或使用 move-class prop 显式配置)。
  如果使其位移的 class 被添加时 CSS 的 transform 属性是“可过渡的”,
  那么该元素会基于 FLIP 技巧平滑地到达动画终点。
  • 示例
<TransitionGroup tag="ul" name="slide">
  <li v-for="item in items" :key="item.id">
    {{ item.text }}
  </li>
</TransitionGroup>

缓存包裹在其中的动态切换组件。
Props

interface KeepAliveProps {
  /**
   * 如果指定,则只有与 `include` 名称
   * 匹配的组件才会被缓存。
   */
  include?: MatchPattern
  /**
   * 任何名称与 `exclude`
   * 匹配的组件都不会被缓存。
   */
  exclude?: MatchPattern
  /**
   * 最多可以缓存多少组件实例。
   */
  max?: number | string
}
type MatchPattern = string | RegExp | (string | RegExp)[]
  • 详细信息
    包裹动态组件时,会缓存不活跃的组件实例,而不是销毁它们。
    任何时候都只能有一个活跃组件实例作为 的直接子节点。
    当一个组件在 中被切换时,
    它的 activated 和 deactivated 生命周期钩子将被调用,
    用来替代 mounted 和 unmounted。这适用于 的直接子节点及其所有子孙节点。

  • 示例
    基本用法:

<KeepAlive>
  <component :is="view"></component>
</KeepAlive>

与 v-if / v-else 分支一起使用时,同一时间只能有一个组件被渲染:

<KeepAlive>
  <comp-a v-if="a > 1"></comp-a>
  <comp-b v-else></comp-b>
</KeepAlive>

与 一起使用:

<Transition>
  <KeepAlive>
    <component :is="view"></component>
  </KeepAlive>
</Transition>

使用 include / exclude:

<!-- 用逗号分隔的字符串 -->
<KeepAlive include="a,b">
  <component :is="view"></component>
</KeepAlive>

<!-- 正则表达式 (使用 `v-bind`) -->
<KeepAlive :include="/a|b/">
  <component :is="view"></component>
</KeepAlive>

<!-- 数组 (使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
  <component :is="view"></component>
</KeepAlive>

使用 max:

<KeepAlive :max="10">
  <component :is="view"></component>
</KeepAlive>

将其插槽内容渲染到 DOM 中的另一个位置。
Props

interface TeleportProps {
  /**
   * 必填项。指定目标容器。
   * 可以是选择器或实际元素。
   */
  to: string | HTMLElement
  /**
   * 当值为 `true` 时,内容将保留在其原始位置
   * 而不是移动到目标容器中。
   * 可以动态更改。
   */
  disabled?: boolean
}
  • 示例
    指定目标容器:
<teleport to="#some-id" />
<teleport to=".some-class" />
<teleport to="[data-teleport]" />

有条件地禁用:

<teleport to="#popup" :disabled="displayVideoInline">
  <video src="./my-movie.mp4">
</teleport>

用于协调对组件树中嵌套的异步依赖的处理。
Props

interface SuspenseProps {
  timeout?: string | number
}

事件

@resolve
@pending
@fallback
接受两个插槽:

如果在渲染时遇到异步依赖项 (异步组件和具有 async setup() 的组件),
它将等到所有异步依赖项解析完成时再显示默认插槽。

特殊元素

内置特殊元素

不是组件
和 具有类似组件的特性,也是模板语法的一部分。
但它们并非真正的组件,同时在模板编译期间会被编译掉。因此,它们通常在模板中用小写字母书写。

一个用于渲染动态组件或元素的“元组件”。
Props

interface DynamicComponentProps {
  is: string | Component
}
  • 详细信息
    要渲染的实际组件由 is prop 决定。
    当 is 是字符串,它既可以是 HTML 标签名也可以是组件的注册名。
    或者,is 也可以直接绑定到组件的定义。

  • 示例
    按注册名渲染组件 (选项式 API):

<script>
import Foo from './Foo.vue'
import Bar from './Bar.vue'

export default {
  components: { Foo, Bar },
  data() {
    return {
      view: 'Foo'
    }
  }
}
</script>

<template>
  <component :is="view" />
</template>

按定义渲染组件 (

<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>

<template>
  <component :is="Math.random() > 0.5 ? Foo : Bar" />
</template>

渲染 HTML 元素:

<component :is="href ? 'a' : 'span'"></component>

内置组件都可以传递给 is,但是如果想通过名称传递则必须先对其进行注册。举例来说:

<script>
import { Transition, TransitionGroup } from 'vue'

export default {
  components: {
    Transition,
    TransitionGroup
  }
}
</script>

<template>
  <component :is="isGroup ? 'TransitionGroup' : 'Transition'">
    ...
  </component>
</template>

如果将组件本身传递给 is 而不是其名称,则不需要注册,例如在

<script setup>
import { ref } from 'vue'

const tag = ref('input')
const username = ref('')
</script>

<template>
  <!-- 由于 'input' 是原生 HTML 元素,因此这个 v-model 不起作用 -->
  <component :is="tag" v-model="username" />
</template>

在实践中,这种极端情况并不常见,因为原生表单字段通常包裹在实际应用的组件中。
如果确实需要直接使用原生元素,那么你可以手动将 v-model 拆分为 attribute 和事件。
参考:动态组件

表示模板中的插槽内容出口。
Props

interface SlotProps {
  /**
   * 任何传递给 <slot> 的 prop 都可以作为作用域插槽
   * 的参数传递
   */
  [key: string]: any
  /**
   * 保留,用于指定插槽名。
   */
  name?: string
}
  • 详细信息
    元素可以使用 name attribute 来指定插槽名。
    当没有指定 name 时,将会渲染默认插槽。
    传递给插槽元素的附加 attributes 将作为插槽 props,传递给父级中定义的作用域插槽。
    元素本身将被其所匹配的插槽内容替换。
    Vue 模板里的 元素会被编译到 JavaScript,因此不要与原生 元素进行混淆。

参考:组件 - 插槽

特殊 Attributes

内置的特殊 Attributes
key

key 这个特殊的 attribute 主要作为 Vue 的虚拟 DOM 算法提示,在比较新旧节点列表时用于识别 vnode。
预期:number | string | symbol

  • 详细信息
    在没有 key 的情况下,Vue 将使用一种最小化元素移动的算法,
    并尽可能地就地更新/复用相同类型的元素。如果传了 key,
    则将根据 key 的变化顺序来重新排列元素,并且将始终移除/销毁 key 已经不存在的元素。
    同一个父元素下的子元素必须具有唯一的 key。重复的 key 将会导致渲染异常。
    最常见的用例是与 v-for 结合:
<ul>
  <li v-for="item in items" :key="item.id">...</li>
</ul>

也可以用于强制替换一个元素/组件而不是复用它。当你想这么做时它可能会很有用:
在适当的时候触发组件的生命周期钩子
触发过渡
举例来说:

<transition>
  <span :key="text">{{ text }}</span>
</transition>
当 text 变化时,<span> 总是会被替换而不是更新,因此 transition 将会被触发。

ref

用于注册模板引用。
预期:string | Function

  • 详细信息
    ref 用于注册元素或子组件的引用。
    使用选项式 API,引用将被注册在组件的 this.$refs 对象里:
<!-- 存储为 this.$refs.p -->
<p ref="p">hello</p>

使用组合式 API,引用将存储在与名字匹配的 ref 里:

<script setup>
import { ref } from 'vue'

const p = ref()
</script>

<template>
  <p ref="p">hello</p>
</template>

如果用于普通 DOM 元素,引用将是元素本身;如果用于子组件,引用将是子组件的实例。
或者 ref 可以接收一个函数值,用于对存储引用位置的完全控制:

<ChildComponent :ref="(el) => child = el" />

关于 ref 注册时机的重要说明:因为 ref 本身是作为渲染函数的结果来创建的,必须等待组件挂载后才能对它进行访问。
this.$refs 也是非响应式的,因此你不应该尝试在模板中使用它来进行数据绑定。

is

用于绑定动态组件。
预期:string | Component
用于原生元素 3.1+
当 is attribute 用于原生 HTML 元素时,
它将被当作 Customized built-in element,其为原生 web 平台的特性。
但是,在这种用例中,你可能需要 Vue 用其组件来替换原生元素,
如 DOM 模板解析注意事项所述。你可以在 is attribute 的值中加上 vue: 前缀,这样 Vue 就会把该元素渲染为 Vue 组件:

<table>
  <tr is="vue:my-row-component"></tr>
</table>

单文件组件

语法定义

SFC 语法定义
总览

一个 Vue 单文件组件 (SFC),通常使用 *.vue 作为文件扩展名,
它是一种使用了类似 HTML 语法的自定义文件格式,用于定义 Vue 组件。一个 Vue 单文件组件在语法上是兼容 HTML 的。
每一个 *.vue 文件都由三种顶层语言块构成:、

<template>
  <div class="example">{{ msg }}</div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'Hello world!'
    }
  }
}
</script>

<style>
.example {
  color: red;
}
</style>

<custom1>
  This could be e.g. documentation for the component.
</custom1>
相应语言块

每个 *.vue 文件最多可以包含一个顶层 块。
语块包裹的内容将会被提取、传递给 @vue/compiler-dom,
预编译为 JavaScript 渲染函数,并附在导出的组件上作为其 render 选项。

每个 *.vue 文件最多可以包含一个

每个 *.vue 文件最多可以包含一个

每个 *.vue 文件可以包含多个

自定义块

在一个 *.vue 文件中可以为任何项目特定需求使用额外的自定义块。
举例来说,一个用作写文档的 块。这里是一些自定义块的真实用例:
Gridsome:
vite-plugin-vue-gql:
vue-i18n:
自定义块的处理需要依赖工具链。如果你想要在构建中集成你的自定义语块,请参见相关工具链指南获取更多细节。

自动名称推导

SFC 在以下场景中会根据文件名自动推导其组件名:
开发警告信息中需要格式化组件名时;
DevTools 中观察组件时;
递归组件自引用时。例如一个名为 FooBar.vue 的组件可以在模板中通过 引用自己。
(同名情况下) 这比明确注册/导入的组件优先级低。

预处理器

代码块可以使用 lang 这个 attribute 来声明预处理器语言,最常见的用例就是在

<script lang="ts">
  // use TypeScript
</script>

lang 在任意块上都能使用,比如我们可以在

<template lang="pug">
p {{ msg }}
</template>

<style lang="scss">
$primary-color:   body {
    color: $primary-color;
  }
</style>

注意对不同预处理器的集成会根据你所使用的工具链而有所不同,具体细节请查看相应的工具链文档来确认:

Src 导入

如果你更喜欢将 *.vue 组件分散到多个文件中,可以为一个语块使用 src 这个 attribute 来导入一个外部文件:

<template src="./template.html"></template>
<style src="./style.css"></style>
<script src="./script.js"></script>

请注意 src 导入和 JS 模块导入遵循相同的路径解析规则,这意味着:
相对路径需要以 ./ 开头
你也可以从 npm 依赖中导入资源

<!-- 从所安装的 "todomvc-app-css" npm 包中导入一个文件 -->
<style src="todomvc-app-css/index.css" />

src 导入对自定义语块也同样适用:

<unit-test src="./unit-test.js">
</unit-test>
注释

在每一个语块中你都可以按照相应语言 (HTML、CSS、JavaScript 和 Pug 等等) 的语法书写注释。
对于顶层注释,请使用 HTML 的注释语法

单文件组件
  <script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。
    当同时使用 SFC 与组合式 API 时该语法是默认推荐。相比于普通的 <script> 语法,它具有更多优势:
  更少的样板内容,更简洁的代码。
  能够使用纯 TypeScript 声明 props 和自定义事件。
  更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。
  更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。
基本语法

要启用该语法,需要在

<script setup>
console.log('hello script setup')
</script>

里面的代码会被编译成组件 setup() 函数的内容。
这意味着与普通的

顶层的绑定会被暴露给模板

当使用

<script setup>
// 变量
const msg = 'Hello!'

// 函数
function log() {
  console.log(msg)
}
</script>

<template>
  <button @click="log">{{ msg }}</button>
</template>

import 导入的内容也会以同样的方式暴露。
这意味着我们可以在模板表达式中直接使用导入的 helper 函数,而不需要通过 methods 选项来暴露它:

<script setup>
import { capitalize } from './helpers'
</script>

<template>
  <div>{{ capitalize('hello') }}</div>
</template>
响应式

响应式状态需要明确使用响应式 API 来创建。和 setup() 函数的返回值一样,ref 在模板中使用的时候会自动解包:

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">{{ count }}</button>
</template>
使用组件
```
这里 MyComponent 应当被理解为像是在引用一个变量。
如果你使用过 JSX,此处的心智模型是类似的。
其 kebab-case 格式的 <my-component> 同样能在模板中使用——不过,
我们强烈建议使用 PascalCase 格式以保持一致性。同时这也有助于区分原生的自定义元素。
动态组件

由于组件是通过变量引用而不是基于字符串组件名注册的,

<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>

<template>
  <component :is="Foo" />
  <component :is="someCondition ? Foo : Bar" />
</template>

请注意组件是如何在三元表达式中被当做变量使用的。

递归组件

一个单文件组件可以通过它的文件名被其自己所引用。
例如:名为 FooBar.vue 的组件可以在其模板中用 引用它自己。
请注意这种方式相比于导入的组件优先级更低。
如果有具名的导入和组件自身推导的名字冲突了,可以为导入的组件添加别名:

import { FooBar as FooBarChild } from './components'
命名空间组件

可以使用带 . 的组件标签,例如 <Foo.Bar> 来引用嵌套在对象属性中的组件。
这在需要从单个文件中导入多个组件的时候非常有用:

<script setup>
import * as Form from './form-components'
</script>

<template>
  <Form.Input>
    <Form.Label>label</Form.Label>
  </Form.Input>
</template>
使用自定义指令

全局注册的自定义指令将正常工作。本地的自定义指令在

<script setup>
const vMyDirective = {
  beforeMount: (el) => {
    // 在元素上做些操作
  }
}
</script>
<template>
  <h1 v-my-directive>This is a Heading</h1>
</template>

如果指令是从别处导入的,可以通过重命名来使其符合命名规范:

<script setup>
import { myDirective as vMyDirective } from './MyDirective.js'
</script>
defineProps() 和 defineEmits()

为了在声明 props 和 emits 选项时获得完整的类型推导支持,
我们可以使用 defineProps 和 defineEmits API,它们将自动地在

<script setup>
const props = defineProps({
  foo: String
})

const emit = defineEmits(['change', 'delete'])
// setup 代码
</script>
  defineProps 和 defineEmits 都是只能在 <script setup> 中使用的编译器宏。
  他们不需要导入,且会随着 <script setup> 的处理过程一同被编译掉。
  defineProps 接收与 props 选项相同的值,defineEmits 接收与 emits 选项相同的值。
  defineProps 和 defineEmits 在选项传入后,会提供恰当的类型推导。
  传入到 defineProps 和 defineEmits 的选项会从 setup 中提升到模块的作用域。
  因此,传入的选项不能引用在 setup 作用域中声明的局部变量。
  这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块作用域内。
  如果使用了 TypeScript,使用纯类型声明来声明 prop 和 emit 也是可以的。
defineExpose

使用

<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

当父组件通过模板引用的方式获取到当前组件的实例,
获取到的实例会像这样 { a: number, b: number } (ref 会和在普通实例中一样被自动解包)

useSlots() 和 useAttrs()

<script setup>
import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs()
</script>

useSlots 和 useAttrs 是真实的运行时函数,
它的返回与 setupContext.slots 和 setupContext.attrs 等价。它们同样也能在普通的组合式 API 中使用。

与普通的
  <script setup> 可以和普通的 <script> 一起使用。普通的 <script> 在有这些需要的情况下或许会被使用到:
  声明无法在 <script setup> 中声明的选项,例如 inheritAttrs 或插件的自定义选项。
  声明模块的具名导出 (named exports)。
  运行只需要在模块作用域执行一次的副作用,或是创建单例对象。
<script>
// 普通 <script>, 在模块作用域下执行 (仅一次)
runSideEffectOnce()

// 声明额外的选项
export default {
  inheritAttrs: false,
  customOptions: {}
}
</script>

<script setup>
// 在 setup() 作用域中执行 (对每个实例皆如此)
</script>
顶层 await
  <script setup> 中可以使用顶层 await。结果代码会被编译成 async setup():
<script setup>
const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>

另外,await 的表达式会自动编译成在 await 之后保留当前组件实例上下文的格式。

注意
async setup() 必须与 Suspense 内置组件组合使用,Suspense 目前还是处于实验阶段的特性,会在将来的版本中稳定。

针对 TypeScript 的功能
针对类型的 props/emit 声明
  • props 和 emit 都可以通过给 defineProps 和 defineEmits 传递纯类型参数的方式来声明:
const props = defineProps<{
  foo: string
  bar?: number
}>()

const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
  • defineProps 或 defineEmits 要么使用运行时声明,要么使用类型声明。同时使用两种声明方式会导致编译报错。
  • 使用类型声明的时候,静态分析会自动生成等效的运行时声明,从而在避免双重声明的前提下确保正确的运行时行为。
  • 在开发模式下,编译器会试着从类型来推导对应的运行时验证。
    例如这里从 foo: string 类型中推断出 foo: String。如果类型是对导入类型的引用,
    这里的推导结果会是 foo: null (与 any 类型相等),因为编译器没有外部文件的信息。

在生产模式下,编译器会生成数组格式的声明来减少打包体积 (这里的 props 会被编译成 [‘foo’, ‘bar’])。

  • 生成的代码仍然是有着合法类型的 TypeScript 代码,它可以在后续的流程中被其他工具处理。
  • 截至目前,类型声明参数必须是以下内容之一,以确保正确的静态分析:
  • 类型字面量
  • 在同一文件中的接口或类型字面量的引用
  • 现在还不支持复杂的类型和从其他文件进行类型导入,但我们有计划在将来支持。
使用类型声明时的默认 props 值
  • 针对类型的 defineProps 声明的不足之处在于,它没有可以给 props 提供默认值的方式。
    为了解决这个问题,我们还提供了 withDefaults 编译器宏:
export interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})
  • 上面代码会被编译为等价的运行时 props 的 default 选项。
    此外,withDefaults 辅助函数提供了对默认值的类型检查,
    并确保返回的 props 的类型删除了已声明默认值的属性的可选标志。
限制

由于模块执行语义的差异,

CSS 功能

单文件组件 CSS 功能
组件作用域 CSS

<style scoped>
.example {
  color: red;
}
</style>

<template>
  <div class="example">hi</div>
</template>

转换为:

<style>
.example[data-v-f3f3eg9] {
  color: red;
}
</style>

<template>
  <div class="example" data-v-f3f3eg9>hi</div>
</template>
子组件的根元素

使用 scoped 后,父组件的样式将不会渗透到子组件中。
不过,子组件的根节点会同时被父组件的作用域样式和子组件的作用域样式影响。
这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。

深度选择器

处于 scoped 样式中的选择器如果想要做更“深度”的选择,也即:影响到子组件,可以使用 :deep() 这个伪类:

<style scoped>
.a :deep(.b) {
  /* ... */
}
</style>
上面的代码会被编译成:

css
.a[data-v-f3f3eg9] .b {
  /* ... */
}

TIP
通过 v-html 创建的 DOM 内容不会被作用域样式影响,但你仍然可以使用深度选择器来设置其样式。

插槽选择器

默认情况下,作用域样式不会影响到 渲染出来的内容,
因为它们被认为是父组件所持有并传递进来的。使用 :slotted 伪类以明确地将插槽内容作为选择器的目标:

<style scoped>
:slotted(div) {
  color: red;
}
</style>
全局选择器

如果想让其中一个样式规则应用到全局,比起另外创建一个

<style scoped>
:global(.red) {
  color: red;
}
</style>
混合使用局部与全局样式

你也可以在同一个组件中同时包含作用域样式和非作用域样式:

<style>
/* 全局样式 */
</style>

<style scoped>
/* 局部样式 */
</style>
作用域样式须知

作用域样式并没有消除对 class 的需求。由于浏览器渲染各种各样 CSS 选择器的方式,
p { color: red } 结合作用域样式使用时 (即当与 attribute 选择器组合的时候) 会慢很多倍。
如果你使用 class 或者 id 来替代,例如 .example { color: red },那你几乎就可以避免性能的损失。
小心递归组件中的后代选择器!对于一个使用了 .a .b 选择器的样式规则来说,
如果匹配到 .a 的元素包含了一个递归的子组件,那么所有的在那个子组件中的 .b 都会匹配到这条样式规则。

CSS Modules

一个

<template>
  <p :class="$style.red">This should be red</p>
</template>

<style module>
.red {
  color: red;
}
</style>

得出的 class 将被哈希化以避免冲突,实现了同样的将 CSS 仅作用于当前组件的效果。

自定义注入名称

你可以通过给 module attribute 一个值来自定义注入 class 对象的属性名:

<template>
  <p :class="classes.red">red</p>
</template>

<style module="classes">
.red {
  color: red;
}
</style>
与组合式 API 一同使用

可以通过 useCssModule API 在 setup() 和

import { useCssModule } from 'vue'

// 在 setup() 作用域中...
// 默认情况下, 返回 <style module> 的 class
useCssModule()

// 具名情况下, 返回 <style module="classes"> 的 class
useCssModule('classes')
CSS 中的 v-bind()

单文件组件的

<template>
  <div class="text">hello</div>
</template>

<script>
export default {
  data() {
    return {
      color: 'red'
    }
  }
}
</script>

<style>
.text {
  color: v-bind(color);
}
</style>

这个语法同样也适用于

<script setup>
const theme = {
  color: 'red'
}
</script>

<template>
  <p>hello</p>
</template>

<style scoped>
p {
  color: v-bind('theme.color');
}
</style>

实际的值会被编译成哈希化的 CSS 自定义属性,因此 CSS 本身仍然是静态的。
自定义属性会通过内联样式的方式应用到组件的根元素上,并且在源值变更的时候响应式地更新。

进阶 API

渲染函数

渲染函数 API
h()

创建虚拟 DOM 节点 (vnode)。

  • 类型
// 完整参数签名
function h(
  type: string | Component,
  props?: object | null,
  children?: Children | Slot | Slots
): VNode

// 省略 props
function h(type: string | Component, children?: Children | Slot): VNode

type Children = string | number | boolean | VNode | null | Children[]

type Slot = () => Children

type Slots = { [name: string]: Slot }
  • 为了便于阅读,对类型进行了简化。

  • 详细信息
    第一个参数既可以是一个字符串 (用于原生元素) 也可以是一个 Vue 组件定义。
    第二个参数是要传递的 prop,第三个参数是子节点。
    当创建一个组件的 vnode 时,子节点必须以插槽函数进行传递。
    如果组件只有默认槽,可以使用单个插槽函数进行传递。否则,必须以插槽函数的对象形式来传递。
    为了方便阅读,当子节点不是插槽对象时,可以省略 prop 参数。

  • 示例
    创建原生元素:

import { h } from 'vue'

// 除了 type 外,其他参数都是可选的
h('div')
h('div', { id: 'foo' })

// attribute 和 property 都可以用于 prop
// Vue 会自动选择正确的方式来分配它
h('div', { class: 'bar', innerHTML: 'hello' })

// class 与 style 可以像在模板中一样
// 用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })

// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })

// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')

// 没有 prop 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])

// children 数组可以同时包含 vnode 和字符串
h('div', ['hello', h('span', 'hello')])

创建组件:

import Foo from './Foo.vue'

// 传递 prop
h(Foo, {
  // 等价于 some-prop="hello"
  someProp: 'hello',
  // 等价于 @update="() => {}"
  onUpdate: () => {}
})

// 传递单个默认插槽
h(Foo, () => 'default slot')

// 传递具名插槽
// 注意,需要使用 `null` 来避免
// 插槽对象被当作是 prop
h(MyComponent, null, {
  default: () => 'default slot',
  foo: () => h('div', 'foo'),
  bar: () => [h('span', 'one'), h('span', 'two')]
})
mergeProps()

合并多个 props 对象,用于处理含有特定的 props 参数的情况。

  • 类型
function mergeProps(...args: object[]): object
  • 详细信息
    mergeProps() 支持以下特定 props 参数的处理,将它们合并成一个对象。
    class
    style
    onXxx 事件监听器——多个同名的事件监听器将被合并到一个数组。
    如果你不需要合并行为而是简单覆盖,可以使用原生 object spread 语法来代替。

  • 示例

import { mergeProps } from 'vue'

const one = {
  class: 'foo',
  onClick: handlerA
}

const two = {
  class: { bar: true },
  onClick: handlerB
}

const merged = mergeProps(one, two)
/**
 {
   class: 'foo bar',
   onClick: [handlerA, handlerB]
 }
 */
cloneVNode()

克隆一个 vnode。

  • 类型
function cloneVNode(vnode: VNode, extraProps?: object): VNode
  • 详细信息
    返回一个克隆的 vnode,可在原有基础上添加一些额外的 prop。
    Vnode 被认为是一旦创建就不能修改的,你不应该修改已创建的 vnode 的 prop,而应该附带不同的/额外的 prop 来克隆它。
    Vnode 具有特殊的内部属性,因此克隆它并不像 object spread 一样简单。cloneVNode() 处理了大部分这样的内部逻辑。

  • 示例

import { h, cloneVNode } from 'vue'

const original = h('div')
const cloned = cloneVNode(original, { id: 'foo' })
isVNode()
  • 判断一个值是否为 vnode 类型。
  • 类型
function isVNode(value: unknown): boolean
resolveComponent()

按名称手动解析已注册的组件。

  • 类型
function resolveComponent(name: string): Component | string
  • 详细信息
    备注:如果你可以直接引入组件就不需使用此方法。
    为了能从正确的组件上下文进行解析,resolveComponent() 必须在渲染函数内调用。
    如果组件未找到,会抛出一个运行时警告,并返回组件名字符串。

  • 示例

const { h, resolveComponent } = Vue

export default {
  render() {
    const ButtonCounter = resolveComponent('ButtonCounter')
    return h(ButtonCounter)
  }
}
resolveDirective()

按名称手动解析已注册的指令。

  • 类型
function resolveDirective(name: string): Directive | undefined
  • 详细信息
    备注:如果你可以直接引入组件就不需使用此方法。
    为了能从正确的组件上下文进行解析,resolveDirective() 必须在渲染函数内调用。
    如果指令没有找到,会抛出一个运行时警告,并返回 undefined。
withDirectives()

用于给 vnode 增加自定义指令。

  • 类型
function withDirectives(
  vnode: VNode,
  directives: DirectiveArguments
): VNode

// [Directive, value, argument, modifiers]
type DirectiveArguments = Array<
  | [Directive]
  | [Directive, any]
  | [Directive, any, string]
  | [Directive, any, string, DirectiveModifiers]
  • 详细信息
    用自定义指令包装一个现有的 vnode。第二个参数是自定义指令数组。
    每个自定义指令也可以表示为 [Directive, value, argument, modifiers] 形式的数组。如果不需要,可以省略数组的尾元素。

  • 示例

import { h, withDirectives } from 'vue'

// 一个自定义指令
const pin = {
  mounted() {
    /* ... */
  },
  updated() {
    /* ... */
  }
}

// <div v-pin:top.animate="200"></div>
const vnode = withDirectives(h('div'), [
  [pin, 200, 'top', { animate: true }]
])
withModifiers()

用于向事件处理函数添加内置 v-on 修饰符。

  • 类型
function withModifiers(fn: Function, modifiers: string[]): Function
  • 示例
import { h, withModifiers } from 'vue'

const vnode = h('button', {
  // 等价于 v-on.stop.prevent
  onClick: withModifiers(() => {
    // ...
  }, ['stop', 'prevent'])
})

服务端渲染

服务端渲染 API
renderToString()

导出自 vue/server-renderer

  • 类型
function renderToString(
  input: App | VNode,
  context?: SSRContext
): Promise<string>
  • 示例
import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'

const app = createSSRApp({
  data: () => ({ msg: 'hello' }),
  template: `<div>{{ msg }}</div>`
})

;(async () => {
  const html = await renderToString(app)
  console.log(html)
})()
SSR 上下文

你可以传入一个可选的上下文对象用来在渲染过程中记录额外的数据,例如访问 Teleport 的内容:

const ctx = {}
const html = await renderToString(app, ctx)

console.log(ctx.teleports) // { '#teleported': 'teleported content' }

这个页面中的其他大多数 SSR API 也可以接受一个上下文对象。该上下文对象可以在组件代码里通过 useSSRContext 辅助函数进行访问。

renderToNodeStream()

将输入渲染为一个 Node.js Readable stream 实例。
导出自 vue/server-renderer

  • 类型
function renderToNodeStream(
  input: App | VNode,
  context?: SSRContext
): Readable
  • 示例
// 在一个 Node.js http 处理函数内
renderToNodeStream(app).pipe(res)

备注
vue/server-renderer 的 ESM 构建不支持此方法,因为它是与 Node.js 环境分离的。请换为使用 pipeToNodeWritable。

pipeToNodeWritable()

将输入渲染并 pipe 到一个 Node.js Writable stream 实例。
导出自 vue/server-renderer

  • 类型
function pipeToNodeWritable(
  input: App | VNode,
  context: SSRContext = {},
  writable: Writable
): void
  • 示例
// 在一个 Node.js http 处理函数内
pipeToNodeWritable(app, {}, res)
renderToWebStream()

将输入渲染为一个 Web ReadableStream 实例。
导出自 vue/server-renderer

  • 类型
function renderToWebStream(
  input: App | VNode,
  context?: SSRContext
): ReadableStream
  • 示例
// 在一个支持 ReadableStream 的环境下
return new Response(renderToWebStream(app))

备注
在不能全局暴露 ReadableStream 构造函数的环境下,请换为使用 pipeToWebWritable()。

pipeToWebWritable()

将输入渲染并 pipe 到一个 Web WritableStream 实例。
导出自 vue/server-renderer

  • 类型
function pipeToWebWritable(
  input: App | VNode,
  context: SSRContext = {},
  writable: WritableStream
): void
  • 示例
    通常与 TransformStream 结合使用:
// 诸如 CloudFlare worker 这样的环境中,TransformStream 是可用的。
// 在 Node.js 中,TransformStream 需要从 'stream/web' 显示导入。
const { readable, writable } = new TransformStream()
pipeToWebWritable(app, {}, writable)

return new Response(readable)
renderToSimpleStream()

通过一个简单的接口,将输入以 stream 模式进行渲染。
导出自 vue/server-renderer

  • 类型
function renderToSimpleStream(
  input: App | VNode,
  context: SSRContext,
  options: SimpleReadable
): SimpleReadable

interface SimpleReadable {
  push(content: string | null): void
  destroy(err: any): void
}
  • 示例
let res = ''

renderToSimpleStream(
  app,
  {},
  {
    push(chunk) {
      if (chunk === null) {
        // done
        console(`render complete: ${res}`)
      } else {
        res += chunk
      }
    },
    destroy(err) {
      // error encountered
    }
  }
)
useSSRContext()

一个运行时 API,用于获取已传递给 renderToString() 或其他服务端渲染 API 的上下文对象。

  • 类型
function useSSRContext<T = Record<string, any>>(): T | undefined
  • 示例
    得到的上下文能够作为附加信息用于渲染最终的 HTML (例如 head 中的元数据)。
<script setup>
import { useSSRContext } from 'vue'

// 确保只在服务端渲染时调用
// https://cn.vitejs.dev/guide/ssr.html#conditional-logic
if (import.meta.env.SSR) {
  const ctx = useSSRContext()
  // ...给上下文对象添加属性
}
</script>
  • TypeScript 工具类型

TypeScript 工具类型
  • 此页面仅列出了一些可能需要解释其使用方式的常用工具类型。有关导出类型的完整列表,请查看源代码。
PropType
  • 用于在用运行时 props 声明时给一个 prop 标注更复杂的类型定义。

  • 示例

import { PropType } from 'vue'

interface Book {
  title: string
  author: string
  year: number
}

export default {
  props: {
    book: {
-       // 提供一个比 `Object` 更具体的类型
      type: Object as PropType<Book>,
      required: true
    }
  }
}
ComponentCustomProperties
  • 用于增强组件实例类型以支持自定义全局属性。

  • 示例

import axios from 'axios'

declare module 'vue' {
  interface ComponentCustomProperties {
    $http: typeof axios
    $translate: (key: string) => string
  }
}

TIP

  • 类型扩展必须被放置在一个模块 .ts 或 .d.ts 文件中。查看类型扩展指南了解更多细节
ComponentCustomOptions
  • 用来扩展组件选项类型以支持自定义选项。

  • 示例

import { Route } from 'vue-router'

declare module 'vue' {
  interface ComponentCustomOptions {
    beforeRouteEnter?(to: any, from: any, next: () => void): void
  }
}

TIP

  • 类型扩展必须被放置在一个模块 .ts 或 .d.ts 文件中。查看类型扩展指南了解更多细节。
ComponentCustomProps

用于扩展全局可用的 TSX props,以便在 TSX 元素上使用没有在组件选项上定义过的 props。

  • 示例
declare module 'vue' {
  interface ComponentCustomProps {
    hello?: string
  }
}

export {}
- // 现在即使没有在组件选项上定义过 hello 这个 prop 也依然能通过类型检查了
<MyComponent hello="world" />

TIP

  • 类型扩展必须被放置在一个模块 .ts 或 .d.ts 文件中。查看类型扩展指南了解更多细节。
CSSProperties
  • 用于扩展在样式属性绑定上允许的值的类型。

  • 示例
    允许任意自定义 CSS 属性:

declare module 'vue' {
  interface CSSProperties {
    [key: `--${string}`]: string
  }
}
<div style={ { '--bg-color': 'blue' } }>
html
<div :style="{ '--bg-color': 'blue' }">

TIP

  • 类型增强必须被放置在一个模块 .ts 或 .d.ts 文件中。查看类型增强指南了解更多细节。

参考

  • SFC

自定义渲染

自定义渲染器 API
createRenderer()

创建一个自定义渲染器。通过提供平台特定的节点创建以及更改 API,你可以在非 DOM 环境中也享受到 Vue 核心运行时的特性。

  • 类型
function createRenderer<HostNode, HostElement>(
  options: RendererOptions<HostNode, HostElement>
): Renderer<HostElement>

interface Renderer<HostElement> {
  render: RootRenderFunction<HostElement>
  createApp: CreateAppFunction<HostElement>
}

interface RendererOptions<HostNode, HostElement> {
  patchProp(
    el: HostElement,
    key: string,
    prevValue: any,
    nextValue: any,
    // the rest is unused for most custom renderers
    isSVG?: boolean,
    prevChildren?: VNode<HostNode, HostElement>[],
    parentComponent?: ComponentInternalInstance | null,
    parentSuspense?: SuspenseBoundary | null,
    unmountChildren?: UnmountChildrenFn
  ): void
  insert(
    el: HostNode,
    parent: HostElement,
    anchor?: HostNode | null
  ): void
  remove(el: HostNode): void
  createElement(
    type: string,
    isSVG?: boolean,
    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

  // optional, DOM-specific
  querySelector?(selector: string): HostElement | null
  setScopeId?(el: HostElement, id: string): void
  cloneNode?(node: HostNode): HostNode
  insertStaticContent?(
    content: string,
    parent: HostElement,
    anchor: HostNode | null,
    isSVG: boolean
  ): [HostNode, HostNode]
}
  • 示例
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 实现的。
要想了解一个简单一些的实现,请参考 @vue/runtime-test,这是一个 Vue 自己做单元测试的私有包。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值