Vue 3.0 組合式API 介紹

2023-05-10 14:26 更新

提示

在閱讀文檔之前,你應(yīng)該已經(jīng)熟悉了這兩個(gè) Vue 基礎(chǔ)創(chuàng)建組件。

在 Vue Mastery 上觀看關(guān)于組合式 API 的免費(fèi)視頻。

通過(guò)創(chuàng)建 Vue 組件,我們可以將接口的可重復(fù)部分及其功能提取到可重用的代碼段中。僅此一項(xiàng)就可以使我們的應(yīng)用程序在可維護(hù)性和靈活性方面走得更遠(yuǎn)。然而,我們的經(jīng)驗(yàn)已經(jīng)證明,光靠這一點(diǎn)可能是不夠的,尤其是當(dāng)你的應(yīng)用程序變得非常大的時(shí)候——想想幾百個(gè)組件。在處理如此大的應(yīng)用程序時(shí),共享和重用代碼變得尤為重要。

假設(shè)在我們的應(yīng)用程序中,我們有一個(gè)視圖來(lái)顯示某個(gè)用戶的倉(cāng)庫(kù)列表。除此之外,我們還希望應(yīng)用搜索和篩選功能。處理此視圖的組件可能如下所示:

  1. // src/components/UserRepositories.vue
  2. export default {
  3. components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  4. props: {
  5. user: { type: String }
  6. },
  7. data () {
  8. return {
  9. repositories: [], // 1
  10. filters: { ... }, // 3
  11. searchQuery: '' // 2
  12. }
  13. },
  14. computed: {
  15. filteredRepositories () { ... }, // 3
  16. repositoriesMatchingSearchQuery () { ... }, // 2
  17. },
  18. watch: {
  19. user: 'getUserRepositories' // 1
  20. },
  21. methods: {
  22. getUserRepositories () {
  23. // 使用 `this.user` 獲取用戶倉(cāng)庫(kù)
  24. }, // 1
  25. updateFilters () { ... }, // 3
  26. },
  27. mounted () {
  28. this.getUserRepositories() // 1
  29. }
  30. }

該組件有以下幾個(gè)職責(zé):

  1. 從假定的外部 API 獲取該用戶名的倉(cāng)庫(kù),并在用戶更改時(shí)刷新它
  2. 使用 searchQuery 字符串搜索存儲(chǔ)庫(kù)
  3. 使用 filters 對(duì)象篩選倉(cāng)庫(kù)

用組件的選項(xiàng) (data、computed、methods、watch) 組織邏輯在大多數(shù)情況下都有效。然而,當(dāng)我們的組件變得更大時(shí),邏輯關(guān)注點(diǎn)的列表也會(huì)增長(zhǎng)。這可能會(huì)導(dǎo)致組件難以閱讀和理解,尤其是對(duì)于那些一開(kāi)始就沒(méi)有編寫(xiě)這些組件的人來(lái)說(shuō)。

Vue 選項(xiàng)式 API: 按選項(xiàng)類型分組的代碼

一個(gè)大型組件的示例,其中邏輯關(guān)注點(diǎn)是按顏色分組。

這種碎片化使得理解和維護(hù)復(fù)雜組件變得困難。選項(xiàng)的分離掩蓋了潛在的邏輯問(wèn)題。此外,在處理單個(gè)邏輯關(guān)注點(diǎn)時(shí),我們必須不斷地“跳轉(zhuǎn)”相關(guān)代碼的選項(xiàng)塊。

如果我們能夠?qū)⑴c同一個(gè)邏輯關(guān)注點(diǎn)相關(guān)的代碼配置在一起會(huì)更好。而這正是組合式 API 使我們能夠做到的。

#組合式 API 基礎(chǔ)

既然我們知道了為什么,我們就可以知道怎么做。為了開(kāi)始使用組合式 API,我們首先需要一個(gè)可以實(shí)際使用它的地方。在 Vue 組件中,我們將此位置稱為 setup。

#setup 組件選項(xiàng)

觀看 Vue Mastery 上的免費(fèi) setup 視頻。

新的 setup 組件選項(xiàng)在創(chuàng)建組件之前執(zhí)行,一旦 props 被解析,并充當(dāng)合成 API 的入口點(diǎn)。

WARNING

由于在執(zhí)行 setup 時(shí)尚未創(chuàng)建組件實(shí)例,因此在 setup 選項(xiàng)中沒(méi)有 this。這意味著,除了 props 之外,你將無(wú)法訪問(wèn)組件中聲明的任何屬性——本地狀態(tài)、計(jì)算屬性方法。

setup 選項(xiàng)應(yīng)該是一個(gè)接受 propscontext 的函數(shù),我們將在稍后討論。此外,我們從 setup 返回的所有內(nèi)容都將暴露給組件的其余部分 (計(jì)算屬性、方法、生命周期鉤子等等) 以及組件的模板。

讓我們添加 setup 到我們的組件中:

  1. // src/components/UserRepositories.vue
  2. export default {
  3. components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  4. props: {
  5. user: { type: String }
  6. },
  7. setup(props) {
  8. console.log(props) // { user: '' }
  9. return {} // 這里返回的任何內(nèi)容都可以用于組件的其余部分
  10. }
  11. // 組件的“其余部分”
  12. }

現(xiàn)在讓我們從提取第一個(gè)邏輯關(guān)注點(diǎn)開(kāi)始 (在原始代碼段中標(biāo)記為“1”)。

  1. 從假定的外部 API 獲取該用戶名的倉(cāng)庫(kù),并在用戶更改時(shí)刷新它

我們將從最明顯的部分開(kāi)始:

  • 倉(cāng)庫(kù)列表
  • 更新倉(cāng)庫(kù)列表的函數(shù)
  • 返回列表和函數(shù),以便其他組件選項(xiàng)可以訪問(wèn)它們

  1. // src/components/UserRepositories.vue `setup` function
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. // 在我們的組件內(nèi)
  4. setup (props) {
  5. let repositories = []
  6. const getUserRepositories = async () => {
  7. repositories = await fetchUserRepositories(props.user)
  8. }
  9. return {
  10. repositories,
  11. getUserRepositories // 返回的函數(shù)與方法的行為相同
  12. }
  13. }

這是我們的出發(fā)點(diǎn),但它還不能工作,因?yàn)槲覀兊?repositories 變量是非響應(yīng)式的。這意味著從用戶的角度來(lái)看,倉(cāng)庫(kù)列表將保持為空。我們來(lái)解決這個(gè)問(wèn)題!

#ref 的響應(yīng)式變量

在 Vue 3.0 中,我們可以通過(guò)一個(gè)新的 ref 函數(shù)使任何響應(yīng)式變量在任何地方起作用,如下所示:

  1. import { ref } from 'vue'
  2. const counter = ref(0)

ref 接受參數(shù)并返回它包裝在具有 value property 的對(duì)象中,然后可以使用該 property 訪問(wèn)或更改響應(yīng)式變量的值:

  1. import { ref } from 'vue'
  2. const counter = ref(0)
  3. console.log(counter) // { value: 0 }
  4. console.log(counter.value) // 0
  5. counter.value++
  6. console.log(counter.value) // 1

在對(duì)象中包裝值似乎不必要,但在 JavaScript 中保持不同數(shù)據(jù)類型的行為統(tǒng)一是必需的。這是因?yàn)樵?JavaScript 中,NumberString 等基本類型是通過(guò)值傳遞的,而不是通過(guò)引用傳遞的:

按引用傳遞與按值傳遞

在任何值周?chē)加幸粋€(gè)包裝器對(duì)象,這樣我們就可以在整個(gè)應(yīng)用程序中安全地傳遞它,而不必?fù)?dān)心在某個(gè)地方失去它的響應(yīng)性。

提示

換句話說(shuō),ref 對(duì)我們的值創(chuàng)建了一個(gè)響應(yīng)式引用。使用引用的概念將在整個(gè)組合式 API 中經(jīng)常使用。

回到我們的例子,讓我們創(chuàng)建一個(gè)響應(yīng)式的 repositories 變量:

  1. // src/components/UserRepositories.vue `setup` function
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. import { ref } from 'vue'
  4. // in our component
  5. setup (props) {
  6. const repositories = ref([])
  7. const getUserRepositories = async () => {
  8. repositories.value = await fetchUserRepositories(props.user)
  9. }
  10. return {
  11. repositories,
  12. getUserRepositories
  13. }
  14. }

完成!現(xiàn)在,每當(dāng)我們調(diào)用 getUserRepositories 時(shí),repositories 都將發(fā)生變化,視圖將更新以反映更改。我們的組件現(xiàn)在應(yīng)該如下所示:

  1. // src/components/UserRepositories.vue
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. import { ref } from 'vue'
  4. export default {
  5. components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  6. props: {
  7. user: { type: String }
  8. },
  9. setup (props) {
  10. const repositories = ref([])
  11. const getUserRepositories = async () => {
  12. repositories.value = await fetchUserRepositories(props.user)
  13. }
  14. return {
  15. repositories,
  16. getUserRepositories
  17. }
  18. },
  19. data () {
  20. return {
  21. filters: { ... }, // 3
  22. searchQuery: '' // 2
  23. }
  24. },
  25. computed: {
  26. filteredRepositories () { ... }, // 3
  27. repositoriesMatchingSearchQuery () { ... }, // 2
  28. },
  29. watch: {
  30. user: 'getUserRepositories' // 1
  31. },
  32. methods: {
  33. updateFilters () { ... }, // 3
  34. },
  35. mounted () {
  36. this.getUserRepositories() // 1
  37. }
  38. }

我們已經(jīng)將第一個(gè)邏輯關(guān)注點(diǎn)中的幾個(gè)部分移到了 setup 方法中,它們彼此非常接近。剩下的就是在 mounted 鉤子中調(diào)用 getUserRepositories,并設(shè)置一個(gè)監(jiān)聽(tīng)器,以便在 user prop 發(fā)生變化時(shí)執(zhí)行此操作。

我們將從生命周期鉤子開(kāi)始。

#生命周期鉤子注冊(cè)內(nèi)部 setup

為了使組合式 API 的特性與選項(xiàng)式 API 相比更加完整,我們還需要一種在 setup 中注冊(cè)生命周期鉤子的方法。這要?dú)w功于從 Vue 導(dǎo)出的幾個(gè)新函數(shù)。組合式 API 上的生命周期鉤子與選項(xiàng)式 API 的名稱相同,但前綴為 on:即 mounted 看起來(lái)像 onMounted。

這些函數(shù)接受在組件調(diào)用鉤子時(shí)將執(zhí)行的回調(diào)。

讓我們將其添加到 setup 函數(shù)中:

  1. // src/components/UserRepositories.vue `setup` function
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. import { ref, onMounted } from 'vue'
  4. // in our component
  5. setup (props) {
  6. const repositories = ref([])
  7. const getUserRepositories = async () => {
  8. repositories.value = await fetchUserRepositories(props.user)
  9. }
  10. onMounted(getUserRepositories) // on `mounted` call `getUserRepositories`
  11. return {
  12. repositories,
  13. getUserRepositories
  14. }
  15. }

現(xiàn)在我們需要對(duì) user prop 所做的更改做出反應(yīng)。為此,我們將使用獨(dú)立的 watch 函數(shù)。

#watch 響應(yīng)式更改

就像我們?nèi)绾问褂?watch 選項(xiàng)在組件內(nèi)的 user property 上設(shè)置偵聽(tīng)器一樣,我們也可以使用從 Vue 導(dǎo)入的 watch 函數(shù)執(zhí)行相同的操作。它接受 3 個(gè)參數(shù):

  • 一個(gè)響應(yīng)式引用或我們想要偵聽(tīng)的 getter 函數(shù)
  • 一個(gè)回調(diào)
  • 可選的配置選項(xiàng)

下面讓我們快速了解一下它是如何工作的

  1. import { ref, watch } from 'vue'
  2. const counter = ref(0)
  3. watch(counter, (newValue, oldValue) => {
  4. console.log('The new counter value is: ' + counter.value)
  5. })

例如,每當(dāng) counter 被修改時(shí) counter.value=5,watch 將觸發(fā)并執(zhí)行回調(diào) (第二個(gè)參數(shù)),在本例中,它將把 'The new counter value is:5' 記錄到我們的控制臺(tái)中。

以下是等效的選項(xiàng)式 API:

  1. export default {
  2. data() {
  3. return {
  4. counter: 0
  5. }
  6. },
  7. watch: {
  8. counter(newValue, oldValue) {
  9. console.log('The new counter value is: ' + this.counter)
  10. }
  11. }
  12. }

有關(guān) watch 的詳細(xì)信息,請(qǐng)參閱我們的深入指南。

現(xiàn)在我們將其應(yīng)用到我們的示例中:

  1. // src/components/UserRepositories.vue `setup` function
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. import { ref, onMounted, watch, toRefs } from 'vue'
  4. // 在我們組件中
  5. setup (props) {
  6. // 使用 `toRefs` 創(chuàng)建對(duì)prop的 `user` property 的響應(yīng)式引用
  7. const { user } = toRefs(props)
  8. const repositories = ref([])
  9. const getUserRepositories = async () => {
  10. // 更新 `prop.user` 到 `user.value` 訪問(wèn)引用值
  11. repositories.value = await fetchUserRepositories(user.value)
  12. }
  13. onMounted(getUserRepositories)
  14. // 在用戶 prop 的響應(yīng)式引用上設(shè)置一個(gè)偵聽(tīng)器
  15. watch(user, getUserRepositories)
  16. return {
  17. repositories,
  18. getUserRepositories
  19. }
  20. }

你可能已經(jīng)注意到在我們的 setup 的頂部使用了 toRefs。這是為了確保我們的偵聽(tīng)器能夠?qū)?user prop 所做的更改做出反應(yīng)。

有了這些變化,我們就把第一個(gè)邏輯關(guān)注點(diǎn)移到了一個(gè)地方。我們現(xiàn)在可以對(duì)第二個(gè)關(guān)注點(diǎn)執(zhí)行相同的操作——基于 searchQuery 進(jìn)行過(guò)濾,這次是使用計(jì)算屬性。

#獨(dú)立的 computed 屬性

refwatch 類似,也可以使用從 Vue 導(dǎo)入的 computed 函數(shù)在 Vue 組件外部創(chuàng)建計(jì)算屬性。讓我們回到我們的 counter 例子:

  1. import { ref, computed } from 'vue'
  2. const counter = ref(0)
  3. const twiceTheCounter = computed(() => counter.value * 2)
  4. counter.value++
  5. console.log(counter.value) // 1
  6. console.log(twiceTheCounter.value) // 2

在這里,computed 函數(shù)返回一個(gè)作為 computed 的第一個(gè)參數(shù)傳遞的 getter 類回調(diào)的輸出的一個(gè)只讀響應(yīng)式引用。為了訪問(wèn)新創(chuàng)建的計(jì)算變量的 value,我們需要像使用 ref 一樣使用 .value property。

讓我們將搜索功能移到 setup 中:

  1. // src/components/UserRepositories.vue `setup` function
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. import { ref, onMounted, watch, toRefs, computed } from 'vue'
  4. // in our component
  5. setup (props) {
  6. // 使用 `toRefs` 創(chuàng)建對(duì) props 的 `user` property 的響應(yīng)式引用
  7. const { user } = toRefs(props)
  8. const repositories = ref([])
  9. const getUserRepositories = async () => {
  10. // 更新 `props.user ` 到 `user.value` 訪問(wèn)引用值
  11. repositories.value = await fetchUserRepositories(user.value)
  12. }
  13. onMounted(getUserRepositories)
  14. // 在用戶 prop 的響應(yīng)式引用上設(shè)置一個(gè)偵聽(tīng)器
  15. watch(user, getUserRepositories)
  16. const searchQuery = ref('')
  17. const repositoriesMatchingSearchQuery = computed(() => {
  18. return repositories.value.filter(
  19. repository => repository.name.includes(searchQuery.value)
  20. )
  21. })
  22. return {
  23. repositories,
  24. getUserRepositories,
  25. searchQuery,
  26. repositoriesMatchingSearchQuery
  27. }
  28. }

對(duì)于其他的邏輯關(guān)注點(diǎn)我們也可以這樣做,但是你可能已經(jīng)在問(wèn)這個(gè)問(wèn)題了——這不就是把代碼移到 setup 選項(xiàng)并使它變得非常大嗎?嗯,那是真的。這就是為什么在繼續(xù)其他任務(wù)之前,我們將首先將上述代碼提取到一個(gè)獨(dú)立的組合式函數(shù)。讓我們從創(chuàng)建 useUserRepositories 開(kāi)始:

  1. // src/composables/useUserRepositories.js
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. import { ref, onMounted, watch } from 'vue'
  4. export default function useUserRepositories(user) {
  5. const repositories = ref([])
  6. const getUserRepositories = async () => {
  7. repositories.value = await fetchUserRepositories(user.value)
  8. }
  9. onMounted(getUserRepositories)
  10. watch(user, getUserRepositories)
  11. return {
  12. repositories,
  13. getUserRepositories
  14. }
  15. }

然后是搜索功能:

  1. // src/composables/useRepositoryNameSearch.js
  2. import { ref, computed } from 'vue'
  3. export default function useRepositoryNameSearch(repositories) {
  4. const searchQuery = ref('')
  5. const repositoriesMatchingSearchQuery = computed(() => {
  6. return repositories.value.filter(repository => {
  7. return repository.name.includes(searchQuery.value)
  8. })
  9. })
  10. return {
  11. searchQuery,
  12. repositoriesMatchingSearchQuery
  13. }
  14. }

現(xiàn)在在單獨(dú)的文件中有了這兩個(gè)功能,我們就可以開(kāi)始在組件中使用它們了。以下是如何做到這一點(diǎn):

  1. // src/components/UserRepositories.vue
  2. import useUserRepositories from '@/composables/useUserRepositories'
  3. import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
  4. import { toRefs } from 'vue'
  5. export default {
  6. components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  7. props: {
  8. user: { type: String }
  9. },
  10. setup (props) {
  11. const { user } = toRefs(props)
  12. const { repositories, getUserRepositories } = useUserRepositories(user)
  13. const {
  14. searchQuery,
  15. repositoriesMatchingSearchQuery
  16. } = useRepositoryNameSearch(repositories)
  17. return {
  18. // 因?yàn)槲覀儾⒉魂P(guān)心未經(jīng)過(guò)濾的倉(cāng)庫(kù)
  19. // 我們可以在 `repositories` 名稱下暴露過(guò)濾后的結(jié)果
  20. repositories: repositoriesMatchingSearchQuery,
  21. getUserRepositories,
  22. searchQuery,
  23. }
  24. },
  25. data () {
  26. return {
  27. filters: { ... }, // 3
  28. }
  29. },
  30. computed: {
  31. filteredRepositories () { ... }, // 3
  32. },
  33. methods: {
  34. updateFilters () { ... }, // 3
  35. }
  36. }

此時(shí),你可能已經(jīng)知道了這個(gè)練習(xí),所以讓我們跳到最后,遷移剩余的過(guò)濾功能。我們不需要深入了解實(shí)現(xiàn)細(xì)節(jié),因?yàn)檫@不是本指南的重點(diǎn)。

  1. // src/components/UserRepositories.vue
  2. import { toRefs } from 'vue'
  3. import useUserRepositories from '@/composables/useUserRepositories'
  4. import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
  5. import useRepositoryFilters from '@/composables/useRepositoryFilters'
  6. export default {
  7. components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  8. props: {
  9. user: { type: String }
  10. },
  11. setup(props) {
  12. const { user } = toRefs(props)
  13. const { repositories, getUserRepositories } = useUserRepositories(user)
  14. const {
  15. searchQuery,
  16. repositoriesMatchingSearchQuery
  17. } = useRepositoryNameSearch(repositories)
  18. const {
  19. filters,
  20. updateFilters,
  21. filteredRepositories
  22. } = useRepositoryFilters(repositoriesMatchingSearchQuery)
  23. return {
  24. // 因?yàn)槲覀儾⒉魂P(guān)心未經(jīng)過(guò)濾的倉(cāng)庫(kù)
  25. // 我們可以在 `repositories` 名稱下暴露過(guò)濾后的結(jié)果
  26. repositories: filteredRepositories,
  27. getUserRepositories,
  28. searchQuery,
  29. filters,
  30. updateFilters
  31. }
  32. }
  33. }

我們完成了!

請(qǐng)記住,我們只觸及了組合式 API 的表面以及它允許我們做什么。要了解更多信息,請(qǐng)參閱深入指南。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)