Pinia TypeScript 集成 #

概述 #

Pinia 从设计之初就考虑了 TypeScript 支持,提供了开箱即用的类型推断。本节将介绍如何在 Pinia 中充分利用 TypeScript。

基本类型定义 #

State 类型 #

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

interface User {
  id: number
  name: string
  email: string
  role: 'admin' | 'user'
}

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

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    user: null,
    token: null,
    loading: false,
    error: null
  })
})

Getters 类型 #

ts
interface UserGetters {
  isAuthenticated: boolean
  displayName: string
  isAdmin: boolean
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({ /* ... */ }),
  
  getters: {
    // 自动推断类型
    isAuthenticated: (state): boolean => !!state.token,
    
    // 显式指定返回类型
    displayName(): string {
      return this.user?.name || 'Guest'
    },
    
    // 复杂类型
    isAdmin(): boolean {
      return this.user?.role === 'admin'
    }
  }
})

Actions 类型 #

ts
interface UserActions {
  login: (email: string, password: string) => Promise<boolean>
  logout: () => void
  updateProfile: (data: Partial<User>) => void
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({ /* ... */ }),
  
  actions: {
    async login(email: string, password: string): Promise<boolean> {
      try {
        const response = await api.login({ email, password })
        this.user = response.user
        this.token = response.token
        return true
      } catch {
        return false
      }
    },
    
    logout(): void {
      this.user = null
      this.token = null
    },
    
    updateProfile(data: Partial<User>): void {
      if (this.user) {
        Object.assign(this.user, data)
      }
    }
  }
})

完整类型定义 #

使用泛型 #

ts
// stores/types.ts
export interface StoreState<T> {
  data: T | null
  loading: boolean
  error: string | null
}

export interface StoreGetters<T> {
  hasData: boolean
  isLoading: boolean
  hasError: boolean
}

export interface StoreActions<T> {
  fetch: () => Promise<void>
  reset: () => void
}
ts
// stores/product.ts
import { defineStore } from 'pinia'

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

interface ProductState extends StoreState<Product[]> {
  selectedProduct: Product | null
}

export const useProductStore = defineStore('product', {
  state: (): ProductState => ({
    data: null,
    loading: false,
    error: null,
    selectedProduct: null
  }),
  
  getters: {
    hasData: (state): boolean => state.data !== null && state.data.length > 0,
    isLoading: (state): boolean => state.loading,
    hasError: (state): boolean => state.error !== null
  },
  
  actions: {
    async fetch(): Promise<void> {
      this.loading = true
      try {
        const response = await fetch('/api/products')
        this.data = await response.json()
      } catch (e) {
        this.error = e instanceof Error ? e.message : 'Unknown error'
      } finally {
        this.loading = false
      }
    },
    
    reset(): void {
      this.data = null
      this.loading = false
      this.error = null
      this.selectedProduct = null
    }
  }
})

Setup Store 类型 #

ref 类型 #

ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  // ref 自动推断类型
  const count = ref(0)
  
  // 显式类型
  const name = ref<string>('')
  
  // 复杂类型
  const user = ref<User | null>(null)
  
  // computed 自动推断
  const double = computed(() => count.value * 2)
  
  // 显式 computed 类型
  const isAdmin = computed<boolean>(() => user.value?.role === 'admin')
  
  return { count, name, user, double, isAdmin }
})

函数类型 #

ts
export const useUserStore = defineStore('user', () => {
  const user = ref<User | null>(null)
  
  // 函数参数和返回值类型
  async function login(email: string, password: string): Promise<boolean> {
    try {
      const response = await api.login({ email, password })
      user.value = response.user
      return true
    } catch {
      return false
    }
  }
  
  function updateProfile(data: Partial<User>): void {
    if (user.value) {
      Object.assign(user.value, data)
    }
  }
  
  return { user, login, updateProfile }
})

扩展 Pinia 类型 #

自定义 Store 属性 #

ts
// types/pinia.d.ts
import 'pinia'
import type { Router } from 'vue-router'

declare module 'pinia' {
  interface PiniaCustomProperties {
    // 添加 $router 属性
    $router: Router
    
    // 添加 $api 方法
    $api: {
      get: <T>(url: string) => Promise<T>
      post: <T>(url: string, data: any) => Promise<T>
    }
  }
}
ts
// main.ts
import { createPinia } from 'pinia'
import { router } from './router'

const pinia = createPinia()

pinia.use(({ store }) => {
  store.$router = router
  
  store.$api = {
    async get<T>(url: string): Promise<T> {
      const response = await fetch(url)
      return response.json()
    },
    async post<T>(url: string, data: any): Promise<T> {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      })
      return response.json()
    }
  }
})

自定义 State 属性 #

ts
// types/pinia.d.ts
declare module 'pinia' {
  interface PiniaCustomStateProperties {
    // 添加版本号
    _version: number
  }
}

类型工具 #

提取 Store 类型 #

ts
// utils/store.ts
import type { Store, StoreState, StoreGetters, StoreActions } from 'pinia'

// 提取 State 类型
export type ExtractState<S> = S extends Store<string, infer State, any, any>
  ? State
  : never

// 提取 Getters 类型
export type ExtractGetters<S> = S extends Store<string, any, infer Getters, any>
  ? Getters
  : never

// 提取 Actions 类型
export type ExtractActions<S> = S extends Store<string, any, any, infer Actions>
  ? Actions
  : never
ts
// 使用
import { useUserStore } from '@/stores/user'

type UserState = ExtractState<ReturnType<typeof useUserStore>>
type UserGetters = ExtractGetters<ReturnType<typeof useUserStore>>
type UserActions = ExtractActions<ReturnType<typeof useUserStore>>

创建类型安全的 Store #

ts
// utils/defineTypedStore.ts
import { defineStore } from 'pinia'

interface StoreDefinition<
  Id extends string,
  S extends Record<string, any>,
  G extends Record<string, any>,
  A extends Record<string, any>
> {
  state: () => S
  getters: G & ThisType<S & G>
  actions: A & ThisType<S & G & A>
}

export function defineTypedStore<
  Id extends string,
  S extends Record<string, any>,
  G extends Record<string, any>,
  A extends Record<string, any>
>(
  id: Id,
  definition: StoreDefinition<Id, S, G, A>
) {
  return defineStore(id, definition)
}

实际示例 #

完整类型化的用户 Store #

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

// 类型定义
interface User {
  id: number
  name: string
  email: string
  role: 'admin' | 'user'
  avatar?: string
}

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

interface UserGetters {
  isAuthenticated: boolean
  displayName: string
  isAdmin: boolean
  avatarUrl: string
}

interface UserActions {
  login: (email: string, password: string) => Promise<boolean>
  logout: () => void
  fetchUser: () => Promise<void>
  updateProfile: (data: Partial<User>) => Promise<boolean>
}

// Store 定义
export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    user: null,
    token: localStorage.getItem('token'),
    loading: false,
    error: null
  }),
  
  getters: {
    isAuthenticated(): boolean {
      return !!this.token && !!this.user
    },
    
    displayName(): string {
      return this.user?.name || 'Guest'
    },
    
    isAdmin(): boolean {
      return this.user?.role === 'admin'
    },
    
    avatarUrl(): string {
      return this.user?.avatar || '/default-avatar.png'
    }
  },
  
  actions: {
    async login(email: string, password: string): Promise<boolean> {
      this.loading = true
      this.error = null
      
      try {
        const response = await fetch('/api/login', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ email, password })
        })
        
        if (!response.ok) {
          throw new Error('Login failed')
        }
        
        const data = await response.json()
        this.user = data.user
        this.token = data.token
        localStorage.setItem('token', data.token)
        
        return true
      } catch (e) {
        this.error = e instanceof Error ? e.message : 'Login failed'
        return false
      } finally {
        this.loading = false
      }
    },
    
    logout(): void {
      this.user = null
      this.token = null
      localStorage.removeItem('token')
    },
    
    async fetchUser(): Promise<void> {
      if (!this.token) return
      
      this.loading = true
      try {
        const response = await fetch('/api/user', {
          headers: { Authorization: `Bearer ${this.token}` }
        })
        this.user = await response.json()
      } catch {
        this.logout()
      } finally {
        this.loading = false
      }
    },
    
    async updateProfile(data: Partial<User>): Promise<boolean> {
      if (!this.user) return false
      
      this.loading = true
      try {
        const response = await fetch(`/api/user/${this.user.id}`, {
          method: 'PATCH',
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${this.token}`
          },
          body: JSON.stringify(data)
        })
        
        this.user = await response.json()
        return true
      } catch {
        return false
      } finally {
        this.loading = false
      }
    }
  }
})

// 导出类型供其他模块使用
export type { User, UserState, UserGetters, UserActions }

最佳实践 #

1. 始终定义 State 类型 #

ts
// 推荐
interface UserState {
  user: User | null
  loading: boolean
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    user: null,
    loading: false
  })
})

// 不推荐
export const useUserStore = defineStore('user', {
  state: () => ({
    user: null,  // 类型不明确
    loading: false
  })
})

2. 使用严格类型 #

ts
// 推荐:使用联合类型
role: 'admin' | 'user'

// 不推荐:使用 string
role: string

3. 导出类型 #

ts
// 导出类型供其他模块使用
export type { User, UserState }

4. 使用类型守卫 #

ts
function isUser(value: unknown): value is User {
  return typeof value === 'object' && value !== null && 'id' in value
}

// 使用
if (isUser(data)) {
  this.user = data
}

下一步 #

现在你已经掌握了 TypeScript 集成,接下来让我们学习性能优化。

最后更新:2026-03-28