本文將分為三個部分:

  1. 通用設計模式
  2. 我的建議
  3. 進一步閱讀

享受並且讓我知道您在專案中使用的模式和實踐🚀

原文出處:https://dev.to/jacobandrewsky/good-practices-and-design-patterns-for-vue-composables-24lk

通用設計模式

我認為了解建置可組合項模式的最佳來源實際上是 Vue.js 文件,您可以在此處查看

基本可組合項

Vue 文件顯示了以下 useMouse 可組合項的示例:

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

// by convention, composable function names start with "use"
export function useMouse() {
  // state encapsulated and managed by the composable
  const x = ref(0)
  const y = ref(0)

  // a composable can update its managed state over time.
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  // a composable can also hook into its owner component's
  // lifecycle to setup and teardown side effects.
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  // expose managed state as return value
  return { x, y }
}

稍後可以在元件中使用它,如下所示:

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

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

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

異步可組合項

為了獲取資料,Vue 建議使用以下可組合結構:

import { ref, watchEffect, toValue } from 'vue'

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

  watchEffect(() => {
    // reset state before fetching..
    data.value = null
    error.value = null
    // toValue() unwraps potential refs or getters
    fetch(toValue(url))
      .then((res) => res.json())
      .then((json) => (data.value = json))
      .catch((err) => (error.value = err))
  })

  return { data, error }
}

然後可以在元件中使用它,如下所示:

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

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

可組合合約

根據上面的示例,以下是所有可組合項都應遵循的約定:

  1. 可組合文件名應以 use 開頭,例如 useSomeAmazingFeature.ts
  2. 它可以接受輸入參數,這些參數可以是字串等基本類型,也可以接受 refs 和 getter,但需要使用 toValue 幫助器
  3. Composable 應該返回一個 ref 值,該值可以在解構可組合項後存取,例如 const { x, y } = useMouse()
  4. 可組合項可以保存可以在整個應用程式中存取和修改的全局狀態。
  5. 可組合性可能會產生副作用,例如加入窗口事件偵聽器,但在卸載元件時應清除它們。
  6. 可組合項只能在 <script setup>setup() 掛鉤中呼叫。它們也應該在這些上下文中同步呼叫。在某些情況下,您還可以在生命週期掛鉤中呼叫它們,例如“onMounted()”。
  7. 可組合項可以呼叫內部的其他可組合項。
  8. 可組合項應在內部包裝某些邏輯,當過於復雜時,應將它們提取到單獨的可組合項中以便於測試。

我的建議

我已經為我的工作專案和開源專案建置了多個可組合項 - NuxtAlgolia、NuxtCloudinary、NuxtMedusa,因此基於這些,我想根據我的經驗在上面的合同中加入一些要點。

有狀態或/和純函數可組合項

在程式碼標準化的某個時刻,您可能會得出這樣的結論:您希望對可組合項中的狀態保留做出決定。

最容易測試的函數是那些不存儲任何狀態的函數(即它們是簡單的輸入/輸出函數),例如負責將字節轉換為人類可讀值的可組合函數。它接受一個值並返回一個不同的值 - 它不存儲任何狀態。

不要誤會我的意思,你不必做出“或”的決定。您可以完全保留有狀態和無狀態可組合項。但這應該是一個書面決定,以便以後更容易與他們合作 🙂

可組合項的單元測試

我們希望使用 Vitest 為我們的前端應用程式實施單元測試。在後端工作時,進行單元測試程式碼覆蓋率非常有用,因為您主要關注邏輯。然而,在前端,您通常使用視覺效果。

因此,我們認為對整個元件進行單元測試可能不是最好的主意,因為我們基本上將對框架本身進行單元測試(如果按下按鈕,檢查狀態是否更改或模式是否打開)。

由於我們已將所有業務邏輯移至可組合項(基本上是 TypeScript 函數)內,因此它們很容易使用 Vitest 進行測試,並且允許我們擁有更穩定的系統。

可組合項的範圍

不久前,在 VueStorefront 中,我們開發了自己的可組合方法(早在它們實際上像這樣被呼叫之前)。在我們的方法中,我們使用可組合項來映射電子商務的業務領域,如下所示:

const { cart, load, addItem, removeItem, remove, ... } = useCart() 

這種方法絕對有用,因為它允許將域包裝在一個函數中。在“useProduct”或“useCategory”等更簡單的示例中,實現和維護相對簡單。然而,正如您在此處的“useCart”示例中看到的那樣,當包裝一個包含更多邏輯而不僅僅是資料獲取的域時,這個可組合項正在發展成為一種非常難以開發和維護的形狀。

此時,我開始為 Nuxt 生態系統做出貢獻,其中引入了不同的方法。在這種新方法中,每個可組合項僅負責一件事。因此,我們的想法不是建置一個巨大的“useCart”可組合項,而是為每個功能建置可組合項,即“useAddToCart”、“useFetchCart”、“useRemovefromCart”等。

因此,維護和測試這些可組合項應該更容易 🙂

進一步閱讀

這將是我的研究的全部內容。如果您想了解有關此主題的更多訊息,請務必查看以下文章:

  • https://vuejs.org/guide/reusability/composables.html
  • https://www.youtube.com/watch?v=bcZM3EogPJE
  • https://vueschool.io/articles/vuejs-tutorials/what-is-a-vue-js-composable/
  • https://blog.logrocket.com/getting-started-vue-composables/
  • https://macopedia.com/blog/news/how-can-vue-3-composables-make-your-life-easier

共有 0 則留言