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)視頻。

通過創(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è)視圖來顯示某個(gè)用戶的倉庫列表。除此之外,我們還希望應(yīng)用搜索和篩選功能。處理此視圖的組件可能如下所示:

// 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
  }
}

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

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

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

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

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

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

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

#組合式 API 基礎(chǔ)

既然我們知道了為什么,我們就可以知道怎么做。為了開始使用組合式 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)中沒有 this。這意味著,除了 props 之外,你將無法訪問組件中聲明的任何屬性——本地狀態(tài)、計(jì)算屬性方法。

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

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

// src/components/UserRepositories.vue


export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  setup(props) {
    console.log(props) // { user: '' }


    return {} // 這里返回的任何內(nèi)容都可以用于組件的其余部分
  }
  // 組件的“其余部分”
}

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

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

我們將從最明顯的部分開始:

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

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'


// 在我們的組件內(nèi)
setup (props) {
  let repositories = []
  const getUserRepositories = async () => {
    repositories = await fetchUserRepositories(props.user)
  }


  return {
    repositories,
    getUserRepositories // 返回的函數(shù)與方法的行為相同
  }
}

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

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

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

import { ref } from 'vue'


const counter = ref(0)

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

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

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

按引用傳遞與按值傳遞

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

提示

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

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

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'


// in our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }


  return {
    repositories,
    getUserRepositories
  }
}

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

// src/components/UserRepositories.vue
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'


export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  setup (props) {
    const repositories = ref([])
    const getUserRepositories = async () => {
      repositories.value = await fetchUserRepositories(props.user)
    }


    return {
      repositories,
      getUserRepositories
    }
  },
  data () {
    return {
      filters: { ... }, // 3
      searchQuery: '' // 2
    }
  },
  computed: {
    filteredRepositories () { ... }, // 3
    repositoriesMatchingSearchQuery () { ... }, // 2
  },
  watch: {
    user: 'getUserRepositories' // 1
  },
  methods: {
    updateFilters () { ... }, // 3
  },
  mounted () {
    this.getUserRepositories() // 1
  }
}

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

我們將從生命周期鉤子開始。

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

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

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

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

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'


// in our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }


  onMounted(getUserRepositories) // on `mounted` call `getUserRepositories`


  return {
    repositories,
    getUserRepositories
  }
}

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

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

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

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

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

import { ref, watch } from 'vue'


const counter = ref(0)
watch(counter, (newValue, oldValue) => {
  console.log('The new counter value is: ' + counter.value)
})

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

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

export default {
  data() {
    return {
      counter: 0
    }
  },
  watch: {
    counter(newValue, oldValue) {
      console.log('The new counter value is: ' + this.counter)
    }
  }
}

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

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

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'


// 在我們組件中
setup (props) {
  // 使用 `toRefs` 創(chuàng)建對prop的 `user` property 的響應(yīng)式引用
  const { user } = toRefs(props)


  const repositories = ref([])
  const getUserRepositories = async () => {
    // 更新 `prop.user` 到 `user.value` 訪問引用值
    repositories.value = await fetchUserRepositories(user.value)
  }


  onMounted(getUserRepositories)


  // 在用戶 prop 的響應(yīng)式引用上設(shè)置一個(gè)偵聽器
  watch(user, getUserRepositories)


  return {
    repositories,
    getUserRepositories
  }
}

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

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

#獨(dú)立的 computed 屬性

refwatch 類似,也可以使用從 Vue 導(dǎo)入的 computed 函數(shù)在 Vue 組件外部創(chuàng)建計(jì)算屬性。讓我們回到我們的 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

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

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

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'


// in our component
setup (props) {
  // 使用 `toRefs` 創(chuàng)建對 props 的 `user` property 的響應(yīng)式引用
  const { user } = toRefs(props)


  const repositories = ref([])
  const getUserRepositories = async () => {
    // 更新 `props.user ` 到 `user.value` 訪問引用值
    repositories.value = await fetchUserRepositories(user.value)
  }


  onMounted(getUserRepositories)


  // 在用戶 prop 的響應(yīng)式引用上設(shè)置一個(gè)偵聽器
  watch(user, getUserRepositories)


  const searchQuery = ref('')
  const repositoriesMatchingSearchQuery = computed(() => {
    return repositories.value.filter(
      repository => repository.name.includes(searchQuery.value)
    )
  })


  return {
    repositories,
    getUserRepositories,
    searchQuery,
    repositoriesMatchingSearchQuery
  }
}

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

// 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
  }
}

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

// 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 {
      // 因?yàn)槲覀儾⒉魂P(guān)心未經(jīng)過濾的倉庫
      // 我們可以在 `repositories` 名稱下暴露過濾后的結(jié)果
      repositories: repositoriesMatchingSearchQuery,
      getUserRepositories,
      searchQuery,
    }
  },
  data () {
    return {
      filters: { ... }, // 3
    }
  },
  computed: {
    filteredRepositories () { ... }, // 3
  },
  methods: {
    updateFilters () { ... }, // 3
  }
}

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

// 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 {
      // 因?yàn)槲覀儾⒉魂P(guān)心未經(jīng)過濾的倉庫
      // 我們可以在 `repositories` 名稱下暴露過濾后的結(jié)果
      repositories: filteredRepositories,
      getUserRepositories,
      searchQuery,
      filters,
      updateFilters
    }
  }
}

我們完成了!

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

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號