阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 组合式Store写法

组合式Store写法

作者:陈川 阅读数:64117人阅读 分类: Vue.js

组合式Store写法

组合式Store是Vue 3中利用Composition API管理状态的一种方式。它通过refreactive等响应式API创建可复用的状态逻辑,相比传统的Options API更加灵活和模块化。

基本概念

组合式Store的核心思想是将相关状态和逻辑组织在一起,形成一个可复用的单元。与Vuex等状态管理库不同,它不需要额外的安装和配置,直接使用Vue的响应式系统。

import { ref, computed } from 'vue'

export function useCounterStore() {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  
  function increment() {
    count.value++
  }
  
  return {
    count,
    doubleCount,
    increment
  }
}

创建和使用Store

创建组合式Store通常在一个单独的文件中定义,然后在组件中导入使用。这种方式保持了状态的单一来源,同时避免了全局状态的污染。

// stores/counter.js
import { ref } from 'vue'

export function useCounter() {
  const count = ref(0)
  
  function increment() {
    count.value++
  }
  
  return {
    count,
    increment
  }
}

在组件中使用:

<script setup>
import { useCounter } from './stores/counter'

const { count, increment } = useCounter()
</script>

<template>
  <button @click="increment">{{ count }}</button>
</template>

状态共享

当需要在多个组件间共享状态时,可以在模块顶层定义响应式变量,这样所有导入该Store的组件都会访问同一个状态实例。

// stores/counter.js
import { ref } from 'vue'

const count = ref(0) // 模块作用域内的共享状态

export function useCounter() {
  function increment() {
    count.value++
  }
  
  return {
    count,
    increment
  }
}

复杂状态管理

对于更复杂的状态,可以使用reactive替代多个ref,使相关状态保持在一个对象中。

// stores/user.js
import { reactive, computed } from 'vue'

const state = reactive({
  user: null,
  isAuthenticated: false
})

export function useUserStore() {
  const fullName = computed(() => {
    return state.user 
      ? `${state.user.firstName} ${state.user.lastName}`
      : 'Guest'
  })
  
  function login(userData) {
    state.user = userData
    state.isAuthenticated = true
  }
  
  function logout() {
    state.user = null
    state.isAuthenticated = false
  }
  
  return {
    state,
    fullName,
    login,
    logout
  }
}

异步操作处理

组合式Store可以方便地处理异步操作,如API请求。结合async/await语法,代码结构清晰易读。

// stores/posts.js
import { ref } from 'vue'
import api from '@/api'

const posts = ref([])
const isLoading = ref(false)
const error = ref(null)

export function usePostsStore() {
  async function fetchPosts() {
    isLoading.value = true
    error.value = null
    
    try {
      const response = await api.get('/posts')
      posts.value = response.data
    } catch (err) {
      error.value = err.message
    } finally {
      isLoading.value = false
    }
  }
  
  return {
    posts,
    isLoading,
    error,
    fetchPosts
  }
}

状态持久化

有时需要将状态持久化到localStorage或sessionStorage,可以在Store中添加相应的逻辑。

// stores/settings.js
import { ref, watch } from 'vue'

const settings = ref(
  JSON.parse(localStorage.getItem('appSettings')) || {
    theme: 'light',
    fontSize: 16
  }
)

watch(settings, (newValue) => {
  localStorage.setItem('appSettings', JSON.stringify(newValue))
}, { deep: true })

export function useSettingsStore() {
  function toggleTheme() {
    settings.value.theme = settings.value.theme === 'light' ? 'dark' : 'light'
  }
  
  function increaseFontSize() {
    settings.value.fontSize += 1
  }
  
  function decreaseFontSize() {
    settings.value.fontSize = Math.max(12, settings.value.fontSize - 1)
  }
  
  return {
    settings,
    toggleTheme,
    increaseFontSize,
    decreaseFontSize
  }
}

模块化组织

随着应用规模增长,可以将Store按功能模块拆分,然后在需要时组合使用。

// stores/index.js
import { useUserStore } from './user'
import { usePostsStore } from './posts'
import { useSettingsStore } from './settings'

export function useStore() {
  return {
    user: useUserStore(),
    posts: usePostsStore(),
    settings: useSettingsStore()
  }
}

在组件中使用:

<script setup>
import { useStore } from './stores'

const { user, posts, settings } = useStore()
</script>

类型安全(TypeScript)

使用TypeScript可以为组合式Store提供更好的类型支持和代码提示。

// stores/counter.ts
import { ref } from 'vue'

interface CounterStore {
  count: Ref<number>
  increment: () => void
}

export function useCounter(): CounterStore {
  const count = ref<number>(0)
  
  function increment(): void {
    count.value++
  }
  
  return {
    count,
    increment
  }
}

性能优化

对于大型应用,可以使用shallowRefshallowReactive来减少不必要的响应式开销。

import { shallowRef } from 'vue'

const largeData = shallowRef({ /* 大型数据对象 */ })

测试策略

组合式Store的测试相对简单,因为它们是纯JavaScript函数,不依赖Vue组件实例。

// stores/counter.spec.js
import { useCounter } from './counter'

describe('useCounter', () => {
  it('should increment count', () => {
    const { count, increment } = useCounter()
    expect(count.value).toBe(0)
    
    increment()
    expect(count.value).toBe(1)
  })
})

与Pinia的比较

虽然组合式Store提供了轻量级的状态管理方案,但对于更复杂的需求,Pinia可能是更好的选择。Pinia基于类似的理念,但提供了更多功能如DevTools集成、插件系统等。

// 使用Pinia的示例
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  actions: {
    increment() {
      this.count++
    }
  }
})

实际应用场景

组合式Store特别适合中小型应用或大型应用中的局部状态管理。例如表单处理、UI状态管理等场景。

// stores/form.js
import { reactive, computed } from 'vue'

export function useFormStore(initialData) {
  const form = reactive({ ...initialData })
  const errors = reactive({})
  const isValid = computed(() => Object.keys(errors).length === 0)
  
  function validate() {
    // 验证逻辑
  }
  
  function reset() {
    Object.assign(form, initialData)
    Object.keys(errors).forEach(key => delete errors[key])
  }
  
  return {
    form,
    errors,
    isValid,
    validate,
    reset
  }
}

响应式工具函数

Vue提供了一系列响应式工具函数,可以在组合式Store中灵活使用。

import { toRef, toRefs, isRef, unref } from 'vue'

export function useProductStore(product) {
  // 将响应式对象的属性转换为ref
  const { id, name } = toRefs(product)
  
  // 检查是否为ref
  if (isRef(product.price)) {
    // ...
  }
  
  // 获取ref的值
  const price = unref(product.price)
  
  return {
    id,
    name,
    price
  }
}

组合Store的复用

可以通过函数参数和返回值的设计,使Store更加灵活和可配置。

// stores/pagination.js
import { ref, computed } from 'vue'

export function usePagination(totalItems, itemsPerPage = 10) {
  const currentPage = ref(1)
  
  const totalPages = computed(() => 
    Math.ceil(totalItems / itemsPerPage)
  )
  
  function nextPage() {
    if (currentPage.value < totalPages.value) {
      currentPage.value++
    }
  }
  
  function prevPage() {
    if (currentPage.value > 1) {
      currentPage.value--
    }
  }
  
  return {
    currentPage,
    totalPages,
    nextPage,
    prevPage
  }
}

副作用管理

使用watchwatchEffect可以在Store中管理副作用,保持逻辑的集中性。

import { ref, watch } from 'vue'

export function useSearchStore() {
  const query = ref('')
  const results = ref([])
  
  watch(query, async (newQuery) => {
    if (newQuery.trim()) {
      results.value = await searchApi(newQuery)
    } else {
      results.value = []
    }
  }, { immediate: true })
  
  return {
    query,
    results
  }
}

状态重置

在某些场景下,需要提供重置状态到初始值的能力。

// stores/filters.js
import { reactive, toRaw } from 'vue'

export function useFiltersStore(initialFilters) {
  const filters = reactive({ ...initialFilters })
  const initial = toRaw(initialFilters) // 获取非响应式副本
  
  function reset() {
    Object.assign(filters, initial)
  }
  
  return {
    filters,
    reset
  }
}

跨Store通信

虽然组合式Store通常是独立的,但有时需要它们之间进行通信。

// stores/auth.js
import { useUserStore } from './user'

export function useAuthStore() {
  const { state: userState } = useUserStore()
  
  function checkPermission(permission) {
    return userState.user?.permissions.includes(permission)
  }
  
  return {
    checkPermission
  }
}

响应式状态转换

使用computed可以创建基于其他状态的派生状态。

import { ref, computed } from 'vue'

export function useCartStore() {
  const items = ref([])
  
  const total = computed(() => 
    items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
  )
  
  const itemCount = computed(() => 
    items.value.reduce((count, item) => count + item.quantity, 0)
  )
  
  return {
    items,
    total,
    itemCount
  }
}

状态快照

有时需要获取状态的不可变快照,可以使用toRaw或展开运算符。

import { reactive, toRaw } from 'vue'

export function useEditorStore() {
  const state = reactive({
    content: '',
    selection: null
  })
  
  function getSnapshot() {
    return { ...toRaw(state) }
  }
  
  return {
    state,
    getSnapshot
  }
}

性能敏感场景

对于性能敏感的场景,可以控制响应式更新的粒度。

import { shallowReactive, markRaw } from 'vue'

export function useCanvasStore() {
  const shapes = shallowReactive([])
  
  function addShape(shape) {
    shapes.push(markRaw(shape)) // 标记为非响应式
  }
  
  return {
    shapes,
    addShape
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌