Appearance
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的核心概念,通过本章的学习,你应该掌握:
- Store的基本结构:State、Getters、Actions
- 两种定义语法:选项式和组合式
- State的定义和初始化
- Getters的高级用法:带参数的Getters
- Actions的实现:同步和异步操作
- Store之间的交互
- 最佳实践:命名、类型安全、错误处理
正确地定义Store是构建可维护应用的关键,确保遵循这些最佳实践来组织你的状态管理逻辑。