返回列表 发布新帖
查看: 64|回复: 0

Pinia 与 TypeScript 集成指南

发表于 2025-9-26 15:01:37 | 查看全部 |阅读模式

这里或许是互联网从业者的最后一片净土,随客社区期待您的加入!

您需要 登录 才可以下载或查看,没有账号?立即注册

×
本帖最后由 zzz 于 2025-9-26 15:11 编辑

Pinia 是 Vue.js 的官方状态管理库,与 TypeScript 的集成非常出色。下面我将详细介绍如何将两者结合使用。

1.基础设置
  安装依赖
  1. npm install pinia
  2. npm install -D typescript @vue/tsconfig
复制代码
  创建 Pinia Store 并添加类型
  1. // stores/counter.ts
  2. import { defineStore } from 'pinia'

  3. // 定义状态接口
  4. interface CounterState {
  5.   count: number
  6.   name: string
  7. }

  8. export const useCounterStore = defineStore('counter', {
  9.   // 状态类型注解
  10.   state: (): CounterState => ({
  11.     count: 0,
  12.     name: 'Counter Store'
  13.   }),
  14.   
  15.   // Getter 类型推断
  16.   getters: {
  17.     doubleCount: (state) => state.count * 2,
  18.     // 明确返回类型
  19.     doubleCountPlusOne(): number {
  20.       return this.doubleCount + 1
  21.     },
  22.     // 使用参数并指定返回类型
  23.     getCountPlus: (state) => {
  24.       return (plus: number): number => state.count + plus
  25.     }
  26.   },
  27.   
  28.   actions: {
  29.     // Action 方法
  30.     increment() {
  31.       this.count++
  32.     },
  33.     // 带参数的 action
  34.     incrementBy(amount: number) {
  35.       this.count += amount
  36.     },
  37.     // 异步 action
  38.     async incrementAsync() {
  39.       const response = await fetch('/api/increment')
  40.       const data = await response.json()
  41.       this.count += data.amount
  42.     }
  43.   }
  44. })
复制代码
2. 组合式 API 风格的 Store
  1. // stores/user.ts
  2. import { defineStore } from 'pinia'
  3. import { ref, computed } from 'vue'

  4. interface User {
  5.   id: number
  6.   name: string
  7.   email: string
  8. }

  9. export const useUserStore = defineStore('user', () => {
  10.   // 状态
  11.   const user = ref<User | null>(null)
  12.   const isLoggedIn = ref(false)
  13.   
  14.   // Getter 等价物
  15.   const userName = computed(() => user.value?.name || 'Guest')
  16.   const userEmail = computed(() => user.value?.email || '')
  17.   
  18.   // Actions
  19.   function login(email: string, password: string) {
  20.     return new Promise<void>((resolve) => {
  21.       setTimeout(() => {
  22.         user.value = {
  23.           id: 1,
  24.           name: 'John Doe',
  25.           email: email
  26.         }
  27.         isLoggedIn.value = true
  28.         resolve()
  29.       }, 1000)
  30.     })
  31.   }
  32.   
  33.   function logout() {
  34.     user.value = null
  35.     isLoggedIn.value = false
  36.   }
  37.   
  38.   return {
  39.     user,
  40.     isLoggedIn,
  41.     userName,
  42.     userEmail,
  43.     login,
  44.     logout
  45.   }
  46. })
复制代码
3. 在组件中使用
  选项式API组件
  1. <template>
  2. <div>
  3. <h1>{{ store.name }}</h1>
  4. <p>Count: {{ store.count }}</p>
  5. <p>Double: {{ store.doubleCount }}</p>
  6. <button @click="store.increment()">Increment</button>
  7. </div>
  8. </template>

  9. <script lang="ts">
  10. import { defineComponent } from 'vue'
  11. import { useCounterStore } from '@/stores/counter'

  12. export default defineComponent({
  13. setup() {
  14. const store = useCounterStore()

  15. return {
  16. store
  17. }
  18. }
  19. })
  20. </script>
复制代码
  组合式API组件
  1. <template>
  2. <div>
  3. <h1>User: {{ userName }}</h1>
  4. <p>Email: {{ userEmail }}</p>
  5. <p v-if="isLoggedIn">Status: Logged In</p>
  6. <button @click="login('test@example.com', 'password')">Login</button>
  7. <button @click="logout">Logout</button>
  8. </div>
  9. </template>

  10. <script setup lang="ts">
  11. import { storeToRefs } from 'pinia'
  12. import { useUserStore } from '@/stores/user'

  13. const userStore = useUserStore()

  14. // 使用 storeToRefs 保持响应式
  15. const { userName, userEmail, isLoggedIn } = storeToRefs(userStore)

  16. // 直接解构 actions
  17. const { login, logout } = userStore
  18. </script>
复制代码
4. 高级类型技巧
  自定义类型 Store
  1. // stores/types.ts
  2. export interface Product {
  3. id: number
  4. name: string
  5. price: number
  6. category: string
  7. }

  8. export interface CartItem {
  9. product: Product
  10. quantity: number
  11. }

  12. export interface CartState {
  13. items: CartItem[]
  14. total: number
  15. }
复制代码
  使用复杂类型的 Store  
  1. // stores/cart.ts
  2. import { defineStore } from 'pinia'
  3. import type { CartState, CartItem, Product } from './types'

  4. export const useCartStore = defineStore('cart', {
  5.   state: (): CartState => ({
  6.     items: [],
  7.     total: 0
  8.   }),
  9.   
  10.   getters: {
  11.     itemCount: (state) => state.items.reduce((sum, item) => sum + item.quantity, 0),
  12.     getItemByProductId: (state) => {
  13.       return (productId: number): CartItem | undefined =>
  14.         state.items.find(item => item.product.id === productId)
  15.     }
  16.   },
  17.   
  18.   actions: {
  19.     addItem(product: Product, quantity: number = 1) {
  20.       const existingItem = this.items.find(item => item.product.id === product.id)
  21.       
  22.       if (existingItem) {
  23.         existingItem.quantity += quantity
  24.       } else {
  25.         this.items.push({ product, quantity })
  26.       }
  27.       
  28.       this.updateTotal()
  29.     },
  30.    
  31.     removeItem(productId: number) {
  32.       this.items = this.items.filter(item => item.product.id !== productId)
  33.       this.updateTotal()
  34.     },
  35.    
  36.     updateTotal() {
  37.       this.total = this.items.reduce((sum, item) =>
  38.         sum + (item.product.price * item.quantity), 0
  39.       )
  40.     }
  41.   }
  42. })
复制代码
5. 插件与类型扩展  
  创建带类型的插件
  1. // plugins/persistence.ts
  2. import { PiniaPluginContext } from 'pinia'

  3. interface PersistenceOptions {
  4.   key?: string
  5.   storage?: Storage
  6. }

  7. function piniaPersistencePlugin(options: PersistenceOptions = {}) {
  8.   return (context: PiniaPluginContext) => {
  9.     const { store } = context
  10.     const key = options.key || `pinia-${store.$id}`
  11.     const storage = options.storage || localStorage
  12.    
  13.     // 从存储中恢复状态
  14.     const stored = storage.getItem(key)
  15.     if (stored) {
  16.       store.$patch(JSON.parse(stored))
  17.     }
  18.    
  19.     // 监听变化并保存
  20.     store.$subscribe((mutation, state) => {
  21.       storage.setItem(key, JSON.stringify(state))
  22.     })
  23.   }
  24. }

  25. export default piniaPersistencePlugin
复制代码
  类型扩展
  1. // types/pinia.d.ts
  2. import 'pinia'

  3. declare module 'pinia' {
  4.   export interface PiniaCustomProperties {
  5.     // 可以添加自定义属性
  6.     $customMethod: (value: string) => void
  7.   }
  8.   
  9.   export interface DefineStoreOptionsBase<S, Store> {
  10.     // 可以添加 store 配置选项
  11.     persist?: boolean
  12.   }
  13. }
复制代码
6. 测试中的类型安全
  1. // tests/counter.spec.ts
  2. import { setActivePinia, createPinia } from 'pinia'
  3. import { useCounterStore } from '@/stores/counter'
  4. import { describe, it, expect, beforeEach } from 'vitest'

  5. describe('Counter Store', () => {
  6.   beforeEach(() => {
  7.     setActivePinia(createPinia())
  8.   })
  9.   
  10.   it('should increment count', () => {
  11.     const store = useCounterStore()
  12.    
  13.     expect(store.count).toBe(0)
  14.     store.increment()
  15.     expect(store.count).toBe(1)
  16.   })
  17.   
  18.   it('should calculate double count', () => {
  19.     const store = useCounterStore()
  20.     store.count = 5
  21.    
  22.     expect(store.doubleCount).toBe(10)
  23.   })
  24.   
  25.   it('should handle async increment', async () => {
  26.     const store = useCounterStore()
  27.    
  28.     // 模拟 API 调用
  29.     global.fetch = vi.fn().mockResolvedValue({
  30.       json: () => Promise.resolve({ amount: 3 })
  31.     })
  32.    
  33.     await store.incrementAsync()
  34.     expect(store.count).toBe(3)
  35.   })
  36. })
复制代码





您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Copyright © 2001-2025 Suike Tech All Rights Reserved. 随客交流社区 (备案号:津ICP备19010126号) |Processed in 0.105297 second(s), 7 queries , Gzip On, MemCached On.
关灯 在本版发帖返回顶部
快速回复 返回顶部 返回列表