App下載

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

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

微信截圖_20230927172059

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

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

  1. 更好的代碼組織

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

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

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

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

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

  1. 更好的邏輯抽象

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

  1. 更好的面向切面編程

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

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

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

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

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

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

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

鼠標跟蹤器示例?

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

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

但是,如果我們想在多個組件中復(fù)用這個相同的邏輯呢?我們可以把這個邏輯以一個組合式函數(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ù)可以隨時更改其狀態(tài)。
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  // 一個組合式函數(shù)也可以掛靠在所屬組件的生命周期上
  // 來啟動和卸載副作用
  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>

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

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

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

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

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

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

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

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

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

useMouse() 組合式函數(shù)沒有接收任何參數(shù),因此讓我們再來看一個需要接收一個參數(shù)的組合式函數(shù)示例。在做異步數(shù)據(jù)請求時,我們常常需要處理不同的狀態(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>

如果在每個需要獲取數(shù)據(jù)的組件中都要重復(fù)這種模式,那就太繁瑣了。讓我們把它抽取成一個組合式函數(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)在我們在組件里只需要:

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

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

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

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

舉例來說,useFetch() 應(yīng)該能夠接收一個 ref:

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

const { data, error } = useFetch(url)

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

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

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

我們可以用 watchEffect() 和 toValue() API 來重構(gòu)我們現(xiàn)有的實現(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() 是一個在 3.3 版本中新增的 API。它的設(shè)計目的是將 ref 或 getter 規(guī)范化為值。如果參數(shù)是 ref,它會返回 ref 的值;如果參數(shù)是函數(shù),它會調(diào)用函數(shù)并返回其返回值。否則,它會原樣返回參數(shù)。它的工作方式類似于 unref(),但對函數(shù)有特殊處理。

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

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

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

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


0 人點贊