Composition API 是 Vue 3 中新增的 API,Vue 3 仍可以使用 Options API。
createApp
用于创建一个 Vue 对象。
setup
setup 组件选项就是使用组合式 API 的入口,它是一个函数,接收两个参数,props和context,返回一个对象。
需要注意,setup 的调用发生在 beforeCreate 和 created 钩子之间,这时 data、computed 和 methods 还没创建,所以访问不到它们,另外this也是不能用的,因为它找不到组件实例。如果组件内使用了 async setup() ,需要在父组件使用该组件的外面加上<Suspense></Suspense>,否则将无法渲染<template>中的内容。
<Suspense><child /></Suspense>
script setup 语法糖
script 标签加上 setup,相当于整个 script 标签的内容嵌套在 setup ,里面定义的函数和变量自动返回,顶层不用也不能再写 return。和 async setup() 一样,顶层使用了 await 的话,需要在父组件使用该组件的外面加上<Suspense></Suspense>。
<template><div>{{ arr[0] }}</div></template><script setup>const p = await Promise.reolve(3) // 顶层 await, 父组件需要加上<Suspense></Suspense>const fn = async () => {const t = await Promise.reolve(3) // 不是顶层 await,可以不加return t}const arr = ref([1,2])// 以上代码即可返回 p,arr,fn// 错误写法://return {//p,arr,fn//}</script>
reactive
reactive 是 Vue 3 中用于将对象转换成响应式对象的 API,并且嵌套的子对象也会转换成响应式对象。它返回的是一个 Proxy 对象。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="app">x: {{ position.x }} y: {{ position.y }}</div><script type="module">import { createApp, reactive } from "./node_modules/vue/dist/vue.esm-browser.js"const app = createApp({props: {user: {type: String,required: true,}},setup(props, context) {console.log(props)console.log(context)const position = reactive({x: 0,y: 0,})return {position}},mounted() {this.position.x = 100 // 如果没有用reactive处理对象,这里的改变不会生效},})// console.log(app)app.mount("#app")</script></body></html>
生命周期钩子
在 setup() 内部调用生命周期钩子,只需要在钩子前加上 “on”,比如调用 mounted -> onMounted。
因为 setup() 是围绕 beforeCreate 和 created生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 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 |
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="app">x: {{ position.x }} <br> y: {{ position.y }}</div><script type="module">import {createApp,reactive,onMounted,onUnmounted,} from "./node_modules/vue/dist/vue.esm-browser.js"function useMousePosition() {const position = reactive({x: 0,y: 0,})const update = e => {position.x = e.pageXposition.y = e.pageY}onMounted(() => {window.addEventListener('mousemove', update)})onUnmounted(() => {window.removeEventListener('mousemove', update)})return position}const app = createApp({setup() {const position = useMousePosition()return {position}},})app.mount("#app")</script></body></html>
toRefs
toRefs 可以把响应式对象的属性也转换成响应式的。如果从一个响应式对象中用{}解构出基本数据类型的属性,它不是响应式的,这时候 toRefs 就派上用场。
toRefs 会给属性创建一个包含 value 属性的对象,该对象是响应式的。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="app">x: {{ x }} <br> y: {{ y }}</div><script type="module">import {createApp,reactive,onMounted,onUnmounted,toRefs} from "./node_modules/vue/dist/vue.esm-browser.js"function useMousePosition() {const position = reactive({x: 0,y: 0,})const update = e => {position.x = e.pageXposition.y = e.pageY}onMounted(() => {window.addEventListener('mousemove', update)})onUnmounted(() => {window.removeEventListener('mousemove', update)})return toRefs(position)}const app = createApp({setup() {const { x, y } = useMousePosition() // 如果没有 toRefs 处理 position,这里解构出的x,y 就不是响应式的return {x,y}},})// console.log(app)app.mount("#app")</script></body></html>
ref
ref 能把一个基本数据类型转换成包含一个 value 属性的响应式对象。如果传递给了一个对象,内部会调用 reactive 创建 Proxy 对象返回。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><div id="app"><button @click="increase">按钮</button><span>{{ count }}</span></div><script type="module">import { createApp, ref } from './node_modules/vue/dist/vue.esm-browser.js'function useCount () {const count = ref(0)return {count,increase: () => {count.value++}}}createApp({setup () {return {...useCount()}}}).mount('#app')</script></body></html>
computed
computed 可以创建计算属性,它接收一个函数,并且在函数中需要处理另外一个响应式数据。
下面的案例,点击按钮,往 todos 插入一个对象,activeCount 的值也会发生变化。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><div id="app"><button @click="push">按钮</button>未完成:{{ activeCount }}</div><script type="module">import { createApp, reactive, computed } from './node_modules/vue/dist/vue.esm-browser.js'const data = [{ text: '看书', completed: false },{ text: '敲代码', completed: false },{ text: '约会', completed: true }]createApp({setup () {const todos = reactive(data)const activeCount = computed(() => {return todos.filter(item => !item.completed).length})return {activeCount,push: () => {todos.push({text: '开会',completed: false})}}}}).mount('#app')</script></body></html>
watch
watch 可以创建一个监听器。
三个参数:
- 第一个参数:要监听的数据,接收一个 ref 或者 reactive 返回的对象,或者数组
- 第二个参数:监听到变化后执行的函数,这个函数接收两个参数,分别是新值和旧值
- 第三个参数:选项对象,deep 和 immediate
返回值:
- 取消监听的函数,调用该函数可以取消监听。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><div id="app"><p>请问一个 yes/no 的问题:<input v-model="question"></p><p>{{ answer }}</p></div><script type="module">// https://www.yesno.wtf/apiimport { createApp, ref, watch } from './node_modules/vue/dist/vue.esm-browser.js'createApp({setup () {const question = ref('')const answer = ref('')watch(question, async (newValue, oldValue) => {const response = await fetch('https://www.yesno.wtf/api')const data = await response.json()answer.value = data.answer})return {question,answer}}}).mount('#app')</script></body></html>
watchEffect
watch 的简化版本,它接收一个函数,监听函数里使用的响应式数据的变化。返回一个取消函数。
const count = ref(0)watchEffect(() => console.log(count.value))// -> 输出 0count.value++// -> 输出 1
