组合式Store写法
组合式Store写法
组合式Store是Vue 3中利用Composition API管理状态的一种方式。它通过ref
、reactive
等响应式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
}
}
性能优化
对于大型应用,可以使用shallowRef
或shallowReactive
来减少不必要的响应式开销。
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
}
}
副作用管理
使用watch
和watchEffect
可以在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
上一篇:创建和使用Store
下一篇:Options式Store写法