Skip to content

状态管理

在 DVHA 框架中,状态管理主要通过 Vue 3 的 Composition API 和 Pinia 实现。本指南将介绍如何有效管理应用状态。

组件级状态

基础状态管理

vue
<script setup>
import { ref, reactive, computed } from 'vue'

// 简单状态
const loading = ref(false)
const count = ref(0)

// 复杂状态对象
const userForm = reactive({
  username: '',
  email: '',
  status: true
})

// 计算属性
const isFormValid = computed(() => {
  return userForm.username && userForm.email
})

// 状态更新方法
const updateUser = (userData) => {
  Object.assign(userForm, userData)
}
</script>

表单状态管理

vue
<script setup>
import { ref, watch } from 'vue'

const model = ref({
  name: '',
  email: '',
  role: null
})

const errors = ref({})
const touched = ref({})

// 监听表单变化
watch(model, (newValue, oldValue) => {
  // 清除已修改字段的错误
  Object.keys(newValue).forEach(key => {
    if (newValue[key] !== oldValue[key]) {
      delete errors.value[key]
    }
  })
}, { deep: true })

// 字段验证
const validateField = (field, value) => {
  touched.value[field] = true
  
  if (field === 'email' && value && !isValidEmail(value)) {
    errors.value[field] = '请输入有效的邮箱地址'
  } else {
    delete errors.value[field]
  }
}

const isValidEmail = (email) => {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
</script>

应用级状态

全局状态 Store

javascript
// stores/app.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useAppStore = defineStore('app', () => {
  // 状态
  const loading = ref(false)
  const theme = ref('light')
  const sidebarCollapsed = ref(false)
  const notifications = ref([])
  
  // 计算属性
  const notificationCount = computed(() => {
    return notifications.value.filter(n => !n.read).length
  })
  
  // 方法
  const setLoading = (state) => {
    loading.value = state
  }
  
  const toggleTheme = () => {
    theme.value = theme.value === 'light' ? 'dark' : 'light'
  }
  
  const toggleSidebar = () => {
    sidebarCollapsed.value = !sidebarCollapsed.value
  }
  
  const addNotification = (notification) => {
    notifications.value.unshift({
      id: Date.now(),
      read: false,
      timestamp: new Date(),
      ...notification
    })
  }
  
  const markAsRead = (id) => {
    const notification = notifications.value.find(n => n.id === id)
    if (notification) {
      notification.read = true
    }
  }
  
  const clearNotifications = () => {
    notifications.value = []
  }
  
  return {
    // 状态
    loading,
    theme,
    sidebarCollapsed,
    notifications,
    
    // 计算属性
    notificationCount,
    
    // 方法
    setLoading,
    toggleTheme,
    toggleSidebar,
    addNotification,
    markAsRead,
    clearNotifications
  }
})

用户状态管理

javascript
// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { authApi } from '@/api/auth'

export const useUserStore = defineStore('user', () => {
  // 状态
  const user = ref(null)
  const token = ref(localStorage.getItem('token'))
  const permissions = ref([])
  const roles = ref([])
  
  // 计算属性
  const isAuthenticated = computed(() => {
    return !!token.value && !!user.value
  })
  
  const userName = computed(() => {
    return user.value?.name || '未登录'
  })
  
  const userAvatar = computed(() => {
    return user.value?.avatar || '/default-avatar.png'
  })
  
  // 方法
  const login = async (credentials) => {
    try {
      const response = await authApi.login(credentials)
      
      token.value = response.token
      user.value = response.user
      permissions.value = response.permissions
      roles.value = response.roles
      
      localStorage.setItem('token', response.token)
      
      return response
    } catch (error) {
      throw error
    }
  }
  
  const logout = async () => {
    try {
      await authApi.logout()
    } catch (error) {
      console.error('退出登录失败:', error)
    } finally {
      token.value = null
      user.value = null
      permissions.value = []
      roles.value = []
      localStorage.removeItem('token')
    }
  }
  
  const fetchProfile = async () => {
    try {
      const response = await authApi.profile()
      user.value = response.user
      permissions.value = response.permissions
      roles.value = response.roles
    } catch (error) {
      console.error('获取用户信息失败:', error)
    }
  }
  
  const hasPermission = (permission) => {
    return permissions.value.includes(permission)
  }
  
  const hasRole = (role) => {
    return roles.value.includes(role)
  }
  
  const hasAnyPermission = (permissionList) => {
    return permissionList.some(permission => hasPermission(permission))
  }
  
  return {
    // 状态
    user,
    token,
    permissions,
    roles,
    
    // 计算属性
    isAuthenticated,
    userName,
    userAvatar,
    
    // 方法
    login,
    logout,
    fetchProfile,
    hasPermission,
    hasRole,
    hasAnyPermission
  }
})

数据缓存

接口数据缓存

javascript
// stores/cache.js
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useCacheStore = defineStore('cache', () => {
  const cache = ref(new Map())
  
  const get = (key) => {
    const item = cache.value.get(key)
    if (!item) return null
    
    // 检查是否过期
    if (item.expireTime && Date.now() > item.expireTime) {
      cache.value.delete(key)
      return null
    }
    
    return item.data
  }
  
  const set = (key, data, ttl = 300000) => { // 默认5分钟
    const expireTime = ttl > 0 ? Date.now() + ttl : null
    cache.value.set(key, {
      data,
      expireTime,
      timestamp: Date.now()
    })
  }
  
  const remove = (key) => {
    cache.value.delete(key)
  }
  
  const clear = () => {
    cache.value.clear()
  }
  
  const has = (key) => {
    return cache.value.has(key) && get(key) !== null
  }
  
  return {
    get,
    set,
    remove,
    clear,
    has
  }
})

使用缓存的数据获取

javascript
// composables/useApi.js
import { ref, computed } from 'vue'
import { useCacheStore } from '@/stores/cache'
import request from '@/utils/request'

export function useApi(url, options = {}) {
  const { 
    cache = false, 
    cacheTTL = 300000,
    immediate = true 
  } = options
  
  const cacheStore = useCacheStore()
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  
  const cacheKey = computed(() => `api:${url}`)
  
  const fetchData = async (params = {}) => {
    const queryString = new URLSearchParams(params).toString()
    const fullUrl = queryString ? `${url}?${queryString}` : url
    const fullCacheKey = `${cacheKey.value}:${queryString}`
    
    // 检查缓存
    if (cache && cacheStore.has(fullCacheKey)) {
      data.value = cacheStore.get(fullCacheKey)
      return data.value
    }
    
    try {
      loading.value = true
      error.value = null
      
      const response = await request.get(fullUrl)
      data.value = response
      
      // 缓存数据
      if (cache) {
        cacheStore.set(fullCacheKey, response, cacheTTL)
      }
      
      return response
    } catch (err) {
      error.value = err
      throw err
    } finally {
      loading.value = false
    }
  }
  
  const refresh = () => {
    if (cache) {
      cacheStore.remove(cacheKey.value)
    }
    return fetchData()
  }
  
  // 自动加载
  if (immediate) {
    fetchData()
  }
  
  return {
    data,
    loading,
    error,
    fetchData,
    refresh
  }
}

响应式状态同步

WebSocket 状态同步

javascript
// composables/useWebSocket.js
import { ref, onUnmounted } from 'vue'
import { useUserStore } from '@/stores/user'

export function useWebSocket(url) {
  const userStore = useUserStore()
  const connected = ref(false)
  const ws = ref(null)
  
  const connect = () => {
    if (ws.value) return
    
    ws.value = new WebSocket(url)
    
    ws.value.onopen = () => {
      connected.value = true
      console.log('WebSocket 连接已建立')
      
      // 发送认证信息
      if (userStore.token) {
        ws.value.send(JSON.stringify({
          type: 'auth',
          token: userStore.token
        }))
      }
    }
    
    ws.value.onmessage = (event) => {
      const data = JSON.parse(event.data)
      handleMessage(data)
    }
    
    ws.value.onclose = () => {
      connected.value = false
      console.log('WebSocket 连接已断开')
      
      // 重连逻辑
      setTimeout(() => {
        if (userStore.isAuthenticated) {
          connect()
        }
      }, 5000)
    }
    
    ws.value.onerror = (error) => {
      console.error('WebSocket 错误:', error)
    }
  }
  
  const disconnect = () => {
    if (ws.value) {
      ws.value.close()
      ws.value = null
    }
  }
  
  const send = (data) => {
    if (ws.value && connected.value) {
      ws.value.send(JSON.stringify(data))
    }
  }
  
  const handleMessage = (data) => {
    switch (data.type) {
      case 'notification':
        const appStore = useAppStore()
        appStore.addNotification(data.payload)
        break
      case 'user_update':
        userStore.fetchProfile()
        break
      case 'system_update':
        // 处理系统更新
        break
    }
  }
  
  onUnmounted(() => {
    disconnect()
  })
  
  return {
    connected,
    connect,
    disconnect,
    send
  }
}

最佳实践

1. 状态结构设计

javascript
// 好的状态结构
const store = defineStore('users', () => {
  const users = ref([])
  const currentUser = ref(null)
  const loading = ref(false)
  const error = ref(null)
  const pagination = ref({
    page: 1,
    pageSize: 20,
    total: 0
  })
  
  // 避免深层嵌套
  const userDetails = ref(new Map()) // 使用 Map 存储详情
  
  return {
    users,
    currentUser,
    loading,
    error,
    pagination,
    userDetails
  }
})

2. 异步操作处理

javascript
// stores/async.js
export const useAsyncStore = defineStore('async', () => {
  const loadingStates = ref(new Map())
  const errors = ref(new Map())
  
  const createAsyncAction = (key, asyncFn) => {
    return async (...args) => {
      loadingStates.value.set(key, true)
      errors.value.delete(key)
      
      try {
        const result = await asyncFn(...args)
        return result
      } catch (error) {
        errors.value.set(key, error)
        throw error
      } finally {
        loadingStates.value.set(key, false)
      }
    }
  }
  
  const isLoading = (key) => {
    return loadingStates.value.get(key) || false
  }
  
  const getError = (key) => {
    return errors.value.get(key) || null
  }
  
  return {
    createAsyncAction,
    isLoading,
    getError
  }
})

3. 状态持久化

javascript
// plugins/persistence.js
import { watch } from 'vue'

export function persistState(store, key, options = {}) {
  const { 
    storage = localStorage,
    serializer = JSON,
    pick = null
  } = options
  
  // 恢复状态
  const savedState = storage.getItem(key)
  if (savedState) {
    try {
      const parsed = serializer.parse(savedState)
      if (pick) {
        Object.keys(parsed).forEach(field => {
          if (pick.includes(field) && store[field] !== undefined) {
            store[field] = parsed[field]
          }
        })
      } else {
        Object.assign(store, parsed)
      }
    } catch (error) {
      console.error('恢复状态失败:', error)
    }
  }
  
  // 监听状态变化
  watch(
    () => store.$state,
    (state) => {
      try {
        const toSave = pick ? 
          Object.fromEntries(
            Object.entries(state).filter(([key]) => pick.includes(key))
          ) : state
        storage.setItem(key, serializer.stringify(toSave))
      } catch (error) {
        console.error('保存状态失败:', error)
      }
    },
    { deep: true }
  )
}

通过合理的状态管理,您可以构建出响应迅速、数据一致的用户界面。