Vue3組合式函式最佳實踐(一)

前端榮耀 發表於 2022-06-21
Vue

截至目前,組合式函式應該是在VUE 3應用程式中組織業務邏輯最佳的方法。
它讓我們可以把一些小塊的通用邏輯進行抽離、複用,使我們的程式碼更易於編寫、閱讀和維護。
由於這種編寫VUE程式碼的方式相對較新,因此您可能想知道編寫組合式函式的最佳實踐是什麼呢?本系列教程可以作為您和您的團隊在進行組合式開發過程中的參考指南。
我們將涵蓋以下內容:

  • 1.如何通過選項物件引數使您的組合更加可配置; 👈 本篇主題
  • 2.使用ref和unref使我們的引數更加靈活;
  • 3.如何使你的返回值更有用;
  • 4.為什麼從介面定義開始可以使你的組合式函式更強大;
  • 5.如何使用非同步程式碼而無需“等待” - 使您的程式碼更易於理解;

不過,首先,我們需要確保我們對組合式函式的理解是一致的。我們先花點時間解釋一下什麼是組合式函式。

什麼是“組合式函式”?

根據官方文件說明,在 Vue 應用的概念中,“組合式函式”是一個利用 Vue 組合式 API 來封裝和複用有狀態邏輯的函式。
這就意味著,任何有狀態邏輯,並且使用了響應式處理的邏輯都可以轉換成組合式函式。這和我們平時抽離封裝的公共方法還是有一些區別的。我們封裝的公共方法往往是無狀態的:它在接收一些輸入後立刻返回所期望的輸出。而組合式函式往往是和狀態邏輯關聯的。

讓我們看看官方給出的useMouse這個組合式函式:

import { ref, onMounted, onUnmounted } from 'vue'

// 按照慣例,組合式函式名以“use”開頭
export function useMouse() {
  // 被組合式函式封裝和管理的狀態
  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))

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

我們把狀態定義為refs,當滑鼠移動的時候我們更新這個狀態。通過返回xy,我們可以在任何元件中使用它們,甚至我們還可以把多個組合式函式巢狀使用。

當我們在元件中使用時

<template>
  X: {{ x }} Y: {{ y }}
</template>

<script setup>
  import { useMouse } from './useMouse';
  const { x, y } = useMouse();
</script>

如您所見,通過使用useMouse我們可以輕鬆的複用這個邏輯。僅僅很少的程式碼,我們就可以在元件中獲取滑鼠座標狀態。

現在我們對組合式函式有了相同的認識,讓我們看一下可以幫助我們編寫更好的組合式函式的第一個方法吧。

選項物件引數

大部分組合式函式都會有一個或兩個必須的引數,然後有一系列可選的引數來幫助進行一些額外的配置。在配置組合式函式時,我們可以將一系列的可選配置放到一個選項物件引數中,而不是一長串引數的形式。

// 使用選項物件引數形式
const title = useTitle('A new title', { titleTemplate: '>> %s <<' });
// Title is now ">> A new title <<"

// 使用多引數形式
const title = useTitle('A new title', '>> %s <<');
// Title is now ">> A new title <<"

選項物件引數的形式可以給我們帶來一些便利:
首先,我們不必記住引數的正確順序,特別是引數很多的時候。雖然現在我們可以通過Typescript和編輯器提示功能來避免這類問題,但是通過這種方式仍然是有差別的。使用Javascript物件,引數的順序就不那麼重要了。(當然,這也要求我們的函式定義需要清晰明瞭,我們後面會談)
其次,程式碼更可讀取,因為我們知道該選項的作用是什麼。
第三,程式碼擴充套件性更好,以後新增新的選項要容易得多。這既適用於為組合式函式本身新增新選項,又適用於巢狀使用時引數傳遞。

因此,使用物件引數更加友好,但是我們該如何來實現呢?

在組合式函式中的實現

現在讓我們看下如何在組合式函式中使用選項物件引數。我們來給上面的useMouse進行一些擴充套件:

export function useMouse(options) {
  const {
    asArray = false,
    throttle = false,
  } = options;

  // ...
};

useMouse本身沒有必傳引數,所以我們直接給它增加一個options引數來進行一些額外的配置。通過解構,我們可以訪問所有的選傳引數,並且為每個引數設定了預設值,這就避免了有些不需要額外配置的呼叫時沒有傳入可選引數的情況。

現在讓我們來看兩個VueUse上面的組合式函式是如何使用這個模式的。VueUse是一個服務於Vue3的組合式函式的常用工具集,它的初衷就是將一切原本並不支援響應式的JS API變得支援響應式,省去程式設計師自己寫相關程式碼。

我們先來看useTitle,然後再看一下useRefHistory是如何實現的。

舉例-useTitle

useTitle的作用非常簡單,就是用來更新頁面的標題。

const title = useTitle('Initial Page Title');
// Title: "Initial Page Title"

title.value = 'New Page Title';
// Title: "New Page Title"

它也有幾個選擇引數,來促進額外的靈活性。我們可以傳入titleTemplate作為模版,並且通過observe來將其設定稱為具備觀察性(內部通過MutationObserver實現):

const title = useTitle('Initial Page Title', {
  titleTemplate: '>> %s <<',
  observe: true,
});
// Title: ">> Initial Page Title <<"

title.value = 'New Page Title';
// Title: ">> New Page Title <<"

當我們檢視它的原始碼的時候可以看到以下處理

export function useTitle(newTitle, options) {
  const {
    document = defaultDocument,
    observe = false,
    titleTemplate = '%s',
  } = options;
  
  // ...
}

useTitle包含一個必傳的引數,以及一個可選引數物件。正如我們上面描述的那樣,它完全是按照這個模式來實現的。
接下來,讓我們看一下一個更復雜的組合式函式是如何使用選項物件模式的。

舉例-useRefHistory

useRefHistory可以幫助我們追蹤一個響應式變數的所有更改,可以讓我們輕鬆的執行撤銷和恢復的操作。

// Set up the count ref and track it
const count = ref(0);
const { undo } = useRefHistory(count);

// Increment the count
count.value++;

// Log out the count, undo, and log again
console.log(counter.value); // 1
undo();
console.log(counter.value); // 0

它支援設定許多不同的配置選擇

{
  deep: false,
  flush: 'pre',
  capacity: -1,
  clone: false,
  // ...
}

如果想知道這些選項引數的完整列表和對應的功能,可以去檢視相關文件,在此不再贅述。

我們可以將選項物件作為第二個引數傳遞,以進一步配置該組合函式的行為,與我們上一個示例相同:

export function useRefHistory(source, options) {
  const {
    deep = false,
    flush = 'pre',
    eventFilter,
  } = options;
 
  // ...
}

我們可以看到它內部僅僅解構出了一部分引數值,這是因為useRefHistory內部依賴了useManualHistory這個組合式函式,其他的選項引數將在後面透傳給useManualHistory時進行展開合併。

// ...

const manualHistory = useManualRefHistory(
  source,
  {
    // Pass along the options object to another composable
    ...options,
    clone: options.clone || deep,
    setSource,
  },
);

// ...

這也和我們前面說到的內容相符:組合式函式可以巢狀使用。

小結

本文是“組合式函式最佳實踐”的第一部分。我們研究瞭如何通過選項物件引數提升組合式函式的靈活性。無須擔心引數順序不對導致的問題,並且可以靈活進行配置項的增加擴充套件。我們不僅僅研究了這個模式本身,我們還通過VueUse中的useTitleuseRefHistory來學習瞭如何實現此模式。它們略有不同,但是這個模式本身就是很簡單的,我們通過它能做到也是有限的。

下一篇我們將介紹如何通過ref和unref使我們的引數更加靈活

// Works if we give it a ref we already have
const countRef = ref(2);
useCount(countRef);

// Also works if we give it just a number
const countRef = useRef(2);

這增加了靈活性,使我們能夠在應用程式中的更多情況下使用我們的組合。