|
这里或许是互联网从业者的最后一片净土,随客社区期待您的加入!
您需要 登录 才可以下载或查看,没有账号?立即注册
×
本帖最后由 zzz 于 2025-9-26 15:11 编辑
Pinia 是 Vue.js 的官方状态管理库,与 TypeScript 的集成非常出色。下面我将详细介绍如何将两者结合使用。
1.基础设置
安装依赖
- npm install pinia
- npm install -D typescript @vue/tsconfig
复制代码 创建 Pinia Store 并添加类型- // stores/counter.ts
- import { defineStore } from 'pinia'
- // 定义状态接口
- interface CounterState {
- count: number
- name: string
- }
- export const useCounterStore = defineStore('counter', {
- // 状态类型注解
- state: (): CounterState => ({
- count: 0,
- name: 'Counter Store'
- }),
-
- // Getter 类型推断
- getters: {
- doubleCount: (state) => state.count * 2,
- // 明确返回类型
- doubleCountPlusOne(): number {
- return this.doubleCount + 1
- },
- // 使用参数并指定返回类型
- getCountPlus: (state) => {
- return (plus: number): number => state.count + plus
- }
- },
-
- actions: {
- // Action 方法
- increment() {
- this.count++
- },
- // 带参数的 action
- incrementBy(amount: number) {
- this.count += amount
- },
- // 异步 action
- async incrementAsync() {
- const response = await fetch('/api/increment')
- const data = await response.json()
- this.count += data.amount
- }
- }
- })
复制代码 2. 组合式 API 风格的 Store- // stores/user.ts
- import { defineStore } from 'pinia'
- import { ref, computed } from 'vue'
- interface User {
- id: number
- name: string
- email: string
- }
- export const useUserStore = defineStore('user', () => {
- // 状态
- const user = ref<User | null>(null)
- const isLoggedIn = ref(false)
-
- // Getter 等价物
- const userName = computed(() => user.value?.name || 'Guest')
- const userEmail = computed(() => user.value?.email || '')
-
- // Actions
- function login(email: string, password: string) {
- return new Promise<void>((resolve) => {
- setTimeout(() => {
- user.value = {
- id: 1,
- name: 'John Doe',
- email: email
- }
- isLoggedIn.value = true
- resolve()
- }, 1000)
- })
- }
-
- function logout() {
- user.value = null
- isLoggedIn.value = false
- }
-
- return {
- user,
- isLoggedIn,
- userName,
- userEmail,
- login,
- logout
- }
- })
复制代码 3. 在组件中使用
选项式API组件
- <template>
- <div>
- <h1>{{ store.name }}</h1>
- <p>Count: {{ store.count }}</p>
- <p>Double: {{ store.doubleCount }}</p>
- <button @click="store.increment()">Increment</button>
- </div>
- </template>
- <script lang="ts">
- import { defineComponent } from 'vue'
- import { useCounterStore } from '@/stores/counter'
- export default defineComponent({
- setup() {
- const store = useCounterStore()
- return {
- store
- }
- }
- })
- </script>
复制代码 组合式API组件
- <template>
- <div>
- <h1>User: {{ userName }}</h1>
- <p>Email: {{ userEmail }}</p>
- <p v-if="isLoggedIn">Status: Logged In</p>
- <button @click="login('test@example.com', 'password')">Login</button>
- <button @click="logout">Logout</button>
- </div>
- </template>
- <script setup lang="ts">
- import { storeToRefs } from 'pinia'
- import { useUserStore } from '@/stores/user'
- const userStore = useUserStore()
- // 使用 storeToRefs 保持响应式
- const { userName, userEmail, isLoggedIn } = storeToRefs(userStore)
- // 直接解构 actions
- const { login, logout } = userStore
- </script>
复制代码 4. 高级类型技巧
自定义类型 Store- // stores/types.ts
- export interface Product {
- id: number
- name: string
- price: number
- category: string
- }
- export interface CartItem {
- product: Product
- quantity: number
- }
- export interface CartState {
- items: CartItem[]
- total: number
- }
复制代码 使用复杂类型的 Store
- // stores/cart.ts
- import { defineStore } from 'pinia'
- import type { CartState, CartItem, Product } from './types'
- export const useCartStore = defineStore('cart', {
- state: (): CartState => ({
- items: [],
- total: 0
- }),
-
- getters: {
- itemCount: (state) => state.items.reduce((sum, item) => sum + item.quantity, 0),
- getItemByProductId: (state) => {
- return (productId: number): CartItem | undefined =>
- state.items.find(item => item.product.id === productId)
- }
- },
-
- actions: {
- addItem(product: Product, quantity: number = 1) {
- const existingItem = this.items.find(item => item.product.id === product.id)
-
- if (existingItem) {
- existingItem.quantity += quantity
- } else {
- this.items.push({ product, quantity })
- }
-
- this.updateTotal()
- },
-
- removeItem(productId: number) {
- this.items = this.items.filter(item => item.product.id !== productId)
- this.updateTotal()
- },
-
- updateTotal() {
- this.total = this.items.reduce((sum, item) =>
- sum + (item.product.price * item.quantity), 0
- )
- }
- }
- })
复制代码 5. 插件与类型扩展
创建带类型的插件- // plugins/persistence.ts
- import { PiniaPluginContext } from 'pinia'
- interface PersistenceOptions {
- key?: string
- storage?: Storage
- }
- function piniaPersistencePlugin(options: PersistenceOptions = {}) {
- return (context: PiniaPluginContext) => {
- const { store } = context
- const key = options.key || `pinia-${store.$id}`
- const storage = options.storage || localStorage
-
- // 从存储中恢复状态
- const stored = storage.getItem(key)
- if (stored) {
- store.$patch(JSON.parse(stored))
- }
-
- // 监听变化并保存
- store.$subscribe((mutation, state) => {
- storage.setItem(key, JSON.stringify(state))
- })
- }
- }
- export default piniaPersistencePlugin
复制代码 类型扩展- // types/pinia.d.ts
- import 'pinia'
- declare module 'pinia' {
- export interface PiniaCustomProperties {
- // 可以添加自定义属性
- $customMethod: (value: string) => void
- }
-
- export interface DefineStoreOptionsBase<S, Store> {
- // 可以添加 store 配置选项
- persist?: boolean
- }
- }
复制代码 6. 测试中的类型安全- // tests/counter.spec.ts
- import { setActivePinia, createPinia } from 'pinia'
- import { useCounterStore } from '@/stores/counter'
- import { describe, it, expect, beforeEach } from 'vitest'
- describe('Counter Store', () => {
- beforeEach(() => {
- setActivePinia(createPinia())
- })
-
- it('should increment count', () => {
- const store = useCounterStore()
-
- expect(store.count).toBe(0)
- store.increment()
- expect(store.count).toBe(1)
- })
-
- it('should calculate double count', () => {
- const store = useCounterStore()
- store.count = 5
-
- expect(store.doubleCount).toBe(10)
- })
-
- it('should handle async increment', async () => {
- const store = useCounterStore()
-
- // 模拟 API 调用
- global.fetch = vi.fn().mockResolvedValue({
- json: () => Promise.resolve({ amount: 3 })
- })
-
- await store.incrementAsync()
- expect(store.count).toBe(3)
- })
- })
复制代码
|
|