App下載

為什么說(shuō)組合式函數(shù)是 Vue3 中最棒的特性之一 ?

若即若離 2023-10-05 15:30:20 瀏覽數(shù) (2020)
反饋

微信截圖_20230927172059

為什么說(shuō)組合式函數(shù)是 Vue3 中最棒的特性之一 ? 

組合式函數(shù)(Composition API)是 Vue3 中引入的一個(gè)重要特性,它可以說(shuō)是 Vue3 中最棒的特性之一,主要有以下幾個(gè)原因:

  1. 更好的代碼組織

組合式函數(shù)讓組件邏輯可以通過組合多個(gè)小的單元函數(shù)來(lái)組織,每個(gè)函數(shù)負(fù)責(zé)一個(gè)具體的功能。這種函數(shù)式的編程范式可以讓代碼更加清晰易懂。

  1. 更好的代碼復(fù)用

組合函數(shù)可以很容易地在多個(gè)組件中復(fù)用,使得開發(fā)者可以抽象出通用的業(yè)務(wù)邏輯作為可復(fù)用的邏輯單元。這避免了同樣邏輯代碼的重復(fù)。

  1. 更好的類型推導(dǎo)

通過 TypeScript 的類型系統(tǒng),組合函數(shù)可以提供更準(zhǔn)確的代碼提示,提高開發(fā)效率。

  1. 更好的邏輯抽象

組合函數(shù)讓組件只需要關(guān)注自身的 UI 展示,通過組合函數(shù)將邏輯抽象成可重用的代碼,使組件代碼更加清晰和聚合。

  1. 更好的面向切面編程

組合函數(shù)天然適合面向切面編程,可以更方便地處理一些與組件邏輯無(wú)關(guān)的橫切關(guān)注點(diǎn),如日志、緩存等。

  1. 更好的邏輯復(fù)用和代碼組織

總之,組合式函數(shù)為 Vue 帶來(lái)了函數(shù)式編程的思想,可以幫助開發(fā)者寫出更優(yōu)雅的代碼,是 Vue3 相比 Vue2 最大的進(jìn)步之一。它讓 Vue 的編程體驗(yàn)更接近 React Hooks。

什么是“組合式函數(shù)”?

在 Vue 應(yīng)用的概念中,“組合式函數(shù)”(Composables) 是一個(gè)利用 Vue 的組合式 API 來(lái)封裝和復(fù)用有狀態(tài)邏輯的函數(shù)。

當(dāng)構(gòu)建前端應(yīng)用時(shí),我們常常需要復(fù)用公共任務(wù)的邏輯。例如為了在不同地方格式化時(shí)間,我們可能會(huì)抽取一個(gè)可復(fù)用的日期格式化函數(shù)。這個(gè)函數(shù)封裝了無(wú)狀態(tài)的邏輯:它在接收一些輸入后立刻返回所期望的輸出。復(fù)用無(wú)狀態(tài)邏輯的庫(kù)有很多,比如你可能已經(jīng)用過的 lodash 或是 date-fns。

相比之下,有狀態(tài)邏輯負(fù)責(zé)管理會(huì)隨時(shí)間而變化的狀態(tài)。一個(gè)簡(jiǎn)單的例子是跟蹤當(dāng)前鼠標(biāo)在頁(yè)面中的位置。在實(shí)際應(yīng)用中,也可能是像觸摸手勢(shì)或與數(shù)據(jù)庫(kù)的連接狀態(tài)這樣的更復(fù)雜的邏輯。

鼠標(biāo)跟蹤器示例?

如果我們要直接在組件中使用組合式 API 實(shí)現(xiàn)鼠標(biāo)跟蹤功能,它會(huì)是這樣的:

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const x = ref(0)
const y = ref(0)

function update(event) {
  x.value = event.pageX
  y.value = event.pageY
}

onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>

<template>Mouse position is at: {{ x }}, {{ y }}</template>

但是,如果我們想在多個(gè)組件中復(fù)用這個(gè)相同的邏輯呢?我們可以把這個(gè)邏輯以一個(gè)組合式函數(shù)的形式提取到外部文件中:

// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'

// 按照慣例,組合式函數(shù)名以“use”開頭
export function useMouse() {
  // 被組合式函數(shù)封裝和管理的狀態(tài)
  const x = ref(0)
  const y = ref(0)

  // 組合式函數(shù)可以隨時(shí)更改其狀態(tài)。
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  // 一個(gè)組合式函數(shù)也可以掛靠在所屬組件的生命周期上
  // 來(lái)啟動(dòng)和卸載副作用
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  // 通過返回值暴露所管理的狀態(tài)
  return { x, y }
}

下面是它在組件中使用的方式:

<script setup>
import { useMouse } from './mouse.js'

const { x, y } = useMouse()
</script>

<template>Mouse position is at: {{ x }}, {{ y }}</template>

如你所見,核心邏輯完全一致,我們做的只是把它移到一個(gè)外部函數(shù)中去,并返回需要暴露的狀態(tài)。和在組件中一樣,你也可以在組合式函數(shù)中使用所有的組合式 API?,F(xiàn)在,useMouse() 的功能可以在任何組件中輕易復(fù)用了。

更酷的是,你還可以嵌套多個(gè)組合式函數(shù):一個(gè)組合式函數(shù)可以調(diào)用一個(gè)或多個(gè)其他的組合式函數(shù)。這使得我們可以像使用多個(gè)組件組合成整個(gè)應(yīng)用一樣,用多個(gè)較小且邏輯獨(dú)立的單元來(lái)組合形成復(fù)雜的邏輯。實(shí)際上,這正是為什么我們決定將實(shí)現(xiàn)了這一設(shè)計(jì)模式的 API 集合命名為組合式 API。

舉例來(lái)說(shuō),我們可以將添加和清除 DOM 事件監(jiān)聽器的邏輯也封裝進(jìn)一個(gè)組合式函數(shù)中:

// event.js
import { onMounted, onUnmounted } from 'vue'

export function useEventListener(target, event, callback) {
  // 如果你想的話,
  // 也可以用字符串形式的 CSS 選擇器來(lái)尋找目標(biāo) DOM 元素
  onMounted(() => target.addEventListener(event, callback))
  onUnmounted(() => target.removeEventListener(event, callback))
}

有了它,之前的 useMouse() 組合式函數(shù)可以被簡(jiǎn)化為:

// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  useEventListener(window, 'mousemove', (event) => {
    x.value = event.pageX
    y.value = event.pageY
  })

  return { x, y }
}

每一個(gè)調(diào)用 useMouse() 的組件實(shí)例會(huì)創(chuàng)建其獨(dú)有的 x、y 狀態(tài)拷貝,因此他們不會(huì)互相影響。如果你想要在組件之間共享狀態(tài),請(qǐng)閱讀狀態(tài)管理這一章。

異步狀態(tài)示例?

useMouse() 組合式函數(shù)沒有接收任何參數(shù),因此讓我們?cè)賮?lái)看一個(gè)需要接收一個(gè)參數(shù)的組合式函數(shù)示例。在做異步數(shù)據(jù)請(qǐng)求時(shí),我們常常需要處理不同的狀態(tài):加載中、加載成功和加載失敗。

<script setup>
import { ref } from 'vue'

const data = ref(null)
const error = ref(null)

fetch('...')
  .then((res) => res.json())
  .then((json) => (data.value = json))
  .catch((err) => (error.value = err))
</script>

<template>
  <div v-if="error">Oops! Error encountered: {{ error.message }}</div>
  <div v-else-if="data">
    Data loaded:
    <pre>{{ data }}</pre>
  </div>
  <div v-else>Loading...</div>
</template>

如果在每個(gè)需要獲取數(shù)據(jù)的組件中都要重復(fù)這種模式,那就太繁瑣了。讓我們把它抽取成一個(gè)組合式函數(shù):

// fetch.js
import { ref } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)

  fetch(url)
    .then((res) => res.json())
    .then((json) => (data.value = json))
    .catch((err) => (error.value = err))

  return { data, error }
}

現(xiàn)在我們?cè)诮M件里只需要:

<script setup>
import { useFetch } from './fetch.js'

const { data, error } = useFetch('...')
</script>

接收響應(yīng)式狀態(tài)?

useFetch() 接收一個(gè)靜態(tài) URL 字符串作為輸入——因此它只會(huì)執(zhí)行一次 fetch 并且就此結(jié)束。如果我們想要在 URL 改變時(shí)重新 fetch 呢?為了實(shí)現(xiàn)這一點(diǎn),我們需要將響應(yīng)式狀態(tài)傳入組合式函數(shù),并讓它基于傳入的狀態(tài)來(lái)創(chuàng)建執(zhí)行操作的偵聽器。

舉例來(lái)說(shuō),useFetch() 應(yīng)該能夠接收一個(gè) ref:

const url = ref('/initial-url')

const { data, error } = useFetch(url)

// 這將會(huì)重新觸發(fā) fetch
url.value = '/new-url'

或者接收一個(gè) getter 函數(shù):

// 當(dāng) props.id 改變時(shí)重新 fetch
const { data, error } = useFetch(() => `/posts/${props.id}`)

我們可以用 watchEffect() 和 toValue() API 來(lái)重構(gòu)我們現(xiàn)有的實(shí)現(xiàn):

// fetch.js
import { ref, watchEffect, toValue } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)

  watchEffect(() => {
    // 在 fetch 之前重置狀態(tài)
    data.value = null
    error.value = null
    // toValue() 將可能的 ref 或 getter 解包
    fetch(toValue(url))
      .then((res) => res.json())
      .then((json) => (data.value = json))
      .catch((err) => (error.value = err))
  })

  return { data, error }
}

toValue() 是一個(gè)在 3.3 版本中新增的 API。它的設(shè)計(jì)目的是將 ref 或 getter 規(guī)范化為值。如果參數(shù)是 ref,它會(huì)返回 ref 的值;如果參數(shù)是函數(shù),它會(huì)調(diào)用函數(shù)并返回其返回值。否則,它會(huì)原樣返回參數(shù)。它的工作方式類似于 unref(),但對(duì)函數(shù)有特殊處理。

注意 toValue(url) 是在 watchEffect 回調(diào)函數(shù)的內(nèi)部調(diào)用的。這確保了在 toValue() 規(guī)范化期間訪問的任何響應(yīng)式依賴項(xiàng)都會(huì)被偵聽器跟蹤。

這個(gè)版本的 useFetch() 現(xiàn)在能接收靜態(tài) URL 字符串、ref 和 getter,使其更加靈活。watch effect 會(huì)立即運(yùn)行,并且會(huì)跟蹤 toValue(url) 期間訪問的任何依賴項(xiàng)。如果沒有跟蹤到依賴項(xiàng)(例如 url 已經(jīng)是字符串),則 effect 只會(huì)運(yùn)行一次;否則,它將在跟蹤到的任何依賴項(xiàng)更改時(shí)重新運(yùn)行。

這是更新后的 useFetch(),為了便于演示,添加了人為延遲和隨機(jī)錯(cuò)誤。

想要了解更多關(guān)于Vue3 組合式函數(shù)的用法, 請(qǐng)點(diǎn)擊 《Vue3 組合式函數(shù)》。


0 人點(diǎn)贊