Vue Hook Store 設計模式最佳實踐指南

Vue Hook Store 設計模式最佳實踐指南

一、引言

在 Vue 3 組合式 API 與 TypeScript 普及的背景下,Hook Store 設計模式應運而生,它結合了 Vue 組合式 API 的靈活性與狀態管理的最佳實踐,為開發者提供了一種輕量級、可測試且易于維護的狀態管理方案。本文將深入探討 Vue Hook Store 的設計理念、核心模式與實戰技巧,幫助開發者構建高質量的 Vue 應用。

二、Hook Store 設計模式核心概念

2.1 定義與核心優勢

Hook Store 是一種基于 Vue 組合式 API 的狀態管理模式,它將狀態、邏輯與副作用封裝在可復用的 hook 中,具有以下優勢:

  • 輕量級:無需額外依賴,僅使用 Vue 內置 API
  • 高內聚:狀態與邏輯緊密關聯,提高代碼可維護性
  • 可測試性:純函數式設計,易于編寫單元測試
  • 靈活組合:通過 hook 組合實現復雜狀態管理

2.2 與傳統狀態管理方案對比

特性Hook StoreVuex/Pinia
學習曲線中高
代碼復雜度中高
類型推導優秀良好
可測試性優秀良好
適用場景中小型項目 / 模塊大型項目

三、Hook Store 基礎架構

3.1 基本結構

一個典型的 Hook Store 包含以下部分:

// useCounter.ts
import { ref, computed, watch, type Ref } from 'vue'export interface CounterState {count: numbertitle: string
}export const useCounter = (initialState: CounterState = { count: 0, title: 'Counter' }) => {// 狀態管理const state = ref(initialState) as Ref<CounterState>// 計算屬性const doubleCount = computed(() => state.value.count * 2)// 方法const increment = () => {state.value.count++}const decrement = () => {state.value.count--}// 副作用watch(() => state.value.count, (newCount) => {console.log(`Count changed to: ${newCount}`)})// 導出狀態與方法return {state,doubleCount,increment,decrement}
}

3.2 在組件中使用

<template><div><h1>{{ counterState.title }}</h1><p>Count: {{ counterState.count }}</p><p>Double Count: {{ doubleCount }}</p><button @click="increment">+</button><button @click="decrement">-</button></div>
</template><script setup>
import { useCounter } from './useCounter'const { state: counterState, doubleCount, increment, decrement } = useCounter()
</script>

四、Hook Store 高級模式

4.1 模塊化設計

將不同業務領域的狀態拆分為獨立的 hook store:

src/stores/auth/useAuth.ts       # 認證狀態useUserProfile.ts # 用戶資料products/useProducts.ts   # 產品列表useCart.ts       # 購物車utils/useLocalStorage.ts # 本地存儲工具

4.2 狀態持久化

通過自定義 hook 實現狀態持久化:

// utils/useLocalStorage.ts
import { ref, watch, type Ref } from 'vue'export const useLocalStorage = <T>(key: string, initialValue: T): Ref<T> => {const getSavedValue = () => {try {const saved = localStorage.getItem(key)return saved ? JSON.parse(saved) : initialValue} catch (error) {console.error(error)return initialValue}}const state = ref(getSavedValue()) as Ref<T>watch(state, (newValue) => {localStorage.setItem(key, JSON.stringify(newValue))}, { deep: true })return state
}

4.3 異步操作處理

在 hook store 中處理 API 請求:

// stores/products/useProducts.ts
import { ref, computed, type Ref } from 'vue'
import { fetchProducts } from '@/api/products'export interface Product {id: numbername: stringprice: number
}export interface ProductsState {items: Product[]loading: booleanerror: string | null
}export const useProducts = () => {const state = ref<ProductsState>({items: [],loading: false,error: null}) as Ref<ProductsState>const getProducts = async () => {state.value.loading = truestate.value.error = nulltry {const response = await fetchProducts()state.value.items = response.data} catch (error: any) {state.value.error = error.message} finally {state.value.loading = false}}const addProduct = (product: Product) => {state.value.items.push(product)}return {state,getProducts,addProduct}
}

4.4 狀態共享與全局狀態

使用 provide/inject 實現跨組件狀態共享:

// stores/useGlobalState.ts
import { provide, inject, ref, type Ref } from 'vue'const GLOBAL_STATE_KEY = Symbol('globalState')interface GlobalState {theme: 'light' | 'dark'isSidebarOpen: boolean
}export const useProvideGlobalState = () => {const state = ref<GlobalState>({theme: 'light',isSidebarOpen: true}) as Ref<GlobalState>const toggleTheme = () => {state.value.theme = state.value.theme === 'light' ? 'dark' : 'light'}const toggleSidebar = () => {state.value.isSidebarOpen = !state.value.isSidebarOpen}provide(GLOBAL_STATE_KEY, {state,toggleTheme,toggleSidebar})return {state,toggleTheme,toggleSidebar}
}export const useGlobalState = () => {return inject(GLOBAL_STATE_KEY)!
}

在根組件中提供全局狀態:

<!-- App.vue -->
<script setup>
import { useProvideGlobalState } from './stores/useGlobalState'useProvideGlobalState()
</script>

在子組件中使用:

<!-- ChildComponent.vue -->
<script setup>
import { useGlobalState } from './stores/useGlobalState'const { state, toggleTheme } = useGlobalState()
</script>

五、Hook Store 最佳實踐

5.1 設計原則

  1. 單一職責:每個 hook store 只負責一個明確的業務領域
  2. 最小暴露:只暴露必要的狀態和方法
  3. 組合優先:通過組合多個 hook store 實現復雜功能
  4. 類型安全:充分利用 TypeScript 提供類型保障

5.2 測試策略

使用 vitest 和 @vue/test-utils 編寫單元測試:

// __tests__/useCounter.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { useCounter } from '../useCounter'describe('useCounter', () => {it('should initialize with default values', () => {const { state } = useCounter()expect(state.value.count).toBe(0)expect(state.value.title).toBe('Counter')})it('should increment count', () => {const { state, increment } = useCounter()increment()expect(state.value.count).toBe(1)})it('should decrement count', () => {const { state, decrement } = useCounter()decrement()expect(state.value.count).toBe(-1)})it('should compute double count', () => {const { state, doubleCount } = useCounter()state.value.count = 5expect(doubleCount.value).toBe(10)})it('should log count changes', () => {const consoleLogSpy = vi.spyOn(console, 'log')const { state } = useCounter()state.value.count = 10expect(consoleLogSpy).toHaveBeenCalledWith('Count changed to: 10')consoleLogSpy.mockRestore()})
})

5.3 性能優化

  1. 使用 shallowRef 代替 ref 存儲大型對象,避免深層響應式開銷
  2. 使用 readonly 包裝狀態,防止意外修改
  3. 在大型列表場景中使用 reactive 而非 ref 包裹數組
  4. 使用 computed 緩存復雜計算結果
import { shallowRef, readonly, computed } from 'vue'export const useLargeDataStore = () => {// 使用shallowRef存儲大型數據const largeList = shallowRef<Item[]>([]) as Ref<Item[]>// 使用readonly防止外部修改const readonlyList = readonly(largeList)// 使用computed緩存計算結果const filteredList = computed(() => largeList.value.filter(item => item.active))return {readonlyList,filteredList}
}

六、應用案例:完整的 Todo 應用

6.1 項目結構

src/stores/todos/useTodos.ts        # Todo列表管理useFilter.ts       # 過濾狀態useLocalStorage.ts # 本地存儲components/TodoList.vueTodoItem.vueTodoFilter.vueApp.vue

6.2 Todo Store 實現

// stores/todos/useTodos.ts
import { ref, computed, type Ref } from 'vue'
import { useLocalStorage } from './useLocalStorage'export interface Todo {id: numbertext: stringcompleted: boolean
}export const useTodos = () => {// 使用localStorage持久化存儲const todos = useLocalStorage<Todo[]>('todos', [])const addTodo = (text: string) => {const newTodo: Todo = {id: Date.now(),text,completed: false}todos.value.push(newTodo)}const toggleTodo = (id: number) => {const todo = todos.value.find(t => t.id === id)if (todo) {todo.completed = !todo.completed}}const deleteTodo = (id: number) => {todos.value = todos.value.filter(t => t.id !== id)}const clearCompleted = () => {todos.value = todos.value.filter(t => !t.completed)}return {todos,addTodo,toggleTodo,deleteTodo,clearCompleted}
}

6.3 過濾狀態管理

// stores/todos/useFilter.ts
import { ref, computed, type Ref } from 'vue'export type Filter = 'all' | 'active' | 'completed'export const useFilter = () => {const currentFilter = ref<Filter>('all') as Ref<Filter>const setFilter = (filter: Filter) => {currentFilter.value = filter}return {currentFilter,setFilter}
}

6.4 組合使用

<!-- TodoList.vue -->
<template><div><input v-model="newTodoText" @keyup.enter="addTodo" placeholder="Add todo" /><button @click="addTodo">Add</button><div><FilterButton :filter="'all'" /><FilterButton :filter="'active'" /><FilterButton :filter="'completed'" /></div><ul><TodoItem v-for="todo in filteredTodos" :key="todo.id" :todo="todo" /></ul><button @click="clearCompleted">Clear Completed</button></div>
</template><script setup>
import { ref, computed } from 'vue'
import { useTodos } from '@/stores/todos/useTodos'
import { useFilter } from '@/stores/todos/useFilter'
import TodoItem from './TodoItem.vue'
import FilterButton from './FilterButton.vue'const { todos, addTodo, clearCompleted } = useTodos()
const { currentFilter } = useFilter()
const newTodoText = ref('')const filteredTodos = computed(() => {switch (currentFilter.value) {case 'active':return todos.value.filter(todo => !todo.completed)case 'completed':return todos.value.filter(todo => todo.completed)default:return todos.value}
})
</script>

七、總結與最佳實踐建議

7.1 適用場景

  • 中小型項目或模塊
  • 需要靈活狀態管理的場景
  • 追求最小化依賴的項目
  • 對 TypeScript 支持有高要求的項目

7.2 與其他狀態管理方案的配合

  • 與 Pinia/Vuex 結合:在大型應用中,核心全局狀態使用 Pinia/Vuex,局部狀態使用 Hook Store
  • 與 Vue Router 結合:在路由守衛中使用 Hook Store 管理導航狀態
  • 與 API 請求庫結合:如 axios、fetch,在 Hook Store 中封裝 API 請求邏輯

7.3 未來趨勢

隨著 Vue 3 組合式 API 的普及,Hook Store 設計模式將越來越受歡迎,未來可能會出現更多基于此模式的工具和最佳實踐,進一步提升 Vue 應用的開發體驗和代碼質量。

通過合理應用 Hook Store 設計模式,開發者可以構建更加模塊化、可測試和可維護的 Vue 應用,同時充分發揮 Vue 3 組合式 API 的強大功能。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/85013.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/85013.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/85013.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

無人機多人協同控制技術解析

一、運行方式 無人機多人點對點控制通常采用以下兩種模式&#xff1a; 1. 主從控制模式 指定一個主控用戶擁有最高優先級&#xff0c;負責飛行路徑規劃、緊急操作等關鍵指令&#xff1b;其他用戶作為觀察者&#xff0c;僅能查看實時畫面或提交輔助指令&#xff0c;需經主…

樹型表查詢方法 —— SQL遞歸

目錄 引言&#xff1a; 自鏈接查詢&#xff1a; 遞歸查詢&#xff1a; 編寫service接口實現&#xff1a; 引言&#xff1a; 看下圖&#xff0c;這是 course_category 課程分類表的結構&#xff1a; 這張表是一個樹型結構&#xff0c;通過父結點id將各元素組成一個樹。 我…

微服務難題?Nacos服務發現來救場

文章目錄 前言1.什么是服務發現2.Nacos 閃亮登場2.1 服務注冊2.2 服務發現 3.Nacos 的優勢3.1 簡單易用3.2 高可用3.3 動態配置 4.實戰演練4.1安裝 Nacos4.2 服務注冊與發現示例代碼&#xff08;以 Spring Boot 為例&#xff09; 總結 前言 大家好&#xff0c;我是沛哥兒。今天…

AStar低代碼平臺-腳本調用C#方法

修改報工表表單&#xff0c;右鍵定義彈出菜單&#xff0c;新增一個菜單項&#xff0c;并在點擊事件腳本中編寫調用腳本。 編譯腳本&#xff0c;然后在模塊代碼里面定義這個方法&#xff1a; public async Task<int> on_call_import(DataRow curRow) {PrintDataRow(cur…

python調用langchain實現RAG

一、安裝langchain 安裝依賴 python -m venv env.\env\Scripts\activatepip3 install langchainpip3 install langchain-corepip3 install langchain-openaipip3 install langchain-communitypip3 install dashscopepip3 install langchain_postgrespip3 install "psyc…

大學大模型教學:基于NC數據的全球氣象可視化解決方案

引言 氣象數據通常以NetCDF(Network Common Data Form)格式存儲,這是一種廣泛應用于科學數據存儲的二進制文件格式。在大學氣象學及相關專業的教學中,掌握如何讀取、處理和可視化NC數據是一項重要技能。本文將詳細介紹基于Python的NC數據處理與可視化解決方案,包含完整的代…

ORB-SLAM2學習筆記:ComputeKeyPointsOctTree分析過程記錄

ComputeKeyPointsOctTree是ORB特征提取器中計算關鍵點的部分&#xff0c;特別是使用八叉樹&#xff08;OctTree&#xff09;方法進行關鍵點分布。 首先&#xff0c;函數參數是vector<vector的引用allKeypoints&#xff0c;用來存儲各層的關鍵點。代碼開頭調整了allKeypoint…

LeetCode Hot100(多維動態規劃)

62. 不同路徑 比較板子的dp&#xff0c;實際上就是到達一個點有兩種方式&#xff0c;從上面來或者是左邊&#xff0c;加起來就可以了 class Solution {public int uniquePaths(int m, int n) {int [][]arr new int[m2][n2];arr[1][1]1;for(int i1;i<m;i){for(int j1;j<…

Oracle MOVE ONLINE 實現原理

Oracle MOVE ONLINE 實現原理 Oracle 的 MOVE ONLINE 操作是一種在線重組表的技術&#xff0c;允許在不中斷業務的情況下重新組織表數據。以下是其實現原理的詳細分析&#xff1a; 基本概念 MOVE ONLINE 是 Oracle 12c 引入的特性&#xff0c;用于替代傳統的 ALTER TABLE ..…

工作流長任務處置方案

以下是前后端協作處理長任務工作流的完整實現方案&#xff0c;結合技術選型與設計要點&#xff0c;以清晰結構呈現&#xff1a; 一、后端實現方案 異步任務隊列架構 ? 技術選型&#xff1a; ? 消息隊列&#xff1a;NATS&#xff08;輕量級&#xff09;或 RabbitMQ&#xf…

RabbitMQ仲裁隊列高可用架構解析

#作者&#xff1a;閆乾苓 文章目錄 概述工作原理1.節點之間的交互2.消息復制3.共識機制4.選舉領導者5.消息持久化6.自動故障轉移 集群環境節點管理仲裁隊列增加集群節點重新平衡仲裁隊列leader所在節點仲裁隊列減少集群節點 副本管理add_member 在給定節點上添加仲裁隊列成員&…

fingerprint2瀏覽器指紋使用記錄

我在uniapp-vue3-H5端使用的&#xff0c;記錄一下 抄的這里前端使用fingerprintjs2獲取瀏覽器指紋fingerprintjs2是通過設備瀏覽器信息獲取瀏覽器指紋的插件&#xff08; - 掘金 1、安裝依賴 npm i fingerprintjs2 -S2、抽成模塊文件&#xff0c;/utils/Fingerprint2.js 生成指…

深度學習面試八股簡略速覽

在準備深度學習面試時&#xff0c;你可能會感到有些不知所措。畢竟&#xff0c;深度學習是一個龐大且不斷發展的領域&#xff0c;涉及眾多復雜的技術和概念。但別擔心&#xff0c;本文將為你提供一份全面的指南&#xff0c;從基礎理論到實際應用&#xff0c;幫助你在面試中脫穎…

使用 Redis 作為向量數據庫

一、什么是向量數據庫&#xff1f; 向量&#xff08;Vector&#xff09;&#xff1a;在機器學習和 AI 中&#xff0c;向量是由一系列數字組成的序列&#xff0c;用于數值化地描述數據的特征或語義。文本、圖像、音頻等非結構化數據可以通過模型轉換成固定長度的向量。 向量數據…

變量的計算

不同類型變量之間的計算 數字型變量可以直接計算 在python中&#xff0c;數字型變量可以直接通過算術運算符計算bool型變量&#xff1a;True 對應數字1 &#xff1b;False 對應數字0、 字符串變量 使用 拼接字符串 使用 * 拼接指定倍數的相同字符串 變量的輸入&#xff1a;&…

PostgreSQL學會如何建表

開始使用PostgreSQL之前&#xff0c; 上一節我們說了怎樣安裝它。 PostgreSQL可能已經安裝到你的電腦上了,安裝后postgre服務默認在電腦開機時運行啟動。 一.了解PostgreSQL的運行 PostgreSQL使用一種客戶端/服務器&#xff08;C/S&#xff09;模型。 和其他典型的客戶端/服務…

Linux驅動學習筆記(十)

熱插拔 1.熱插拔&#xff1a;就是帶電插拔&#xff0c;即允許用戶在不關閉系統&#xff0c;不切斷電源的情況下拆卸或安裝硬盤&#xff0c;板卡等設備。熱插拔是內核和用戶空間之間&#xff0c;通過調用用戶空間程序實現交互來實現的&#xff0c;當內核發生了某種熱拔插事件時…

大模型應用開發第五講:成熟度模型:從ChatGPT(L2)到未來自主Agent(L4)

大模型應用開發第五講&#xff1a;成熟度模型&#xff1a;從ChatGPT&#xff08;L2&#xff09;到未來自主Agent&#xff08;L4&#xff09; 資料取自《大模型應用開發&#xff1a;動手做AI Agent 》。 查看總目錄&#xff1a;學習大綱 關于DeepSeek本地部署指南可以看下我之…

Delphi 導入excel

Delphi導入Excel的常見方法可分為兩種主流方案&#xff1a;基于OLE自動化操作Excel原生接口和利用第三方組件庫。以下為具體實現流程及注意事項&#xff1a; ?一、OLE自動化方案&#xff08;推薦基礎場景&#xff09;? 該方法通過COM接口調用本地安裝的Excel程序&#xff0c…

Selenium的第四天打卡——Selenium瀏覽器應用(完整版)

Selenium瀏覽器應用 目錄 Selenium瀏覽器應用 一、瀏覽器操作示例代碼 1.設置瀏覽器縮放大小 2.瀏覽器前進和后退 3.瀏覽器刷新 二、WebDriver常見方法 三、鼠標事件示例 四、鍵盤事件示例 五、獲取斷言信息 六、窗口的切換 七、關鍵注意事項 一、瀏覽器操作示例代…