什么是组合式 API?
组合式 API 即vue3.0新增的 setup 组件选项, 在创建组件之前执行,一旦 props 被解析,并充当合成 API 的入口点.因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。
// 之前版本
export default {
beforeCreate () {
console.log('this is beforeCreate')
},
created () {
console.log('this is created')
}
}
// 3.0 版本
export default {
setup() {
console.log('write beforeCreate and created code')
}
}
由于在执行 setup 时尚未创建组件实例,因此在 setup 选项中没有 this。因此,你只能访问以下 property:
propsattrsslotsemit换句话说,你将无法访问以下组件选项:datacomputedmethods
简单用法
setup 选项应该是一个接受 props 和 context 的函数。
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
我们从 setup 返回的所有内容都将暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板.
<template>
<h1>This is {{title}}</h1>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const title = ref('Vue3.0');
return {
title
}
}
}
</script>
上面代码在代码块之中我们使用 ref 创建了一个响应变量, 在 Vue 3.0 中,我们可以通过一个新的 ref 函数使任何响应式变量在任何地方起作用,其接受参数并返回它包装在具有 value property 的对象中,然后可以使用该 property 访问或更改响应式变量的值, 关于响应式可查看:响应性基础 API
import { ref } from 'vue'
const counter = ref(0)
console.log(counter) // { value: 0 }
console.log(counter.value) // 0
counter.value++
console.log(counter.value) // 1
setup 中访问组件周期钩子
在 setup中,你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
下表包含如何在 setup () 内部调用生命周期钩子:
| 选项式 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 |
在上面我们已经将结果, beforeCreate 和 created 生命周期的方法应该转移到setup()函数中执行。
setup 中使用watch
我们可以从 Vue 导入 watch 函数, 该函数接受三个参数:
- 一个响应式引用或我们想要侦听的 getter 函数
- 一个回调
- 可选的配置选项
import { ref, watch } from 'vue'
export default {
setup() {
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
}
}
setup 中使用computed
import { ref, computed } from 'vue'
export default {
setup() {
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
twiceTheCounter.value++ // error
}
}
上面代码在代码块之中我们使用 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象, 所以,twiceTheCounter.value++ 产生了报错。我们也可以使用具有 get 和 set 函数的对象来创建可写的 ref 对象。
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
setup 中使用Provide/inject
在 setup() 中使用 provide 时,我们首先从 vue 显式导入 provide 方法。这使我们能够调用 provide 时来定义每个 property。
provide 函数允许你通过两个参数定义 property:
- property 的 name ( 类型)
- property 的 value
<!-- 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>
在 setup() 中使用 inject 时,还需要从 vue 显式导入它。一旦我们这样做了,我们就可以调用它来定义如何将它暴露给我们的组件。 inject 函数有两个参数:
- 要注入的 property 的名称
- 一个默认的值 (可选)
<!-- 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>
为什么需要组合式 API?
在 vue3.0 之前的版本,我们可以通过创建 Vue 组件,将接口的可重复部分及其功能提取到可重用的代码段中,这使我们的应用程序在可维护性和灵活性方面走得更远。但光靠这一点可能是不够的,尤其是当你的应用程序变得非常大的时候——想想几百个组件。在处理如此大的应用程序时,共享和重用代码变得尤为重要。
假设在我们的应用程序中,我们有一个视图来显示某个用户的仓库列表。除此之外,我们还希望应用搜索和筛选功能。 该组件含有的功能有:
- 从假定的外部 API 获取该用户名的仓库,并在用户更改时刷新它
- 使用
searchQuery字符串搜索存储库 - 使用
filters对象筛选仓库
vue3.0 之前版本,我们的组件代码应该如下所示:
// src/components/UserRepositories.vue
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
data () {
return {
repositories: [], // 1
filters: { ... }, // 3
searchQuery: '' // 2
}
},
computed: {
filteredRepositories () { ... }, // 3
repositoriesMatchingSearchQuery () { ... }, // 2
},
watch: {
user: 'getUserRepositories' // 1
},
methods: {
getUserRepositories () {
// 使用 `this.user` 获取用户仓库
}, // 1
updateFilters () { ... }, // 3
},
mounted () {
this.getUserRepositories() // 1
}
}
而如果我们使用vue3.0 组合式 API, 那我们的代码可以重构为以下的代码:
外部 API 获取该用户名的仓库,并在用户更改时刷新它
// src/composables/useUserRepositories.js
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch } from 'vue'
export default function useUserRepositories(user) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
然后是搜索功能:
// src/composables/useRepositoryNameSearch.js
import { ref, computed } from 'vue'
export default function useRepositoryNameSearch(repositories) {
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(repository => {
return repository.name.includes(searchQuery.value)
})
})
return {
searchQuery,
repositoriesMatchingSearchQuery
}
}
在组件中使用他们:
// src/components/UserRepositories.vue
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import { toRefs } from 'vue'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
setup (props) {
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories)
return {
// 因为我们并不关心未经过滤的仓库
// 我们可以在 `repositories` 名称下暴露过滤后的结果
repositories: repositoriesMatchingSearchQuery,
getUserRepositories,
searchQuery,
}
},
data () {
return {
filters: { ... }, // 3
}
},
computed: {
filteredRepositories () { ... }, // 3
},
methods: {
updateFilters () { ... }, // 3
}
}
最终完成的结果:
// src/components/UserRepositories.vue
import { toRefs } from 'vue'
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import useRepositoryFilters from '@/composables/useRepositoryFilters'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
setup(props) {
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories)
const {
filters,
updateFilters,
filteredRepositories
} = useRepositoryFilters(repositoriesMatchingSearchQuery)
return {
// 因为我们并不关心未经过滤的仓库
// 我们可以在 `repositories` 名称下暴露过滤后的结果
repositories: filteredRepositories,
getUserRepositories,
searchQuery,
filters,
updateFilters
}
}
}