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.pageX
position.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.pageX
position.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/api
import { 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))
// -> 输出 0
count.value++
// -> 输出 1