Vuex 与 Composition API 的深度整合实践

一、useStore 组合式函数

在 Vue 3 的 Composition API 中,我们可以通过 useStore 函数来访问 Vuex store 实例。这是 Vuex 4 为 Composition API 提供的内置支持。

import { useStore } from 'vuex'

export default {
  setup() {
    const store = useStore()
    
    // 现在可以通过 store 访问所有 Vuex 功能
    return {
      store
    }
  }
}

实践建议

  • 在大型应用中,建议在 setup() 顶部统一获取 store 实例
  • 可以将 useStore() 的结果赋给一个具有语义化的变量名,如 userStoreproductStore

二、在 setup 中访问 State 和 Getters

访问 State

import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
  setup() {
    const store = useStore()
    
    // 直接访问 state
    const count = computed(() => store.state.count)
    
    // 使用展开运算符访问多个 state
    const { username, userId } = computed(() => ({
      username: store.state.user.name,
      userId: store.state.user.id
    })).value
    
    return {
      count,
      username,
      userId
    }
  }
}

访问 Getters

setup() {
  const store = useStore()
  
  // 访问 getter
  const doubleCount = computed(() => store.getters.doubleCount)
  
  // 带参数的 getter
  const getUserById = (id) => computed(() => store.getters.getUserById(id))
  
  return {
    doubleCount,
    getUserById
  }
}

实践建议

  • 始终使用 computed 包装 state 和 getters,确保响应性
  • 对于频繁访问的 state,考虑使用解构赋值提高代码可读性
  • 对于带参数的 getters,可以封装成函数形式返回计算属性

三、封装自定义 Vuex 组合逻辑

我们可以将常用的 Vuex 操作封装成可复用的组合式函数,这是 Composition API 的最大优势之一。

基础封装示例

// useUserStore.js
import { computed } from 'vue'
import { useStore } from 'vuex'

export function useUserStore() {
  const store = useStore()
  
  const currentUser = computed(() => store.state.user.current)
  const isLoggedIn = computed(() => store.getters['user/isLoggedIn'])
  
  const login = (credentials) => store.dispatch('user/login', credentials)
  const logout = () => store.dispatch('user/logout')
  
  return {
    currentUser,
    isLoggedIn,
    login,
    logout
  }
}

带命名空间的模块封装

// useProductStore.js
export function useProductStore() {
  const store = useStore()
  
  // 使用命名空间路径访问模块
  const products = computed(() => store.state.products.items)
  const featuredProducts = computed(() => store.getters['products/featured'])
  
  const fetchProducts = () => store.dispatch('products/fetchAll')
  const addProduct = (product) => store.dispatch('products/add', product)
  
  return {
    products,
    featuredProducts,
    fetchProducts,
    addProduct
  }
}

高级封装:带选项的组合函数

// usePaginatedData.js
export function usePaginatedData(moduleName, options = {}) {
  const { pageSize = 10 } = options
  const store = useStore()
  
  const items = computed(() => store.state[moduleName].items)
  const currentPage = computed(() => store.state[moduleName].pagination.page)
  
  const paginatedItems = computed(() => {
    const start = (currentPage.value - 1) * pageSize
    return items.value.slice(start, start + pageSize)
  })
  
  const setPage = (page) => store.commit(`${moduleName}/SET_PAGE`, page)
  
  return {
    items,
    currentPage,
    paginatedItems,
    setPage
  }
}

实践建议

  • 按照业务领域而非技术功能组织组合函数
  • 为复杂的组合函数添加 JSDoc 注释说明用法
  • 考虑将组合函数放在单独的 composables 目录中
  • 对于跨多个组件的通用逻辑,优先使用组合函数而非 mixins

四、响应式操作的最佳实践

import { watch, toRefs } from 'vue'

export function useCart() {
  const store = useStore()
  
  const cartItems = computed(() => store.state.cart.items)
  const cartTotal = computed(() => store.getters['cart/total'])
  
  // 使用 toRefs 确保解构后保持响应性
  const { items, checkoutStatus } = toRefs(store.state.cart)
  
  // 监听 cartItems 变化
  watch(cartItems, (newItems) => {
    if (newItems.length > 5) {
      console.warn('Cart has more than 5 items')
    }
  }, { deep: true })
  
  const checkout = async () => {
    try {
      await store.dispatch('cart/checkout')
      // 处理成功逻辑
    } catch (error) {
      // 处理错误
    }
  }
  
  return {
    cartItems,
    cartTotal,
    checkout,
    items,
    checkoutStatus
  }
}

五、与 TypeScript 的类型集成

import { computed } from 'vue'
import { useStore } from 'vuex'
import { RootState } from '../store/types'

interface User {
  id: number
  name: string
  email: string
}

export function useUserStore() {
  const store = useStore<RootState>()
  
  const currentUser = computed<User | null>(() => store.state.user.current)
  const isAdmin = computed<boolean>(() => store.getters['user/isAdmin'])
  
  const updateProfile = (payload: { name: string; email: string }) => {
    return store.dispatch('user/updateProfile', payload)
  }
  
  return {
    currentUser,
    isAdmin,
    updateProfile
  }
}

架构建议

  1. 对于中小型应用,可以直接在组件中使用 useStore
  2. 对于大型应用,推荐为每个业务模块创建专用的组合函数
  3. 组合函数应该聚焦单一职责,避免成为"上帝对象"
  4. 考虑将组合函数分为:

    • 只读操作(state/getters)
    • 写入操作(mutations/actions)
    • 复杂业务逻辑

通过这种模式,我们可以在 Vue 3 应用中实现:

  • 更好的类型推断(配合 TypeScript)
  • 更清晰的逻辑组织
  • 更高的代码复用性
  • 更简单的单元测试

总结

Vuex 与 Composition API 的结合为状态管理带来了新的可能性。通过 useStore 和自定义组合函数,我们可以创建更具表达力和可维护性的状态逻辑。关键是要找到适合项目规模的抽象层级 - 小型项目可能只需要基本的 useStore 使用,而大型应用则能从精心设计的组合函数中获益良多。

评论已关闭