iT邦幫忙

2023 iThome 鐵人賽

DAY 18
0
Vue.js

Vue & GraphQL 探險之旅:30天,從新手村到魔王之巔系列 第 18

[Day18] 魔法重構:使用 Vue 3 Composition API 與 Composable 實現模組化程式碼管理

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20231017/20141111BQK8Tq38Ea.png

DRY 原則:Don't Repeat Yourself

該原則的基本思想是,任何形式的重複程式碼都應該被消除,因為它們增加了維護的複雜性和風險。
– From 《Clean Code》

Day16 的文章中,我們觀察到在 Header 與 Sidebar 中存在了重複的搜尋功能邏輯。

其實我們在日常的開發過程中,經常會遇到重複或相似的功能邏輯。這不僅會增加維護的複雜度,還可能造成潛在的問題。

Vue 3 透過其強大的 Composition API 和 Composables,為我們提供了一個簡潔而強大的解決方案。

進一步,我們還可以整合 Apollo Client 與 Vue 3 的 Composition API,讓 GraphQL API 的結構更加清晰且方便管理。


文章範例程式碼 GitHub

範例程式碼 GitHub 連結

Day18 開始前分支:feat/day_16/implement_search_function
Day18 進度完成分支:refactor/day_18/extract_search_composable


避免程式碼的重複與冗餘

消除重複是 Clean Code 的核心原則之一,藉由減少不必要的程式碼重複,從而提升程式碼的易維護性和可讀性。

首先,我們應該敏銳地察覺到程式碼的壞味道 (Code Smell),並且及時思考這是否有潛在的問題。我們應該盡量選擇最佳的寫法,以減少未來的維護成本。

Code Smell: Duplication

Code Smell: 程式碼中的壞味道,指向可能存在的潛在風險和應該改善之處。這些問題不一定會直接引發錯誤,但可能反映出設計上的不良之處,而這往往導致程式碼變得難以維護或拓展。

Duplication 被認為是一種 Code Smell。

簡單來說,如果在多個地方看到相同或非常相似的程式碼片段,這就是一個警告信號。

重複的程式碼意味著未來當開發者需要修改這部分邏輯時,可能需要在多處進行相同的更改,這增加了出錯的機會並降低了維護效率。因此,避免重複是提高程式碼品質的一個關鍵策略。

所有的重複都是魔鬼嗎?

然而,當我們談論「重複」時,不僅僅是指完全相同的程式碼片段。

很多時候,程式碼雖然在表面上看起來是相似的,但它們所承載的業務邏輯和意義可能有很大的區別。

在這種情況下,正確地辨識這些相似性是非常關鍵的。過度的抽象化可能會讓程式碼變得難以理解和維護。有時候,容許一定程度的重複可以提高程式碼的可讀性,且為未來可能有所變化的業務邏輯提供更大的彈性。

我們的目標應該是寫出既不重複又易於維護的程式碼,而不僅僅是追求抽象的完美。

換句話說,DRY 原則並不是盲目消除所有的重複,而是一個思考如何更有效地組織和寫出高品質程式碼的方法。

探討功能邏輯的重複性

在我們試圖將重複的部分獨立出來之前,必須進行深入的思考,包括但不限於:

  1. 重構優勢:重構能帶來哪些益處?這是否值得我們所投入的成本?
  2. 復原難易度:獨立出來後,如果遇到需求變更需要還原時,會不會很麻煩?
  3. 業務一致性:這些看似重複的部分,在業務邏輯上真的完全一致嗎?
  4. 可讀性影響:抽取後,程式碼的可讀性是提高還是下降?

Vue 3 Composition API

Vue Composition API 是 Vue 3 提供的一套新功能,使開發者能夠更有組織地組合和重用功能邏輯。

跟 Option API 相比,Composition API 提供了一種方式,讓開發者可以在不依賴 Vue 實例選項(如 data、methods)的情況下,寫出組合性的程式碼。

Composition API 的核心優點

  1. 模組化與重用: 允許開發者更容易地重用和組合功能邏輯,使得自定義的功能可以在多個組件之間共享。

  2. 清晰的組件結構: 讓相關的邏輯保持在一起,而不是基於選項 (如 data、methods、watch) 進行分散,使得程式碼更為組織化和可讀。

  3. 強化響應式: 使用 refreactive 強化了 Vue 的響應式能力,提供了更直觀的狀態管理和效能優化。

  4. 易於測試: 模組化的邏輯,讓測試特定的功能邏輯變得更加容易和直觀。

  5. 更好的 TypeScript 支援: 提供更好的 TypeScript 整合,使得開發者可以在編碼過程中得到更強大的類型推斷和檢查。

Composable 的使用場景

Composable 是 Vue 3 Composition API 的一部分,允許開發者封裝和重用邏輯功能。

透過 Composable,相關的功能和狀態可以被組織成獨立的模組,提供清晰且模組化的程式結構。此外,它還強化了資料的響應式能力,確保資料變動時的即時更新。

使用場景

  1. 重複的功能邏輯: 當多個組件有共享的邏輯時,例如資料提取、表單驗證等,可以將其放入 Composable。

  2. 非同步操作管理: 例如對 API 的呼叫,可以將相關的載入狀態、錯誤處理等封裝到 Composable。

  3. 複雜的狀態管理: 當組件內部的狀態變得複雜且需要一些相關的操作時,可以考慮使用 Composable 來進行組織。

  4. 整合第三方函式庫:若你要把 Vue 和其他第三方函式庫(像是 D3、Three.js)結合起來,可以利用 Composable 來處理這些交互的邏輯。


實戰:模組化程式碼重構

完整程式碼範例請參考:refactor/day_18/extract_search_composable

識別重複的功能邏輯

Header.vueSidebar.vue 都有同樣的程式碼片段:

<script setup lang="ts">
// ...

const router = useRouter()
const searchTerm = ref('')

function handleSearch() {
  if (searchTerm.value)
    router.push({ name: 'search-results', query: { keyword: searchTerm.value } })
}
</script>

並且這個業務邏輯同樣都是文章搜尋的功能,這的確是一個 Duplication 的 Code Smell

使用 Composable 進行模組化重構

讓我們將共用的邏輯移動到 Composable useSearch 內:
src/composables/useSearch.ts

import type { Ref } from 'vue'
import { ref } from 'vue'
import { useRouter } from 'vue-router'

export default function useSearch(): { searchTerm: Ref<string>; handleSearch: () => Promise<void> } {
  const router = useRouter()
  const searchTerm = ref('')

  async function handleSearch() {
    if (searchTerm.value) {
      try {
        await router.push({ name: 'search-results', query: { keyword: searchTerm.value } })
      }
      catch (error) {
        console.error('Error navigating to search results:', error)
      }
    }
  }

  return {
    searchTerm,
    handleSearch,
  }
}

handleSearch 改為非同步函式
值得注意的是,我們將 handleSearch 改為非同步函式,並在其內部使用 try/catch 來捕獲任何可能的錯誤。

這是因為在 Vue Router 4 中,router.push() 返回一個 Promise,而我們應該要妥善處理這個 Promise 的返回結果。

為什麼在 SFC 中可以不用處理呢?
在 Vue 3 以及 Vue Router 4 的 script setup 環境中,Vue 的錯誤處理機制會自動捕獲模板或 setup 函式的非同步錯誤。

然而,當我們將這段程式碼移至 composable 後,Vue 的錯誤處理機制不一定能自動捕獲使用時的非同步錯誤。

為了避免這種情況,最佳做法是始終處理 router.push() 返回的 Promise,無論它是在 script setup 中還是在 composable 中。

在 Component 中使用 Composable

現在我們可以在 Header.vueSidebar.vue 中使用此 useSearch

<script setup lang="ts">
import { useSearch } from '@/composables/useSearch'

const { searchTerm, handleSearch } = useSearch()
</script>

如此一來,就輕鬆完成了模組化重構!

這樣做的好處是,如果搜尋功能邏輯在未來需要修改,我們只需要修改 useSearch.ts。此外,重構後也明顯提升了 Component 程式碼的易維護性和可讀性。


Vue Apollo Composition API

我們在開發中常常用到的 Vue Apollo 的 useQueryuseMutation,這兩者其實也是 composable 函式。它們是被設計在 Vue 3 的 Composition API 環境中使用,且可以與其他 composable 函式組合,創建更複雜的功能和邏輯。

模組化和重用 GraphQL 查詢

我們可以通過將 GraphQL 查詢邏輯包裝在 composable 函式中,在不同組件中重用它,確保程式碼的 DRY (Don't Repeat Yourself) 和可維護性。

假設,我們有一個 取得使用者資料 的 Query

const { result, loading, error, refetch } = useQuery(gqlGetUserData);

以及一個 更新使用者資料 的 Mutation

const { mutate, loading, error } = useMutation(gqlUpdateUserData);

那麼我們可以組合 useQueryuseMutation 來實現 useUser composable 函式:

function useUser() {
    const { result: userData, refetch } = useQuery(gqlGetUserData);
    const { mutate: updateUser } = useMutation(gqlUpdateUserData);

    return {
        userData,
        refetchUserData: refetch,
        updateUser
    };
}

如此一來,就能輕鬆在 Component 中引入封裝好的 GraphQL API 邏輯,甚至我們可以把獨有的錯誤處理邏輯也一併封裝起來。


Recap

今日,我們探討了 DRY 原則與如何重構程式碼的重複與冗餘。我們必須要能正確地識別重複的程式碼片段,消除程式碼中的壞味道,重構寫出既不重複又易於維護的程式碼,而不僅僅是追求抽象的完美。

接著我們演練了如何透過 Vue 3 強大的 Composition API 和 Composables 模組化重構程式碼,透過封裝和重用邏輯,提升了程式碼的易維護性與可讀性。

最後進一步探討如何模組化和重用 GraphQL 查詢,暸解了 composable 允許我們在 Vue 3 的新環境中,更加模組化和靈活地組織 GraphQL 相關的邏輯和功能。

明天,我們將深入瞭解 CRUD 中的 Create 與 Delete,透過 GraphQL Mutation 來實際操作文章的新增與刪除,讓部落格應用有更豐富的互動性!


上一篇
[Day17] 星塵護盾:防禦 XSS 的第一道防線 – Vue 的文本轉義機制
下一篇
[Day19] 實戰演練:資料的生與死,GraphQL Mutation 實現文章的新增與刪除
系列文
Vue & GraphQL 探險之旅:30天,從新手村到魔王之巔31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言