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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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常见方法 三、鼠标事件示例 四、键盘事件示例 五、获取断言信息 六、窗口的切换 七、关键注意事项 一、浏览器操作示例代…