Skip to content
On this page

Pinia Store定义

在Pinia中,Store是存储和管理应用状态的核心概念。本章将详细介绍如何定义Store、不同类型的Store以及最佳实践。

Store定义基础

使用defineStore函数

typescript
import { defineStore } from 'pinia'

// 最基础的Store定义
export const useMainStore = defineStore('main', {
  state: () => ({
    count: 0,
    name: 'Eduardo',
    isAdmin: true
  })
})

Store的三个核心组成部分

typescript
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  // 1. State - 存储数据
  state: () => ({
    user: null as User | null,
    isLoggedIn: false,
    preferences: {
      theme: 'light',
      language: 'en'
    }
  }),
  
  // 2. Getters - 计算属性
  getters: {
    fullName: (state) => {
      if (state.user) {
        return `${state.user.firstName} ${state.user.lastName}`
      }
      return ''
    },
    
    isAdmin: (state) => {
      return state.user?.role === 'admin'
    }
  },
  
  // 3. Actions - 方法
  actions: {
    login(user: User) {
      this.user = user
      this.isLoggedIn = true
    },
    
    logout() {
      this.user = null
      this.isLoggedIn = false
    },
    
    updatePreferences(newPrefs: Partial<UserPreferences>) {
      this.preferences = { ...this.preferences, ...newPrefs }
    }
  }
})

interface User {
  id: string
  firstName: string
  lastName: string
  role: string
}

interface UserPreferences {
  theme: 'light' | 'dark'
  language: string
}

不同的Store定义语法

选项式语法(Options Store)

typescript
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    lastChanged: null as Date | null
  }),
  
  getters: {
    doubleCount: (state) => state.count * 2,
    
    // 带参数的getter
    getMultipliedCount: (state) => (factor: number) => {
      return state.count * factor
    }
  },
  
  actions: {
    increment() {
      this.count++
      this.lastChanged = new Date()
    },
    
    incrementBy(amount: number) {
      this.count += amount
      this.lastChanged = new Date()
    },
    
    async incrementAsync() {
      await new Promise(resolve => setTimeout(resolve, 1000))
      this.increment()
    }
  }
})

组合式语法(Setup Store)

typescript
import { defineStore } from 'pinia'
import { ref, computed, watch } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  // State
  const count = ref(0)
  const lastChanged = ref<Date | null>(null)
  
  // Getters
  const doubleCount = computed(() => count.value * 2)
  
  const getMultipliedCount = (factor: number) => {
    return count.value * factor
  }
  
  // Actions
  function increment() {
    count.value++
    lastChanged.value = new Date()
  }
  
  function incrementBy(amount: number) {
    count.value += amount
    lastChanged.value = new Date()
  }
  
  async function incrementAsync() {
    await new Promise(resolve => setTimeout(resolve, 1000))
    increment()
  }
  
  // 可以使用Vue的组合式API
  watch(count, (newCount) => {
    console.log(`Count changed to: ${newCount}`)
  })
  
  return {
    // Expose state
    count,
    lastChanged,
    
    // Expose getters
    doubleCount,
    getMultipliedCount,
    
    // Expose actions
    increment,
    incrementBy,
    incrementAsync
  }
})

State的详细定义

基础State

typescript
export const useBasicStore = defineStore('basic', {
  state: () => ({
    // 基础类型
    count: 0,
    name: 'Alice',
    isActive: true,
    
    // 对象和数组
    users: [] as User[],
    config: {
      apiUrl: 'https://api.example.com',
      timeout: 5000
    }
  })
})

State初始化函数

typescript
export const useAdvancedStore = defineStore('advanced', {
  state: () => ({
    // 从localStorage初始化
    theme: localStorage.getItem('theme') || 'light',
    language: navigator.language || 'en',
    
    // 基于当前时间的初始化
    lastVisit: new Date().toISOString(),
    
    // 复杂对象
    session: {
      id: Math.random().toString(36).substr(2, 9),
      timestamp: Date.now()
    }
  })
})

State类型声明

typescript
interface CounterState {
  count: number
  doubleCount: number
  history: number[]
}

export const useTypedStore = defineStore('typed', {
  state: (): CounterState => ({
    count: 0,
    doubleCount: 0,
    history: []
  })
})

Getters的高级用法

基础Getters

typescript
export const useUserStore = defineStore('user', {
  state: () => ({
    users: [] as User[],
    currentUser: null as User | null
  }),
  
  getters: {
    // 简单计算属性
    adminUsers: (state) => {
      return state.users.filter(user => user.role === 'admin')
    },
    
    // 访问其他getter
    adminUserCount: (state) => {
      return state.users.filter(user => user.role === 'admin').length
    },
    
    // 条件计算
    isLoggedIn: (state) => {
      return state.currentUser !== null
    }
  }
})

带参数的Getters

typescript
export const useProductStore = defineStore('product', {
  state: () => ({
    products: [] as Product[]
  }),
  
  getters: {
    // 返回函数的getter
    getProductById: (state) => {
      return (id: string) => {
        return state.products.find(product => product.id === id)
      }
    },
    
    // 带过滤条件的getter
    getProductsByCategory: (state) => {
      return (category: string) => {
        return state.products.filter(
          product => product.category === category
        )
      }
    },
    
    // 带搜索功能的getter
    searchProducts: (state) => {
      return (query: string) => {
        return state.products.filter(
          product => 
            product.name.toLowerCase().includes(query.toLowerCase()) ||
            product.description.toLowerCase().includes(query.toLowerCase())
        )
      }
    }
  }
})

interface Product {
  id: string
  name: string
  description: string
  category: string
  price: number
}

Getter中的复杂逻辑

typescript
export const useAnalyticsStore = defineStore('analytics', {
  state: () => ({
    events: [] as AnalyticsEvent[]
  }),
  
  getters: {
    // 统计数据
    eventCountByType: (state) => {
      const counts: Record<string, number> = {}
      state.events.forEach(event => {
        counts[event.type] = (counts[event.type] || 0) + 1
      })
      return counts
    },
    
    // 时间范围内的事件
    getEventsInDateRange: (state) => {
      return (startDate: Date, endDate: Date) => {
        return state.events.filter(event => {
          const eventDate = new Date(event.timestamp)
          return eventDate >= startDate && eventDate <= endDate
        })
      }
    },
    
    // 平均值计算
    averageEventDuration: (state) => {
      if (state.events.length === 0) return 0
      const total = state.events.reduce(
        (sum, event) => sum + (event.duration || 0), 
        0
      )
      return total / state.events.length
    }
  }
})

interface AnalyticsEvent {
  id: string
  type: string
  timestamp: string
  duration?: number
  properties?: Record<string, any>
}

Actions的高级用法

同步Actions

typescript
export const useTodoStore = defineStore('todo', {
  state: () => ({
    todos: [] as Todo[],
    currentFilter: 'all' as 'all' | 'active' | 'completed'
  }),
  
  actions: {
    // 基础操作
    addTodo(text: string) {
      const newTodo: Todo = {
        id: Date.now().toString(),
        text,
        completed: false,
        createdAt: new Date()
      }
      this.todos.push(newTodo)
    },
    
    // 更新操作
    toggleTodo(id: string) {
      const todo = this.todos.find(todo => todo.id === id)
      if (todo) {
        todo.completed = !todo.completed
      }
    },
    
    // 删除操作
    removeTodo(id: string) {
      this.todos = this.todos.filter(todo => todo.id !== id)
    },
    
    // 批量操作
    toggleAll() {
      const allCompleted = this.todos.every(todo => todo.completed)
      this.todos.forEach(todo => {
        todo.completed = !allCompleted
      })
    },
    
    // 清理操作
    clearCompleted() {
      this.todos = this.todos.filter(todo => !todo.completed)
    },
    
    // 设置过滤器
    setFilter(filter: 'all' | 'active' | 'completed') {
      this.currentFilter = filter
    }
  }
})

interface Todo {
  id: string
  text: string
  completed: boolean
  createdAt: Date
}

异步Actions

typescript
export const useApiStore = defineStore('api', {
  state: () => ({
    users: [] as User[],
    loading: false,
    error: null as string | null,
    pagination: {
      page: 1,
      perPage: 10,
      total: 0
    }
  }),
  
  actions: {
    // 获取数据
    async fetchUsers() {
      this.loading = true
      this.error = null
      
      try {
        const response = await fetch('/api/users')
        if (!response.ok) throw new Error('Failed to fetch users')
        
        const data = await response.json()
        this.users = data.users
        this.pagination = data.pagination
      } catch (error) {
        this.error = (error as Error).message
      } finally {
        this.loading = false
      }
    },
    
    // 创建数据
    async createUser(userData: Omit<User, 'id'>) {
      this.loading = true
      try {
        const response = await fetch('/api/users', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(userData)
        })
        
        if (!response.ok) throw new Error('Failed to create user')
        
        const newUser = await response.json()
        this.users.push(newUser)
      } catch (error) {
        this.error = (error as Error).message
      } finally {
        this.loading = false
      }
    },
    
    // 更新数据
    async updateUser(id: string, updates: Partial<User>) {
      this.loading = true
      try {
        const response = await fetch(`/api/users/${id}`, {
          method: 'PATCH',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(updates)
        })
        
        if (!response.ok) throw new Error('Failed to update user')
        
        const updatedUser = await response.json()
        const index = this.users.findIndex(user => user.id === id)
        if (index !== -1) {
          this.users[index] = updatedUser
        }
      } catch (error) {
        this.error = (error as Error).message
      } finally {
        this.loading = false
      }
    },
    
    // 删除数据
    async deleteUser(id: string) {
      this.loading = true
      try {
        const response = await fetch(`/api/users/${id}`, {
          method: 'DELETE'
        })
        
        if (!response.ok) throw new Error('Failed to delete user')
        
        this.users = this.users.filter(user => user.id !== id)
      } catch (error) {
        this.error = (error as Error).message
      } finally {
        this.loading = false
      }
    },
    
    // 批量操作
    async batchUpdateUserStatus(userIds: string[], status: string) {
      this.loading = true
      try {
        const response = await fetch('/api/users/batch-update', {
          method: 'PATCH',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ userIds, status })
        })
        
        if (!response.ok) throw new Error('Failed to update users')
        
        const updatedUsers = await response.json()
        updatedUsers.forEach((updatedUser: User) => {
          const index = this.users.findIndex(u => u.id === updatedUser.id)
          if (index !== -1) {
            this.users[index] = updatedUser
          }
        })
      } catch (error) {
        this.error = (error as Error).message
      } finally {
        this.loading = false
      }
    }
  }
})

Store之间的交互

访问其他Store

typescript
// stores/session.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'

export const useSessionStore = defineStore('session', {
  state: () => ({
    sessionId: '',
    lastActivity: new Date()
  }),
  
  actions: {
    async initializeSession() {
      // 访问其他store
      const userStore = useUserStore()
      
      if (userStore.isLoggedIn) {
        this.sessionId = `session_${Date.now()}_${userStore.user?.id}`
        this.lastActivity = new Date()
      }
    },
    
    updateActivity() {
      this.lastActivity = new Date()
    }
  }
})

Store组合

typescript
// stores/app.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { useCartStore } from './cart'
import { useSessionStore } from './session'

export const useAppStore = defineStore('app', {
  state: () => ({
    initialized: false
  }),
  
  getters: {
    isReady(): boolean {
      const userStore = useUserStore()
      const cartStore = useCartStore()
      
      return userStore.isLoggedIn && cartStore.itemCount > 0
    }
  },
  
  actions: {
    async initializeApp() {
      const userStore = useUserStore()
      const sessionStore = useSessionStore()
      
      // 初始化多个store
      await Promise.all([
        userStore.fetchUserProfile(),
        sessionStore.initializeSession()
      ])
      
      this.initialized = true
    }
  }
})

Store最佳实践

1. Store命名约定

typescript
// 推荐:使用use前缀和Store后缀
export const useUserStore = defineStore('user', { ... })
export const useCartStore = defineStore('cart', { ... })
export const useProductStore = defineStore('product', { ... })

// 避免:不清晰的命名
export const useData = defineStore('data', { ... }) // 不好

2. Store文件组织

stores/
├── index.ts          # 导出所有stores
├── user.ts           # 用户相关状态
├── cart.ts           # 购物车状态
├── products.ts       # 产品状态
└── session.ts        # 会话状态

3. 类型安全

typescript
// types/store.ts
export interface User {
  id: string
  name: string
  email: string
  role: 'admin' | 'user' | 'guest'
}

export interface CartItem {
  id: string
  name: string
  price: number
  quantity: number
}

// stores/user.ts
import type { User } from '@/types/store'

export const useUserStore = defineStore('user', {
  state: (): { user: User | null; loading: boolean } => ({
    user: null,
    loading: false
  })
})

4. 错误处理

typescript
export const useRobustStore = defineStore('robust', {
  state: () => ({
    data: null as Data[] | null,
    loading: false,
    error: null as string | null,
    lastFetchTime: null as number | null
  }),
  
  actions: {
    async fetchData(force = false) {
      // 避免重复请求
      if (this.loading && !force) return
      
      // 检查缓存
      if (!force && this.lastFetchTime && 
          Date.now() - this.lastFetchTime < 5 * 60 * 1000) { // 5分钟缓存
        return
      }
      
      this.loading = true
      this.error = null
      
      try {
        const response = await fetch('/api/data')
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`)
        }
        this.data = await response.json()
        this.lastFetchTime = Date.now()
      } catch (error) {
        this.error = (error as Error).message
        console.error('Failed to fetch data:', error)
      } finally {
        this.loading = false
      }
    }
  }
})

模块化Store

Store模块化组织

typescript
// stores/modules/auth.ts
import { defineStore } from 'pinia'

interface AuthState {
  user: User | null
  token: string | null
  refreshToken: string | null
  loading: boolean
  error: string | null
}

export const useAuthStore = defineStore('auth', {
  state: (): AuthState => ({
    user: null,
    token: localStorage.getItem('token'),
    refreshToken: localStorage.getItem('refreshToken'),
    loading: false,
    error: null
  }),
  
  getters: {
    isAuthenticated: (state) => !!state.token,
    hasRefreshToken: (state) => !!state.refreshToken
  },
  
  actions: {
    async login(credentials: LoginCredentials) {
      this.loading = true
      this.error = null
      
      try {
        const response = await fetch('/api/auth/login', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(credentials)
        })
        
        if (!response.ok) {
          throw new Error('Invalid credentials')
        }
        
        const { user, token, refreshToken } = await response.json()
        
        this.user = user
        this.token = token
        this.refreshToken = refreshToken
        
        localStorage.setItem('token', token)
        localStorage.setItem('refreshToken', refreshToken)
      } catch (error) {
        this.error = (error as Error).message
        throw error
      } finally {
        this.loading = false
      }
    },
    
    async logout() {
      this.user = null
      this.token = null
      this.refreshToken = null
      
      localStorage.removeItem('token')
      localStorage.removeItem('refreshToken')
    }
  }
})

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

interface LoginCredentials {
  email: string
  password: string
}

总结

Store定义是Pinia的核心概念,通过本章的学习,你应该掌握:

  1. Store的基本结构:State、Getters、Actions
  2. 两种定义语法:选项式和组合式
  3. State的定义和初始化
  4. Getters的高级用法:带参数的Getters
  5. Actions的实现:同步和异步操作
  6. Store之间的交互
  7. 最佳实践:命名、类型安全、错误处理

正确地定义Store是构建可维护应用的关键,确保遵循这些最佳实践来组织你的状态管理逻辑。