Skip to content
On this page

Pinia TypeScript集成

Pinia与TypeScript的集成非常优秀,提供了完整的类型安全支持。本章将详细介绍如何在Pinia中充分利用TypeScript的类型系统,确保状态管理的类型安全和开发体验。

TypeScript基础集成

基础类型定义

typescript
import { defineStore } from 'pinia'

// 定义类型接口
interface User {
  id: string
  name: string
  email: string
  role: 'admin' | 'user' | 'moderator'
  createdAt: Date
}

interface Preferences {
  theme: 'light' | 'dark'
  language: string
  notifications: boolean
}

// 定义Store状态类型
interface UserState {
  currentUser: User | null
  preferences: Preferences
  loading: boolean
  error: string | null
}

// 定义Store
export const useUserStore = defineStore('user', {
  // 状态定义 - TypeScript会自动推断类型
  state: (): UserState => ({
    currentUser: null,
    preferences: {
      theme: 'light',
      language: 'en',
      notifications: true
    },
    loading: false,
    error: null
  }),

  // Getter类型定义
  getters: {
    // 自动推断返回类型
    isAuthenticated: (state): boolean => state.currentUser !== null,
    
    // 复杂计算类型的getter
    userProfile: (state): (Pick<User, 'id' | 'name' | 'email'> & { isOnline: boolean }) | null => {
      if (!state.currentUser) return null
      return {
        id: state.currentUser.id,
        name: state.currentUser.name,
        email: state.currentUser.email,
        isOnline: true // 简化的在线状态
      }
    },
    
    // 带参数的getter - 返回函数
    getUserById: (state) => {
      return (id: string): User | undefined => {
        // 这里需要扩展状态以包含用户列表
        return state.currentUser?.id === id ? state.currentUser : undefined
      }
    }
  },

  // Action类型定义
  actions: {
    // 同步action
    setCurrentUser(user: User) {
      this.currentUser = user
      this.error = null
    },

    // 异步action
    async login(credentials: { email: string; password: string }): Promise<void> {
      this.loading = true
      this.error = null

      try {
        // 模拟API调用
        const response = await fetch('/api/auth/login', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(credentials)
        })

        if (!response.ok) throw new Error('Login failed')

        const userData: User = await response.json()
        this.setCurrentUser(userData)
      } catch (error) {
        this.error = (error as Error).message
        throw error
      } finally {
        this.loading = false
      }
    },

    // 更新偏好设置
    async updatePreferences(updates: Partial<Preferences>) {
      this.preferences = { ...this.preferences, ...updates }
      
      // 保存到后端
      try {
        await fetch('/api/preferences', {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(this.preferences)
        })
      } catch (error) {
        console.error('Failed to update preferences:', error)
        // 恢复到之前的设置
        this.preferences = { ...this.preferences, ...updates }
      }
    },

    // 登出
    logout() {
      this.currentUser = null
      this.loading = false
      this.error = null
    }
  }
})

在组件中使用类型安全的Store

vue
<template>
  <div class="user-profile">
    <div v-if="userStore.loading" class="loading">
      Loading...
    </div>
    
    <div v-else-if="userStore.error" class="error">
      {{ userStore.error }}
    </div>
    
    <div v-else-if="userStore.isAuthenticated" class="profile">
      <h2>Welcome, {{ userStore.currentUser?.name }}!</h2>
      <p>Email: {{ userStore.currentUser?.email }}</p>
      <p>Role: {{ userStore.currentUser?.role }}</p>
      
      <div class="preferences">
        <h3>Preferences</h3>
        <div>
          <label>
            Theme:
            <select 
              :value="userStore.preferences.theme" 
              @change="handleThemeChange"
            >
              <option value="light">Light</option>
              <option value="dark">Dark</option>
            </select>
          </label>
        </div>
        <div>
          <label>
            Language:
            <input 
              type="text" 
              :value="userStore.preferences.language"
              @input="handleLanguageChange"
            />
          </label>
        </div>
        <div>
          <label>
            <input 
              type="checkbox" 
              :checked="userStore.preferences.notifications"
              @change="handleNotificationToggle"
            />
            Enable Notifications
          </label>
        </div>
      </div>
      
      <button @click="userStore.logout()" class="logout-btn">
        Logout
      </button>
    </div>
    
    <div v-else class="login">
      <h2>Login</h2>
      <form @submit.prevent="handleLogin">
        <div>
          <input 
            v-model="loginForm.email" 
            type="email" 
            placeholder="Email" 
            required
          />
        </div>
        <div>
          <input 
            v-model="loginForm.password" 
            type="password" 
            placeholder="Password" 
            required
          />
        </div>
        <button type="submit" :disabled="userStore.loading">
          {{ userStore.loading ? 'Logging in...' : 'Login' }}
        </button>
      </form>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useUserStore, type User } from '@/stores/user'

// 类型化的Store实例
const userStore = useUserStore()

// 类型化的表单数据
const loginForm = reactive({
  email: '',
  password: ''
})

// 类型化的事件处理器
const handleLogin = async () => {
  try {
    await userStore.login(loginForm)
    // 登录成功后的处理
    console.log('Login successful', userStore.currentUser)
  } catch (error) {
    console.error('Login failed:', error)
  }
}

const handleThemeChange = (event: Event) => {
  const target = event.target as HTMLSelectElement
  userStore.updatePreferences({ theme: target.value as 'light' | 'dark' })
}

const handleLanguageChange = (event: Event) => {
  const target = event.target as HTMLInputElement
  userStore.updatePreferences({ language: target.value })
}

const handleNotificationToggle = (event: Event) => {
  const target = event.target as HTMLInputElement
  userStore.updatePreferences({ notifications: target.checked })
}
</script>

<style scoped>
.user-profile {
  max-width: 600px;
  margin: 0 auto;
  padding: 2rem;
}

.loading, .error {
  text-align: center;
  padding: 2rem;
}

.error {
  color: #d32f2f;
}

.profile, .login {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 2rem;
}

.preferences {
  margin: 2rem 0;
  padding: 1rem;
  background-color: #f5f5f5;
  border-radius: 4px;
}

.preferences div {
  margin-bottom: 1rem;
}

.logout-btn {
  background-color: #f44336;
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 4px;
  cursor: pointer;
}
</style>

高级类型模式

泛型Store定义

typescript
// stores/generic.ts
export interface Identifiable {
  id: string
}

export interface Timestamped {
  createdAt: Date
  updatedAt: Date
}

// 泛型Store定义
export function createEntityStore<T extends Identifiable & Timestamped>(
  storeId: string,
  initialState: T[] = []
) {
  return defineStore(storeId, {
    state: () => ({
      entities: [] as T[],
      loading: false,
      error: null as string | null,
      selectedId: null as string | null
    }),

    getters: {
      // 根据ID获取实体
      getById: (state) => {
        return (id: string): T | undefined => {
          return state.entities.find(entity => entity.id === id)
        }
      },

      // 获取所有实体
      allEntities: (state): readonly T[] => state.entities,

      // 获取选中的实体
      selectedEntity: (state): T | null => {
        if (!state.selectedId) return null
        return state.entities.find(entity => entity.id === state.selectedId) || null
      },

      // 实体数量
      count: (state): number => state.entities.length,

      // 是否为空
      isEmpty: (state): boolean => state.entities.length === 0
    },

    actions: {
      // 添加实体
      addEntity(entity: T) {
        // 检查是否已存在
        const existingIndex = this.entities.findIndex(e => e.id === entity.id)
        if (existingIndex !== -1) {
          this.entities[existingIndex] = { ...this.entities[existingIndex], ...entity }
        } else {
          this.entities.push(entity)
        }
      },

      // 更新实体
      updateEntity(id: string, updates: Partial<T>) {
        const index = this.entities.findIndex(entity => entity.id === id)
        if (index !== -1) {
          this.entities[index] = { ...this.entities[index], ...updates } as T
        }
      },

      // 删除实体
      removeEntity(id: string) {
        this.entities = this.entities.filter(entity => entity.id !== id)
        if (this.selectedId === id) {
          this.selectedId = null
        }
      },

      // 选择实体
      selectEntity(id: string) {
        this.selectedId = id
      },

      // 清除选择
      clearSelection() {
        this.selectedId = null
      },

      // 批量添加实体
      addMultiple(entities: T[]) {
        entities.forEach(entity => this.addEntity(entity))
      },

      // 设置所有实体
      setAll(entities: T[]) {
        this.entities = [...entities]
      },

      // 异步加载实体
      async fetchEntities(fetcher: () => Promise<T[]>) {
        this.loading = true
        this.error = null

        try {
          const entities = await fetcher()
          this.setAll(entities)
        } catch (error) {
          this.error = (error as Error).message
          throw error
        } finally {
          this.loading = false
        }
      }
    }
  })
}

// 使用泛型Store
interface Product extends Identifiable, Timestamped {
  name: string
  price: number
  category: string
  inStock: boolean
}

interface Order extends Identifiable, Timestamped {
  userId: string
  items: OrderItem[]
  total: number
  status: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled'
}

interface OrderItem {
  productId: string
  quantity: number
  price: number
}

// 创建具体的Store实例
export const useProductStore = createEntityStore<Product>('products')
export const useOrderStore = createEntityStore<Order>('orders')

条件类型和高级类型操作

typescript
// types/conditional.ts
// 条件类型 - 根据某个属性的存在来决定类型
type WithOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
type WithRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>

// 工具类型 - 提取Store的State类型
type StoreState<TStore> = TStore extends { $state: infer TState } ? TState : never

// 工具类型 - 提取Store的Getters类型
type StoreGetters<TStore> = TStore extends { 
  [K: string]: any; 
  $state: any 
} ? Pick<TStore, { [K in keyof TStore]: TStore[K] extends Function ? never : K }[keyof TStore]> : never

// 工具类型 - 提取Store的Actions类型
type StoreActions<TStore> = TStore extends { 
  [K: string]: infer TValue 
} ? { [K in keyof TStore as TValue extends Function ? K : never]: TValue } : never

// 创建类型安全的Store工厂
interface StoreConfig<State, Getters, Actions> {
  state: () => State
  getters?: Getters
  actions?: Actions
}

// 类型化的Store创建函数
function createTypedStore<
  S extends Record<string, any>,
  G extends Record<string, any>,
  A extends Record<string, any>
>(
  id: string,
  config: {
    state: () => S
    getters?: G
    actions?: A
  }
) {
  return defineStore(id, {
    state: config.state,
    getters: config.getters || {},
    actions: config.actions || {}
  }) as () => {
    $state: S
  } & S & G & A
}

// 使用示例
interface CartItem {
  id: string
  name: string
  price: number
  quantity: number
}

interface CartState {
  items: CartItem[]
  discount: number
  shipping: number
}

interface CartGetters {
  readonly total: number
  readonly itemCount: number
  readonly discountedTotal: number
}

interface CartActions {
  addItem(item: Omit<CartItem, 'quantity'>): void
  removeItem(id: string): void
  updateQuantity(id: string, quantity: number): void
  applyDiscount(percent: number): void
  clearCart(): void
}

export const useCartStore = createTypedStore<CartState, CartGetters, CartActions>('cart', {
  state: () => ({
    items: [],
    discount: 0,
    shipping: 0
  }),
  
  getters: {
    get total() {
      return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
    },
    
    get itemCount() {
      return this.items.reduce((count, item) => count + item.quantity, 0)
    },
    
    get discountedTotal() {
      return Math.max(0, (this.total - this.discount) + this.shipping)
    }
  },
  
  actions: {
    addItem(item) {
      const existingItem = this.items.find(i => i.id === item.id)
      if (existingItem) {
        existingItem.quantity += 1
      } else {
        this.items.push({ ...item, quantity: 1 })
      }
    },
    
    removeItem(id) {
      this.items = this.items.filter(item => item.id !== id)
    },
    
    updateQuantity(id, quantity) {
      if (quantity <= 0) {
        this.removeItem(id)
        return
      }
      
      const item = this.items.find(i => i.id === id)
      if (item) {
        item.quantity = quantity
      }
    },
    
    applyDiscount(percent) {
      this.discount = Math.min(this.total * (percent / 100), this.total)
    },
    
    clearCart() {
      this.items = []
      this.discount = 0
    }
  }
})

类型安全的Store模式

Store组合类型

typescript
// stores/composables.ts
import { computed, ComputedRef } from 'vue'
import { storeToRefs } from 'pinia'

// 类型安全的storeToRefs增强版
export function typedStoreToRefs<TStore>(store: TStore) {
  const refs = storeToRefs(store)
  return refs as {
    [K in keyof TStore as TStore[K] extends ComputedRef<any> ? K : never]: TStore[K]
  }
}

// Store选择器类型
type Selector<TState, TResult> = (state: TState) => TResult

export function createSelector<TState, TResult>(
  selector: Selector<TState, TResult>
): Selector<TState, TResult> {
  return selector
}

// 带类型安全的选择器Hook
export function useStoreSelector<TStore, TResult>(
  store: TStore,
  selector: (state: TStore extends { $state: infer TState } ? TState : never) => TResult
): ComputedRef<TResult> {
  return computed(() => selector(store.$state))
}

// 使用示例
interface User {
  id: string
  name: string
  email: string
  role: string
}

interface AppState {
  user: User | null
  theme: 'light' | 'dark'
  language: string
}

export const useAppStore = defineStore('app', {
  state: (): AppState => ({
    user: null,
    theme: 'light',
    language: 'en'
  }),
  
  getters: {
    isLoggedIn: (state) => state.user !== null,
    userName: (state) => state.user?.name || 'Guest',
    userEmail: (state) => state.user?.email
  },
  
  actions: {
    setUser(user: User) {
      this.user = user
    },
    
    setTheme(theme: 'light' | 'dark') {
      this.theme = theme
    },
    
    setLanguage(language: string) {
      this.language = language
    }
  }
})

// 在组件中使用类型安全的选择器
import { useAppStore } from '@/stores/app'

// 组件中使用
const appStore = useAppStore()

// 类型安全的状态引用
const { user, theme, language } = typedStoreToRefs(appStore)

// 类型安全的选择器
const userDisplayName = useStoreSelector(
  appStore,
  (state) => state.user ? `${state.user.name} (${state.user.email})` : 'Guest'
)

const isLoggedIn = useStoreSelector(
  appStore,
  (state) => state.user !== null
)

类型化的Store中间件

typescript
// stores/middlewares.ts
interface MiddlewareContext<State, Getters, Actions> {
  state: State
  getters: Getters
  actions: Actions
  next: (payload: any) => any
}

type Middleware<State, Getters, Actions> = (
  context: MiddlewareContext<State, Getters, Actions>
) => any

// 类型安全的Store增强器
export function createStoreEnhancer<S, G, A>(
  storeFactory: () => any,
  middlewares: Middleware<S, G, A>[] = []
) {
  return () => {
    const store = storeFactory()
    
    // 应用中间件
    middlewares.forEach(middleware => {
      middleware({
        state: store.$state,
        getters: store,
        actions: store,
        next: (payload) => payload
      })
    })
    
    return store
  }
}

// 日志中间件类型定义
interface LogMiddlewareConfig {
  logActions?: boolean
  logMutations?: boolean
  filter?: (mutation: any) => boolean
}

export function createLogMiddleware<S, G, A>(config: LogMiddlewareConfig = {}) {
  const { logActions = true, logMutations = true, filter } = config
  
  return (context: MiddlewareContext<S, G, A>) => {
    // 如果需要,可以在这里拦截和处理store行为
    return context.next(context)
  }
}

// 持久化中间件
interface PersistenceMiddlewareConfig {
  key: string
  storage?: Storage
}

export function createPersistenceMiddleware<S>(config: PersistenceMiddlewareConfig) {
  const { key, storage = localStorage } = config
  
  return (context: MiddlewareContext<S, any, any>) => {
    // 恢复状态
    const savedState = storage.getItem(key)
    if (savedState) {
      try {
        const parsedState = JSON.parse(savedState)
        // 这里需要根据具体实现来恢复状态
        console.log('Restored state from', key)
      } catch (error) {
        console.error('Failed to restore state:', error)
      }
    }
    
    // 监听状态变化并保存
    const unsubscribe = context.actions.$subscribe((mutation, state) => {
      try {
        storage.setItem(key, JSON.stringify(state))
      } catch (error) {
        console.error('Failed to persist state:', error)
      }
    })
    
    // 返回清理函数
    return () => unsubscribe()
  }
}

类型推断优化

智能类型推断

typescript
// stores/inference.ts
// 使用映射类型来自动推断getter类型
type GetterDefinitions<State> = {
  [K: string]: (state: State) => any
}

type InferGetters<G extends GetterDefinitions<any>> = {
  [K in keyof G]: G[K] extends (state: any) => infer R ? R : never
}

// 使用条件类型来区分同步和异步actions
type SyncAction = (...args: any[]) => any
type AsyncAction = (...args: any[]) => Promise<any>

type ActionDefinitions = {
  [K: string]: SyncAction | AsyncAction
}

type InferActions<A extends ActionDefinitions> = {
  [K in keyof A]: A[K]
}

// 高级类型推断示例
interface Todo {
  id: string
  text: string
  completed: boolean
  createdAt: Date
}

interface TodoFilters {
  status: 'all' | 'active' | 'completed'
  search: string
}

// 完整的Todo Store类型定义
interface TodoStoreState {
  todos: Todo[]
  filters: TodoFilters
  loading: boolean
  error: string | null
}

interface TodoStoreGetters {
  readonly filteredTodos: Todo[]
  readonly activeCount: number
  readonly completedCount: number
  readonly allCompleted: boolean
  getTodoById: (id: string) => Todo | undefined
}

interface TodoStoreActions {
  addTodo(text: string): void
  removeTodo(id: string): void
  toggleTodo(id: string): void
  setFilter(filter: Partial<TodoFilters>): void
  clearCompleted(): void
  fetchTodos(): Promise<void>
  updateTodo(id: string, updates: Partial<Todo>): void
}

// 使用类型推断创建Store
export const useTodoStore = defineStore('todos', {
  state: (): TodoStoreState => ({
    todos: [],
    filters: {
      status: 'all',
      search: ''
    },
    loading: false,
    error: null
  }),
  
  getters: {
    // TypeScript会自动推断返回类型
    filteredTodos: (state) => {
      let result = state.todos
      
      // 根据状态过滤
      if (state.filters.status === 'active') {
        result = result.filter(todo => !todo.completed)
      } else if (state.filters.status === 'completed') {
        result = result.filter(todo => todo.completed)
      }
      
      // 根据搜索词过滤
      if (state.filters.search) {
        const searchLower = state.filters.search.toLowerCase()
        result = result.filter(todo => 
          todo.text.toLowerCase().includes(searchLower)
        )
      }
      
      return result
    },
    
    activeCount: (state) => state.todos.filter(todo => !todo.completed).length,
    
    completedCount: (state) => state.todos.filter(todo => todo.completed).length,
    
    allCompleted: (state) => state.todos.length > 0 && 
                       state.todos.every(todo => todo.completed),
    
    getTodoById: (state) => {
      return (id: string) => state.todos.find(todo => todo.id === id)
    }
  },
  
  actions: {
    addTodo(text: string) {
      if (!text.trim()) return
      
      const newTodo: Todo = {
        id: Date.now().toString(),
        text: text.trim(),
        completed: false,
        createdAt: new Date()
      }
      
      this.todos.push(newTodo)
    },
    
    removeTodo(id: string) {
      this.todos = this.todos.filter(todo => todo.id !== id)
    },
    
    toggleTodo(id: string) {
      const todo = this.todos.find(t => t.id === id)
      if (todo) {
        todo.completed = !todo.completed
      }
    },
    
    setFilter(filter: Partial<TodoFilters>) {
      this.filters = { ...this.filters, ...filter }
    },
    
    clearCompleted() {
      this.todos = this.todos.filter(todo => !todo.completed)
    },
    
    async fetchTodos() {
      this.loading = true
      this.error = null
      
      try {
        const response = await fetch('/api/todos')
        if (!response.ok) throw new Error('Failed to fetch todos')
        
        const todos: Todo[] = await response.json()
        this.todos = todos
      } catch (error) {
        this.error = (error as Error).message
        throw error
      } finally {
        this.loading = false
      }
    },
    
    updateTodo(id: string, updates: Partial<Todo>) {
      const todoIndex = this.todos.findIndex(t => t.id === id)
      if (todoIndex !== -1) {
        this.todos[todoIndex] = { ...this.todos[todoIndex], ...updates }
      }
    }
  }
}) satisfies () => {
  $state: TodoStoreState
} & TodoStoreState & TodoStoreGetters & TodoStoreActions

类型安全的Store测试

类型化的测试辅助函数

typescript
// __tests__/typescript.spec.ts
import { setActivePinia, createPinia } from 'pinia'
import { useUserStore, type User } from '@/stores/user'
import { useTodoStore } from '@/stores/inference'
import { vi, describe, it, beforeEach, expect } from 'vitest'

describe('TypeScript Integration Tests', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })

  it('should provide full type safety for user store', () => {
    const userStore = useUserStore()
    
    // 状态类型检查
    expect(typeof userStore.loading).toBe('boolean')
    expect(userStore.error).toBeNull()
    
    // Action参数类型检查
    const mockUser: User = {
      id: '1',
      name: 'John Doe',
      email: 'john@example.com',
      role: 'user',
      createdAt: new Date()
    }
    
    userStore.setCurrentUser(mockUser)
    
    // Getter返回类型检查
    expect(userStore.isAuthenticated).toBe(true)
    expect(userStore.currentUser?.name).toBe('John Doe')
    
    // 类型安全的action调用
    userStore.updatePreferences({ theme: 'dark' })
    
    expect(userStore.preferences.theme).toBe('dark')
  })

  it('should maintain type safety in todo store', () => {
    const todoStore = useTodoStore()
    
    // 状态类型检查
    expect(Array.isArray(todoStore.todos)).toBe(true)
    expect(typeof todoStore.loading).toBe('boolean')
    
    // Action调用类型检查
    todoStore.addTodo('Test todo')
    
    // Getter类型检查
    expect(todoStore.activeCount).toBe(1)
    expect(todoStore.allCompleted).toBe(false)
    
    // 带参数的getter类型检查
    const todo = todoStore.getTodoById(todoStore.todos[0].id)
    expect(todo).toBeDefined()
    expect(todo?.text).toBe('Test todo')
  })

  it('should handle async actions with proper typing', async () => {
    const userStore = useUserStore()
    
    // Mock fetch
    global.fetch = vi.fn(() =>
      Promise.resolve({
        ok: true,
        json: () => Promise.resolve({
          id: '1',
          name: 'Jane Doe',
          email: 'jane@example.com',
          role: 'admin',
          createdAt: new Date()
        })
      } as Response)
    ) as any

    // 类型安全的异步action调用
    await expect(userStore.login({
      email: 'jane@example.com',
      password: 'password'
    })).resolves.not.toThrow()
    
    expect(userStore.currentUser?.name).toBe('Jane Doe')
    expect(userStore.isAuthenticated).toBe(true)
  })

  it('should enforce type safety for partial updates', () => {
    const userStore = useUserStore()
    
    const mockUser: User = {
      id: '1',
      name: 'John Doe',
      email: 'john@example.com',
      role: 'user',
      createdAt: new Date()
    }
    
    userStore.setCurrentUser(mockUser)
    
    // 类型安全的属性访问
    const name = userStore.currentUser?.name
    expect(name).toBe('John Doe')
    
    // 类型安全的可选链
    const nonExistent = userStore.currentUser?.nonExistentProperty
    // 这应该是undefined,TypeScript会正确识别
    
    // 类型安全的partial更新
    userStore.updatePreferences({ 
      theme: 'dark', // 正确的枚举值
      // notifications: true // 可选属性
      // language不会被要求,因为它有默认值
    })
  })
})

// 类型测试 - 确保类型定义正确
describe('Type Definitions', () => {
  it('should enforce correct types at compile time', () => {
    const userStore = useUserStore()
    
    // 以下代码应该在TypeScript检查中报错:
    // userStore.updatePreferences({ theme: 'invalid_theme' }) // 错误:不是有效主题
    // userStore.currentUser = {} // 错误:缺少必需属性
    
    // 正确的类型使用
    userStore.updatePreferences({ theme: 'dark' })
    userStore.setCurrentUser({
      id: '1',
      name: 'Test User',
      email: 'test@example.com',
      role: 'user',
      createdAt: new Date()
    })
    
    expect(userStore.currentUser?.role).toBe('user')
  })
})

高级类型技巧

条件类型和分布式条件类型

typescript
// types/advanced.ts
// 提取所有函数类型的键
type FunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? K : never
}[keyof T]

// 提取所有非函数类型的键
type NonFunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? never : K
}[keyof T]

// 分离函数和非函数属性
type Functions<T> = Pick<T, FunctionPropertyNames<T>>
type NonFunctions<T> = Pick<T, NonFunctionPropertyNames<T>>

// 在Store中使用
interface AdvancedStoreState {
  count: number
  name: string
  items: string[]
}

interface AdvancedStoreActions {
  increment(): void
  decrement(): void
  setName(name: string): void
  addItem(item: string): void
  reset(): void
}

interface AdvancedStoreGetters {
  doubleCount: number
  hasItems: boolean
  upperCaseName: string
}

// 完整的Store类型
type AdvancedStoreType = AdvancedStoreState & AdvancedStoreActions & AdvancedStoreGetters

// 提取各部分
type StateProps = NonFunctions<AdvancedStoreType>
type ActionProps = Functions<AdvancedStoreType>

// 高级类型操作
type MakeOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
type MakeRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>

// 创建类型安全的Store配置
interface StoreConfigBase<S, G, A> {
  state: () => S
  getters?: G
  actions?: A
}

// 类型安全的Store创建器
function createStrictStore<S, G, A>(id: string, config: StoreConfigBase<S, G, A>) {
  return defineStore(id, config) as () => S & G & A
}

// 使用示例
export const useAdvancedStore = createStrictStore<
  AdvancedStoreState,
  AdvancedStoreGetters,
  AdvancedStoreActions
>('advanced', {
  state: () => ({
    count: 0,
    name: '',
    items: []
  }),
  
  getters: {
    get doubleCount() {
      return this.count * 2
    },
    
    get hasItems() {
      return this.items.length > 0
    },
    
    get upperCaseName() {
      return this.name.toUpperCase()
    }
  },
  
  actions: {
    increment() {
      this.count++
    },
    
    decrement() {
      this.count--
    },
    
    setName(name: string) {
      this.name = name
    },
    
    addItem(item: string) {
      this.items.push(item)
    },
    
    reset() {
      this.count = 0
      this.name = ''
      this.items = []
    }
  }
})

总结

Pinia与TypeScript的集成提供了完整的类型安全,通过本章的学习,你应该掌握:

  1. 基础类型集成:如何为Store定义完整的类型
  2. 高级类型模式:泛型Store、条件类型等高级技巧
  3. 类型安全模式:Store组合、选择器等模式的类型化实现
  4. 类型推断优化:利用TypeScript的智能推断能力
  5. 测试类型安全:确保类型定义的正确性
  6. 高级类型技巧:分布式条件类型等高级特性

TypeScript的类型安全能够帮助我们在编译时发现潜在错误,提供更好的开发体验和代码可维护性。