Composition API 是 Vue 3 中新增的 API,Vue 3 仍可以使用 Options API。

createApp

用于创建一个 Vue 对象。

setup

setup 组件选项就是使用组合式 API 的入口,它是一个函数,接收两个参数,propscontext,返回一个对象。
需要注意,setup 的调用发生在 beforeCreate 和 created 钩子之间,这时 data、computed 和 methods 还没创建,所以访问不到它们,另外this也是不能用的,因为它找不到组件实例。如果组件内使用了 async setup() ,需要在父组件使用该组件的外面加上<Suspense></Suspense>,否则将无法渲染<template>中的内容。

  1. <Suspense>
  2. <child />
  3. </Suspense>

script setup 语法糖

script 标签加上 setup,相当于整个 script 标签的内容嵌套在 setup ,里面定义的函数和变量自动返回,顶层不用也不能再写 return。和 async setup() 一样,顶层使用了 await 的话,需要在父组件使用该组件的外面加上<Suspense></Suspense>

  1. <template>
  2. <div>
  3. {{ arr[0] }}
  4. </div>
  5. </template>
  6. <script setup>
  7. const p = await Promise.reolve(3) // 顶层 await, 父组件需要加上<Suspense></Suspense>
  8. const fn = async () => {
  9. const t = await Promise.reolve(3) // 不是顶层 await,可以不加
  10. return t
  11. }
  12. const arr = ref([1,2])
  13. // 以上代码即可返回 p,arr,fn
  14. // 错误写法:
  15. //return {
  16. //p,arr,fn
  17. //}
  18. </script>

reactive

reactive 是 Vue 3 中用于将对象转换成响应式对象的 API,并且嵌套的子对象也会转换成响应式对象。它返回的是一个 Proxy 对象。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <div id="app">x: {{ position.x }} y: {{ position.y }}</div>
  11. <script type="module">
  12. import { createApp, reactive } from "./node_modules/vue/dist/vue.esm-browser.js"
  13. const app = createApp({
  14. props: {
  15. user: {
  16. type: String,
  17. required: true,
  18. }
  19. },
  20. setup(props, context) {
  21. console.log(props)
  22. console.log(context)
  23. const position = reactive({
  24. x: 0,
  25. y: 0,
  26. })
  27. return {
  28. position
  29. }
  30. },
  31. mounted() {
  32. this.position.x = 100 // 如果没有用reactive处理对象,这里的改变不会生效
  33. },
  34. })
  35. // console.log(app)
  36. app.mount("#app")
  37. </script>
  38. </body>
  39. </html>

生命周期钩子

在 setup() 内部调用生命周期钩子,只需要在钩子前加上 “on”,比如调用 mounted -> onMounted
因为 setup() 是围绕 beforeCreatecreated生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。
unmounted等于 Vue 2 的destroyed钩子。

选项式 API Hook inside setup
beforeCreate Not needed*
created Not needed*
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
activated onActivated
deactivated onDeactivated
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <div id="app">x: {{ position.x }} <br> y: {{ position.y }}</div>
  11. <script type="module">
  12. import {
  13. createApp,
  14. reactive,
  15. onMounted,
  16. onUnmounted,
  17. } from "./node_modules/vue/dist/vue.esm-browser.js"
  18. function useMousePosition() {
  19. const position = reactive({
  20. x: 0,
  21. y: 0,
  22. })
  23. const update = e => {
  24. position.x = e.pageX
  25. position.y = e.pageY
  26. }
  27. onMounted(() => {
  28. window.addEventListener('mousemove', update)
  29. })
  30. onUnmounted(() => {
  31. window.removeEventListener('mousemove', update)
  32. })
  33. return position
  34. }
  35. const app = createApp({
  36. setup() {
  37. const position = useMousePosition()
  38. return {
  39. position
  40. }
  41. },
  42. })
  43. app.mount("#app")
  44. </script>
  45. </body>
  46. </html>

toRefs

toRefs 可以把响应式对象的属性也转换成响应式的。如果从一个响应式对象中用{}解构出基本数据类型的属性,它不是响应式的,这时候 toRefs 就派上用场。
toRefs 会给属性创建一个包含 value 属性的对象,该对象是响应式的。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <div id="app">x: {{ x }} <br> y: {{ y }}</div>
  11. <script type="module">
  12. import {
  13. createApp,
  14. reactive,
  15. onMounted,
  16. onUnmounted,
  17. toRefs
  18. } from "./node_modules/vue/dist/vue.esm-browser.js"
  19. function useMousePosition() {
  20. const position = reactive({
  21. x: 0,
  22. y: 0,
  23. })
  24. const update = e => {
  25. position.x = e.pageX
  26. position.y = e.pageY
  27. }
  28. onMounted(() => {
  29. window.addEventListener('mousemove', update)
  30. })
  31. onUnmounted(() => {
  32. window.removeEventListener('mousemove', update)
  33. })
  34. return toRefs(position)
  35. }
  36. const app = createApp({
  37. setup() {
  38. const { x, y } = useMousePosition() // 如果没有 toRefs 处理 position,这里解构出的x,y 就不是响应式的
  39. return {
  40. x,y
  41. }
  42. },
  43. })
  44. // console.log(app)
  45. app.mount("#app")
  46. </script>
  47. </body>
  48. </html>

ref

ref 能把一个基本数据类型转换成包含一个 value 属性的响应式对象。如果传递给了一个对象,内部会调用 reactive 创建 Proxy 对象返回。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Document</title>
  7. </head>
  8. <body>
  9. <div id="app">
  10. <button @click="increase">按钮</button>
  11. <span>{{ count }}</span>
  12. </div>
  13. <script type="module">
  14. import { createApp, ref } from './node_modules/vue/dist/vue.esm-browser.js'
  15. function useCount () {
  16. const count = ref(0)
  17. return {
  18. count,
  19. increase: () => {
  20. count.value++
  21. }
  22. }
  23. }
  24. createApp({
  25. setup () {
  26. return {
  27. ...useCount()
  28. }
  29. }
  30. }).mount('#app')
  31. </script>
  32. </body>
  33. </html>

computed

computed 可以创建计算属性,它接收一个函数,并且在函数中需要处理另外一个响应式数据。
下面的案例,点击按钮,往 todos 插入一个对象,activeCount 的值也会发生变化。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Document</title>
  7. </head>
  8. <body>
  9. <div id="app">
  10. <button @click="push">按钮</button>
  11. 未完成:{{ activeCount }}
  12. </div>
  13. <script type="module">
  14. import { createApp, reactive, computed } from './node_modules/vue/dist/vue.esm-browser.js'
  15. const data = [
  16. { text: '看书', completed: false },
  17. { text: '敲代码', completed: false },
  18. { text: '约会', completed: true }
  19. ]
  20. createApp({
  21. setup () {
  22. const todos = reactive(data)
  23. const activeCount = computed(() => {
  24. return todos.filter(item => !item.completed).length
  25. })
  26. return {
  27. activeCount,
  28. push: () => {
  29. todos.push({
  30. text: '开会',
  31. completed: false
  32. })
  33. }
  34. }
  35. }
  36. }).mount('#app')
  37. </script>
  38. </body>
  39. </html>

watch

watch 可以创建一个监听器。
三个参数:

  • 第一个参数:要监听的数据,接收一个 ref 或者 reactive 返回的对象,或者数组
  • 第二个参数:监听到变化后执行的函数,这个函数接收两个参数,分别是新值和旧值
  • 第三个参数:选项对象,deep 和 immediate

返回值:

  • 取消监听的函数,调用该函数可以取消监听。
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Document</title>
  7. </head>
  8. <body>
  9. <div id="app">
  10. <p>
  11. 请问一个 yes/no 的问题:
  12. <input v-model="question">
  13. </p>
  14. <p>{{ answer }}</p>
  15. </div>
  16. <script type="module">
  17. // https://www.yesno.wtf/api
  18. import { createApp, ref, watch } from './node_modules/vue/dist/vue.esm-browser.js'
  19. createApp({
  20. setup () {
  21. const question = ref('')
  22. const answer = ref('')
  23. watch(question, async (newValue, oldValue) => {
  24. const response = await fetch('https://www.yesno.wtf/api')
  25. const data = await response.json()
  26. answer.value = data.answer
  27. })
  28. return {
  29. question,
  30. answer
  31. }
  32. }
  33. }).mount('#app')
  34. </script>
  35. </body>
  36. </html>

watchEffect

watch 的简化版本,它接收一个函数,监听函数里使用的响应式数据的变化。返回一个取消函数。

  1. const count = ref(0)
  2. watchEffect(() => console.log(count.value))
  3. // -> 输出 0
  4. count.value++
  5. // -> 输出 1