1.创建一个应用实例
- 每个 Vue 应用都是通过用 createApp 函数创建一个新的应用实例
- 该应用实例是用来在应用中注册“全局”组件的
const app = Vue.createApp({
})
app.component('SearchInput', SearchInputComponent)
app.directive('focus', FocusDirective)
app.use(LocalePlugin)
- 应用实例暴露的大多数方法都会返回该同一实例,允许链式
Vue.createApp({
}).component('SearchInput', SearchInputComponent).directive('focus', FocusDirective).use(LocalePlugin)
2.setup
为了开始使用组合式 API,我们首先需要一个可以实际使用它的地方。在 Vue 组件中,我们将此位置称为 setup
新的 setup 选项在组件创建之前执行,一旦 props 被解析,就将作为组合式 API 的入口。
setup 选项是一个接收 props 和 context 的函数,我们将在之后进行讨论。此外,我们将 setup 返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板
使用 setup 函数时,它将接收两个参数:
- props
- context
props 是响应式的,当传入新的 prop 时,它将被更新。
但是,因为 props 是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。
如果需要解构 prop,可以在 setup 函数中使用 toRefs 函数来完成此操作:
// MyBook.vueimport {
toRefs } from 'vue'setup(props) {
const {
title } = toRefs(props)console.log(title.value)
}
如果 title 是可选的 prop,则传入的 props 中可能没有 title 。在这种情况下,toRefs 将不会为 title 创建一个 ref 。你需要使用 toRef 替代它:
// MyBook.vue
import {
toRef } from 'vue'
setup(props) {
const title = toRef(props, 'title')console.log(title.value)
}
Context
传递给 setup 函数的第二个参数是 context。context 是一个普通的 JavaScript 对象,它暴露组件的三个 property:
// MyBook.vueexport default {
setup(props, context) {
// Attribute (非响应式对象)console.log(context.attrs)// 插槽 (非响应式对象)console.log(context.slots)// 触发事件 (方法)console.log(context.emit)}
}
context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构。
// MyBook.vue
export default {
setup(props, {
attrs, slots, emit }) {
...}
}
attrs 和 slots 是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.x 或 slots.x 的方式引用 property。请注意,与 props 不同,attrs 和 slots 是非响应式的。如果你打算根据 attrs 或 slots 更改应用副作用,那么应该在 onUpdated 生命周期钩子中执行此操作。
注意,从 setup 返回的 refs 在模板中访问时是被自动浅解包的,因此不应在模板中使用 .value。
setup 还可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态:
// MyBook.vueimport {
h, ref, reactive } from 'vue'export default {
setup() {
const readersNumber = ref(0)const book = reactive({
title: 'Vue 3 Guide' })// 请注意这里我们需要显式调用 ref 的 valuereturn () => h('div', [readersNumber.value, book.title])}
}
使用 Provide
在 setup() 中使用 provide 时,我们首先从 vue 显式导入 provide 方法。这使我们能够调用 provide 来定义每个 property。
provide 函数允许你通过两个参数定义 property:
name ( 类型)
value
使用 MyMap 组件后,provide 的值可以按如下方式重构:
<!-- src/components/MyMap.vue -->
<template><MyMarker />
</template><script>
import {
provide } from 'vue'
import MyMarker from './MyMarker.vue'export default {
components: {
MyMarker},setup() {
provide('location', 'North Pole')provide('geolocation', {
longitude: 90,latitude: 135})}
}
</script>
使用 inject
在 setup() 中使用 inject 时,也需要从 vue 显式导入。导入以后,我们就可以调用它来定义暴露给我们的组件方式。
inject 函数有两个参数:
要 inject 的 property 的 name
默认值 (可选)
使用 MyMarker 组件,可以使用以下代码对其进行重构:
<!-- src/components/MyMarker.vue -->
<script>
import {
inject } from 'vue'export default {
setup() {
const userLocation = inject('location', 'The Universe')const userGeolocation = inject('geolocation')return {
userLocation,userGeolocation}}
}
</script>
响应性
添加响应性
为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用 ref 或 reactive。
使用 MyMap 组件,我们的代码可以更新如下:
<!-- src/components/MyMap.vue -->
<template><MyMarker />
</template><script>
import {
provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue'export default {
components: {
MyMarker},setup() {
const location = ref('North Pole')const geolocation = reactive({
longitude: 90,latitude: 135})provide('location', location)provide('geolocation', geolocation)}
}
</script>
现在,如果这两个 property 中有任何更改,MyMarker 组件也将自动更新!
#修改响应式 property
当使用响应式 provide / inject 值时,建议尽可能将对响应式 property 的所有修改限制在定义 provide 的组件内部。
例如,在需要更改用户位置的情况下,我们最好在 MyMap 组件中执行此操作。
<!-- src/components/MyMap.vue -->
<template><MyMarker />
</template><script>
import {
provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue'export default {
components: {
MyMarker},setup() {
const location = ref('North Pole')const geolocation = reactive({
longitude: 90,latitude: 135})provide('location', location)provide('geolocation', geolocation)return {
location}},methods: {
updateLocation() {
this.location = 'South Pole'}}
}
</script>
然而,有时我们需要在注入数据的组件内部更新 inject 的数据。在这种情况下,我们建议 provide 一个方法来负责改变响应式 property。
<!-- src/components/MyMap.vue -->
<template><MyMarker />
</template><script>
import {
provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue'export default {
components: {
MyMarker},setup() {
const location = ref('North Pole')const geolocation = reactive({
longitude: 90,latitude: 135})const updateLocation = () => {
location.value = 'South Pole'}provide('location', location)provide('geolocation', geolocation)provide('updateLocation', updateLocation)}
}
</script>
<!-- src/components/MyMarker.vue -->
<script>
import {
inject } from 'vue'export default {
setup() {
const userLocation = inject('location', 'The Universe')const userGeolocation = inject('geolocation')const updateUserLocation = inject('updateLocation')return {
userLocation,userGeolocation,updateUserLocation}}
}
</script>
最后,如果要确保通过 provide 传递的数据不会被 inject 的组件更改,我们建议对提供者的 property 使用 readonly。
<!-- src/components/MyMap.vue -->
<template><MyMarker />
</template><script>
import {
provide, reactive, readonly, ref } from 'vue'
import MyMarker from './MyMarker.vue'export default {
components: {
MyMarker},setup() {
const location = ref('North Pole')const geolocation = reactive({
longitude: 90,latitude: 135})const updateLocation = () => {
location.value = 'South Pole'}provide('location', readonly(location))provide('geolocation', readonly(geolocation))provide('updateLocation', updateLocation)}
}
</script>
3.带 ref 的响应式变量
这些函数接受一个回调,当钩子被组件调用时,该回调将被执行。
只有访问嵌套的 ref 时需要在模板中添加 .value:
让我们将其添加到 setup 函数中:
// src/components/UserRepositories.vue `setup` function
import {
fetchUserRepositories } from '@/api/repositories'
import {
ref, onMounted } from 'vue'// 在我们的组件中
setup (props) {
const repositories = ref([])const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)}onMounted(getUserRepositories) // 在 `mounted` 时调用 `getUserRepositories`return {
repositories,getUserRepositories}
}
使用解构的两个 property 的响应性都会丢失。对于这种情况,我们需要将我们的响应式对象转换为一组 ref。这些 ref 将保留与源对象的响应式关联:
import {
reactive, toRefs } from 'vue'const book = reactive({
author: 'Vue Team',year: '2020',title: 'Vue 3 Guide',description: 'You are reading this book right now ;)',price: 'free'
})let {
author, title } = toRefs(book)title.value = 'Vue 3 Detailed Guide' // 我们需要使用 .value 作为标题,现在是 ref
console.log(book.title) // 'Vue 3 Detailed Guide'
4.watch 响应式更改
就像我们在组件中使用 watch 选项并在 user property 上设置侦听器一样,我们也可以使用从 Vue 导入的 watch 函数执行相同的操作。它接受 3 个参数:
- 一个想要侦听的响应式引用或 getter 函数
- 一个回调
- 可选的配置选项
- 下面让我们快速了解一下它是如何工作的
import {
ref, watch } from 'vue'const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
现在我们将其应用到我们的示例中:
// src/components/UserRepositories.vue `setup` function
import {
fetchUserRepositories } from '@/api/repositories'
import {
ref, onMounted, watch, toRefs } from 'vue'// 在我们组件中
setup (props) {
// 使用 `toRefs` 创建对prop的 `user` property 的响应式引用const {
user } = toRefs(props)const repositories = ref([])const getUserRepositories = async () => {
// 更新 `prop.user` 到 `user.value` 访问引用值repositories.value = await fetchUserRepositories(user.value)}onMounted(getUserRepositories)// 在 user prop 的响应式引用上设置一个侦听器watch(user, getUserRepositories)return {
repositories,getUserRepositories}
}
5.独立的 computed 属性
与 ref 和 watch 类似,也可以使用从 Vue 导入的 computed 函数在 Vue 组件外部创建计算属性。让我们回到 counter 的例子:
import {
ref, computed } from 'vue'const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
6.生命周期
选项API | setup |
---|---|
beforeMount | onBeforeMount |
mounted | onMounted |
updated | onUpdated |
unmounted | onUnmounted |
因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。