下面详细介绍在 Vue3 + TypeScript 项目中使用 Pinia ^3.0.4 的完整步骤和最佳实践,包含核心概念、代码示例和常见场景。
一、环境准备
确保项目已安装 Vue3 + TypeScript,然后安装 Pinia:
bash
运行
perl
# npm
npm install pinia@^3.0.4
# yarn
yarn add pinia@^3.0.4
# pnpm
pnpm add pinia@^3.0.4
二、初始化 Pinia
在项目入口文件(如 main.ts)中创建并挂载 Pinia 实例:
typescript
运行
javascript
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
// 创建 Pinia 实例
const pinia = createPinia()
// 挂载到 Vue 应用
app.use(pinia)
app.mount('#app')
三、核心概念:定义 Store
Pinia 中只有 store 概念(替代 Vuex 的 State/Mutation/Action/Getter),通过 defineStore 定义,推荐按功能模块划分 store。
3.1 基础示例(用户 Store)
创建 src/stores/user.ts:
typescript
运行
typescript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// 第一个参数:store 唯一 ID(必须唯一)
// 第二个参数:store 配置对象
export const useUserStore = defineStore('user', () => {
// ========== 1. 状态(State):用 ref/reactive 定义 ==========
const name = ref<string>('张三')
const age = ref<number>(20)
const permissions = ref<string[]>(['read', 'write'])
// ========== 2. 计算属性(Getters):用 computed 定义 ==========
// 注意:getter 依赖状态自动缓存,类似 Vue 组件的 computed
const fullInfo = computed(() => {
return `姓名:${name.value},年龄:${age.value}`
})
const hasAdminPermission = computed(() => {
return permissions.value.includes('admin')
})
// ========== 3. 方法(Actions):普通函数(支持同步/异步) ==========
// 同步 Action
function updateName(newName: string) {
name.value = newName
}
// 异步 Action(示例:模拟接口请求)
async function fetchUserInfo(userId: number) {
try {
// 模拟 API 请求
const res = await new Promise<{ name: string; age: number }>((resolve) => {
setTimeout(() => {
resolve({ name: '李四', age: 25 })
}, 1000)
})
name.value = res.name
age.value = res.age
} catch (error) {
console.error('获取用户信息失败:', error)
}
}
// ========== 4. 暴露状态/计算属性/方法 ==========
return {
name,
age,
permissions,
fullInfo,
hasAdminPermission,
updateName,
fetchUserInfo
}
})
3.2 类型提示说明
- Pinia 结合 TypeScript 会自动推导类型,无需手动声明(如
name自动推导为Ref<string>)。 - 如果需要显式指定类型,可在
ref/computed中明确标注(如上例)。
四、在组件中使用 Store
4.1 基础使用(组合式 API)
vue
xml
<!-- src/components/UserInfo.vue -->
<template>
<div>
<h3>{{ userStore.fullInfo }}</h3>
<p>是否有管理员权限:{{ userStore.hasAdminPermission ? '是' : '否' }}</p>
<button @click="handleUpdateName">修改姓名</button>
<button @click="handleFetchUser">异步获取用户信息</button>
</div>
</template>
<script setup lang="ts">
// 导入定义的 store
import { useUserStore } from '@/stores/user'
// 获取 store 实例(Pinia 会自动管理单例,多次调用返回同一个实例)
const userStore = useUserStore()
// 方法示例
const handleUpdateName = () => {
userStore.updateName('王五')
}
const handleFetchUser = async () => {
await userStore.fetchUserInfo(1)
}
</script>
4.2 解构 Store(保持响应式)
直接解构 store 会丢失响应式,需使用 storeToRefs:
typescript
运行
javascript
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 解构状态/计算属性(保持响应式)
const { name, age, fullInfo } = storeToRefs(userStore)
// 方法可直接解构(无需 ref)
const { updateName } = userStore
// 使用
console.log(name.value) // 响应式
updateName('赵六') // 正常调用
五、高级用法
5.1 重置 Store 状态
调用 $reset() 方法重置状态到初始值:
typescript
运行
scss
const userStore = useUserStore()
userStore.$reset() // 重置所有状态
5.2 批量修改状态
使用 $patch 方法批量更新(性能更优):
typescript
运行
javascript
// 方式1:对象形式(适合简单更新)
userStore.$patch({
name: '钱七',
age: 30
})
// 方式2:函数形式(适合复杂更新)
userStore.$patch((state) => {
state.permissions.push('admin')
state.age += 1
})
5.3 监听 Store 变化
使用 $subscribe 监听状态变化(类似 Vue 的 watch):
typescript
运行
javascript
const userStore = useUserStore()
// 监听所有状态变化
const unsubscribe = userStore.$subscribe((mutation, state) => {
console.log('状态变化:', mutation, state)
// mutation.type:'direct'(直接修改)| 'patch object' | 'patch function'
// mutation.storeId:store 唯一 ID
})
// 取消监听(组件卸载时调用)
onUnmounted(() => {
unsubscribe()
})
// 单独监听某个属性(用 Vue 的 watch)
watch(() => userStore.age, (newAge, oldAge) => {
console.log('年龄变化:', newAge, oldAge)
})
5.4 Store 之间相互调用
一个 store 可以导入并使用另一个 store:
typescript
运行
typescript
// src/stores/cart.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', () => {
const userStore = useUserStore()
const cartList = ref<Array<{ id: number; name: string }>>([])
// 根据用户权限过滤购物车
const filteredCart = computed(() => {
if (userStore.hasAdminPermission) {
return cartList.value
}
return cartList.value.filter(item => item.id < 10)
})
function addToCart(item: { id: number; name: string }) {
// 使用 userStore 的状态
if (userStore.age >= 18) {
cartList.value.push(item)
}
}
return { cartList, filteredCart, addToCart }
})
5.5 持久化存储(可选)
结合 pinia-plugin-persistedstate 实现状态持久化(如 localStorage):
bash
运行
bash
# 安装插件
npm install pinia-plugin-persistedstate
typescript
运行
javascript
// main.ts
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
// 注册持久化插件
pinia.use(piniaPluginPersistedstate)
typescript
运行
javascript
// 修改 user store,添加持久化配置
export const useUserStore = defineStore('user', () => {
// ... 原有代码
}, {
// 持久化配置
persist: {
key: 'user-store', // 自定义存储的 key
storage: localStorage, // 存储方式(localStorage/sessionStorage)
paths: ['name', 'age'] // 只持久化 name 和 age,默认所有状态
}
})
六、类型扩展(可选)
如果需要扩展 Pinia 的类型(如自定义插件),可创建 src/types/pinia.d.ts:
typescript
运行
typescript
import 'pinia'
declare module 'pinia' {
export interface PiniaCustomProperties {
// 扩展 store 实例的属性/方法
$log: () => void
}
}
// 在 main.ts 中注册
pinia.use(({ store }) => {
store.$log = () => {
console.log(`[${store.$id}]`, store.$state)
}
})
// 使用
userStore.$log() // 打印 store 状态
七、注意事项
- Store ID 唯一性 :
defineStore的第一个参数必须唯一,否则会导致状态冲突。 - 避免在非组件中滥用 Store:如果在工具函数中使用 Store,确保 Pinia 已初始化。
- 异步 Action 错误处理:异步 Action 需手动捕获异常,避免页面崩溃。
- 响应式保持 :解构 Store 状态时必须使用
storeToRefs,方法可直接解构。
以上就是 Vue3 + TypeScript 中使用 Pinia ^3.0.4 的完整指南,涵盖了基础用法、高级特性和最佳实践,可根据项目需求灵活调整。