# 组合式 API

本节例子中代码使用的单文件组件语法

# setup

一个组件选项,在组件被创建之前props 被解析之后执行。它是组合式 API 的入口。

  • 入参:

    • {Data} props
    • {SetupContext} context

    与使用选项式 API 时的 this.$props 类似,该 props 对象将仅包含显性声明的 prop。并且,所有声明了的 prop,不管父组件是否向其传递了,都将出现在 props 对象中。其中未被传入的可选的 prop 的值会是 undefined

    如果需要检测一个可选的 prop 是否未被传递,你可以将其默认值设置为一个 Symbol:

    const isAbsent = Symbol()
    export default {
      props: {
        foo: { default: isAbsent }
      },
      setup(props) {
        if (props.foo === isAbsent) {
          // foo 没有被传入。
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  • 类型声明

    interface Data {
      [key: string]: unknown
    }
    
    interface SetupContext {
      attrs: Data
      slots: Slots
      emit: (event: string, ...args: unknown[]) => void
      expose: (exposed?: Record<string, any>) => void
    }
    
    function setup(props: Data, context: SetupContext): Data
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    TIP

    若要对传递给 setup() 的参数进行类型推断,你需要使用 defineComponent

  • 示例:

    使用模板:

    <!-- MyBook.vue -->
    <template>
      <div>{{ readersNumber }} {{ book.title }}</div>
    </template>
    
    <script>
      import { ref, reactive } from 'vue'
    
      export default {
        setup() {
          const readersNumber = ref(0)
          const book = reactive({ title: 'Vue 3 Guide' })
    
          // 暴露给模板
          return {
            readersNumber,
            book
          }
        }
      }
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    使用渲染函数:

    // MyBook.vue
    
    import { h, ref, reactive } from 'vue'
    
    export default {
      setup() {
        const readersNumber = ref(0)
        const book = reactive({ title: 'Vue 3 Guide' })
        // 请注意,我们需要在这里显式地使用 ref 值
        return () => h('div', [readersNumber.value, book.title])
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    如果返回了渲染函数,则不能再返回其他 property。如果需要将 property 暴露给外部访问,比如通过父组件的 ref,可以使用 expose

    // MyBook.vue
    
    import { h } from 'vue'
    
    export default {
      setup(props, { expose }) {
        const reset = () => {
          // 某些重置逻辑
        }
    
        // expose 只能被调用一次。
        // 如果需要暴露多个 property,则它们
        // 必须全部包含在传递给 expose 的对象中。
        expose({
          reset
        })
    
        return () => h('div')
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  • 参考组合式 API setup

# 生命周期钩子

可以通过直接导入 onX 函数来注册生命周期钩子:

import { onMounted, onUpdated, onUnmounted } from 'vue'

const MyComponent = {
  setup() {
    onMounted(() => {
      console.log('mounted!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    onUnmounted(() => {
      console.log('unmounted!')
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这些生命周期钩子注册函数只能在 setup() 期间同步使用,因为它们依赖于内部的全局状态来定位当前活动的实例 (此时正在调用其 setup() 的组件实例)。在没有当前活动实例的情况下,调用它们将会出错。

组件实例的上下文也是在生命周期钩子的同步执行期间设置的,因此,在生命周期钩子内同步创建的侦听器和计算属性也会在组件卸载时自动删除。

选项式 API 的生命周期选项和组合式 API 之间的映射

  • beforeCreate -> 使用 setup()

  • created -> 使用 setup()

  • beforeMount -> onBeforeMount

  • mounted -> onMounted

  • beforeUpdate -> onBeforeUpdate

  • updated -> onUpdated

  • beforeUnmount -> onBeforeUnmount

  • unmounted -> onUnmounted

  • errorCaptured -> onErrorCaptured

  • renderTracked -> onRenderTracked

  • renderTriggered -> onRenderTriggered

  • activated -> onActivated

  • deactivated -> onDeactivated

  • 参考组合式 API 生命周期钩子

# Provide / Inject

provideinject 启用依赖注入。这两者只能在使用当前活动实例的 setup() 期间被调用。

  • 类型声明

    interface InjectionKey<T> extends Symbol {}
    
    function provide<T>(key: InjectionKey<T> | string, value: T): void
    
    // 没有默认值
    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
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    Vue 提供了一个 InjectionKey 接口,该接口是扩展了 Symbol 的泛型类型。它可用于在生产者和消费者之间同步 inject 值的类型:

    import { InjectionKey, provide, inject } from 'vue'
    
    const key: InjectionKey<string> = Symbol()
    
    provide(key, 'foo') // 若提供非字符串值将出错
    
    const foo = inject(key) // foo 的类型: string | undefined
    
    1
    2
    3
    4
    5
    6
    7

    如果使用了字符串 key 或非类型化的 symbol,则需要显式声明 inject 值的类型:

    const foo = inject<string>('foo') // string | undefined
    
    1
  • 参考

# getCurrentInstance

getCurrentInstance 支持访问内部组件实例。

WARNING

getCurrentInstance 只暴露给高阶使用场景,典型的比如在库中。强烈反对在应用的代码中使用 getCurrentInstance。请不要把它当作在组合式 API 中获取 this 的替代方案来使用。

import { getCurrentInstance } from 'vue'

const MyComponent = {
  setup() {
    const internalInstance = getCurrentInstance()

    internalInstance.appContext.config.globalProperties // 访问 globalProperties
  }
}
1
2
3
4
5
6
7
8
9

getCurrentInstance 只能setup生命周期钩子中调用。

如需在 setup生命周期钩子外使用,请先在 setup 中调用 getCurrentInstance() 获取该实例然后再使用。

const MyComponent = {
  setup() {
    const internalInstance = getCurrentInstance() // 有效

    const id = useComponentId() // 有效

    const handleClick = () => {
      getCurrentInstance() // 无效
      useComponentId() // 无效

      internalInstance // 有效
    }

    onMounted(() => {
      getCurrentInstance() // 有效
    })

    return () =>
      h(
        'button',
        {
          onClick: handleClick
        },
        `uid: ${id}`
      )
  }
}

// 在组合式函数中调用也可以正常执行
function useComponentId() {
  return getCurrentInstance().uid
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32