如题,结合官方文档与网络帖子,利用AI整理
Pinia 知识点整理:从基础到进阶
目录
简介
安装和基本配置
核心概念
基础用法
进阶用法
插件系统
TypeScript 支持
最佳实践
与 Vuex 的对比
总结
简介
什么是 Pinia?
Pinia 是 Vue.js 的专属状态管理库,它允许你跨组件或页面共享状态。Pinia 起始于 2019 年 11 月的一次实验,其目的是设计一个拥有组合式 API 的 Vue 状态管理库。
为什么选择 Pinia?
Pinia 相比传统的状态管理方案具有以下优势:
TypeScript 支持 :原生支持 TypeScript,提供完善的类型推断
简洁的 API :比 Vuex 更简洁,减少了样板代码
组合式 API :支持 Vue 3 的组合式 API 风格
DevTools 支持 :完整的 DevTools 集成,支持时间旅行调试
插件系统 :强大的插件系统,可以扩展 Pinia 的功能
服务端渲染支持 :原生支持服务端渲染
热更新 :开发时无需重载页面即可修改 Store
核心特性
Pinia 的核心概念包括:
安装和基本配置
安装 Pinia
使用 npm 或 yarn 安装 Pinia:
1 2 3 4 5 6 7 8 9 10 11 npm install pinia yarn add pinia pnpm add pinia bun add pinia
Vue 2 兼容性
如果使用 Vue 2,还需要安装 composition-api:
1 npm install pinia @vue/composition-api
基本配置
在 main.ts 中初始化 Pinia:
1 2 3 4 5 6 7 8 9 10 import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' const app = createApp (App )const pinia = createPinia ()app.use (pinia) app.mount ('#app' )
核心概念
Store
Store 是 Pinia 的核心概念,每个 Store 都是一个独立的模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { defineStore } from 'pinia' export const useCounterStore = defineStore ('counter' , () => { const count = ref (0 ) const name = ref ('Pinia' ) const doubleCount = computed (() => count.value * 2 ) function increment ( ) { count.value ++ } async function fetchData ( ) { const data = await fetch ('/api/data' ) name.value = data.name } return { count, name, doubleCount, increment, fetchData } })
State
State 是存储应用状态的地方,相当于组件中的 data:
1 2 3 4 const count = ref (0 )const user = ref (null )const todos = ref ([])
Getters
Getters 是计算属性,用于从 State 中派生出新的状态:
1 2 3 const doubleCount = computed (() => count.value * 2 )const doublePlusOne = computed (() => doubleCount.value + 1 )
Actions
Actions 用于修改状态,可以是同步或异步的:
1 2 3 4 5 6 7 8 9 function increment ( ) { count.value ++ } async function fetchUser ( ) { const response = await fetch ('/api/user' ) user.value = await response.json () }
基础用法
在组件中使用 Store
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template > <div > <h2 > {{ counterStore.name }}</h2 > <p > Count: {{ counterStore.count }}</p > <p > Double Count: {{ counterStore.doubleCount }}</p > <button @click ="counterStore.increment" > Increment</button > <button @click ="counterStore.fetchData" > Fetch Data</button > </div > </template > <script setup lang ="ts" > import { useCounterStore } from '@/stores/counter' const counterStore = useCounterStore ()</script >
解构 Store
直接解构会失去响应性,需要使用 storeToRefs:
1 2 3 4 5 6 7 8 9 10 <script setup lang="ts" > import { storeToRefs } from 'pinia' import { useCounterStore } from '@/stores/counter' const counterStore = useCounterStore ()const { count, doubleCount } = storeToRefs (counterStore)const { increment, fetchData } = counterStore</script>
修改 State
有多种方式可以修改 State:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script setup lang="ts" > import { useCounterStore } from '@/stores/counter' const counterStore = useCounterStore ()counterStore.count ++ counterStore.$patch({ count : counterStore.count + 1 , name : 'New Name' }) counterStore.$patch((state ) => { state.count ++ state.todos .push ({ id : 1 , text : 'Learn Pinia' }) }) counterStore.increment () </script>
进阶用法
监听 State 变化
使用 $subscribe 监听状态变化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script setup lang="ts" > import { useCounterStore } from '@/stores/counter' const counterStore = useCounterStore ()const unsubscribe = counterStore.$subscribe((mutation, state ) => { console .log ('State changed:' , mutation, state) localStorage .setItem ('counter' , JSON .stringify (state)) }) const pinia = usePinia ()pinia.use (({ store } ) => { store.$subscribe((mutation, state ) => { console .log (`Store ${store.$id} changed` ) }) }) </script>
Getter 高级用法
访问其他 Store
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { defineStore } from 'pinia' import { useCounterStore } from './counter' export const useUserStore = defineStore ('user' , { state : () => ({ userId : 1 }), getters : { userWithCount : (state ) => { const counterStore = useCounterStore () return { userId : state.userId , count : counterStore.count } } } })
Getter 参数化
1 2 3 4 5 6 7 const getUserById = computed (() => { return (userId : number ) => users.value .find (user => user.id === userId) }) const user = userStore.getUserById (1 )
Action 高级用法
异步 Actions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 export const useUserStore = defineStore ('user' , { state : () => ({ users : [], loading : false , error : null }), actions : { async fetchUsers ( ) { this .loading = true this .error = null try { const response = await fetch ('/api/users' ) this .users = await response.json () } catch (error) { this .error = error.message } finally { this .loading = false } }, async addUser (userData ) { const response = await fetch ('/api/users' , { method : 'POST' , body : JSON .stringify (userData) }) const newUser = await response.json () this .users .push (newUser) return newUser } } })
Action 间的调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 actions : { async fetchAndProcessData ( ) { await this .fetchUsers () this .processUserData () }, processUserData ( ) { this .users = this .users .map (user => ({ ...user, fullName : `${user.firstName} ${user.lastName} ` })) } }
插件系统
自定义插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 export function piniaLogger ( ) { return (context ) => { const { store } = context store.$onAction(({ name, args, after, onError } ) => { console .log (`[Pinia Logger] Action "${name} " started with args:` , args) after ((result ) => { console .log (`[Pinia Logger] Action "${name} " succeeded with result:` , result) }) onError ((error ) => { console .error (`[Pinia Logger] Action "${name} " failed with error:` , error) }) }) store.$subscribe((mutation ) => { console .log (`[Pinia Logger] State changed in store "${store.$id} ":` , mutation) }) } }
使用插件
1 2 3 4 5 6 // src/main.ts import { createPinia } from 'pinia' import { piniaLogger } from './plugins/pinia-logger' const pinia = createPinia() pinia.use(piniaLogger())
状态持久化插件
使用 pinia-plugin-persistedstate 实现状态持久化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 npm install pinia-plugin-persistedstate import { createPinia } from 'pinia' import piniaPluginPersist from 'pinia-plugin-persistedstate' const pinia = createPinia ()pinia.use (piniaPluginPersist) export const useUserStore = defineStore ('user' , { state : () => ({ token : '' , userInfo : null }), persist : { enabled : true , strategies : [ { key : 'user-store' , storage : localStorage , paths : ['token' , 'userInfo' ] } ] } })
TypeScript 支持
类型定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 interface User { id : number name : string email : string } interface CounterState { count : number name : string user : User | null } import { defineStore } from 'pinia' import type { CounterState } from '@/types' export const useCounterStore = defineStore ('counter' , { state : (): CounterState => ({ count : 0 , name : 'Pinia' , user : null }), getters : { userName : (state): string => state.user ?.name || 'Guest' }, actions : { setUser (user : User | null ) { this .user = user } } })
组合式 API 类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import { defineStore } from 'pinia' import { ref, computed } from 'vue' import type { User } from '@/types' export const useUserStore = defineStore ('user' , () => { const users = ref<User []>([]) const loading = ref (false ) const userCount = computed (() => users.value .length ) const activeUsers = computed (() => users.value .filter (user => user.active ) ) const fetchUsers = async (): Promise <void > => { loading.value = true const response = await fetch ('/api/users' ) users.value = await response.json () loading.value = false } const addUser = (user : Omit <User , 'id' >): void => { const newUser = { ...user, id : Date .now () } users.value .push (newUser) } return { users, loading, userCount, activeUsers, fetchUsers, addUser } })
最佳实践
项目结构
推荐的项目结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 src/ ├── stores/ │ ├── index.ts # Pinia 实例 │ ├── modules/ │ │ ├── system/ # 系统级状态 │ │ │ ├── app.ts # 应用配置 │ │ │ ├── user.ts # 用户信息 │ │ │ └── permission.ts # 权限管理 │ │ └── business/ # 业务模块 │ │ ├── cart.ts # 购物车 │ │ └── product.ts # 产品管理 └── types/ # 类型定义 └── index.ts
模块化设计
1 2 3 4 5 6 // src/stores/index.ts export * from './modules/system/app' export * from './modules/system/user' export * from './modules/system/permission' export * from './modules/business/cart' export * from './modules/business/product'
状态设计原则
单一职责 :每个 Store 应该专注于一个特定的业务领域
最小状态 :只存储必要的状态,避免存储可以计算得出的数据
不可变性 :虽然 Pinia 允许直接修改状态,但复杂对象建议使用不可变模式
类型安全 :为所有状态和操作添加类型定义
性能优化
避免过度使用全局状态 :只将真正需要共享的状态放入 Store
合理使用 Getters :利用缓存机制提高性能
批量更新 :使用 $patch 进行批量状态更新
按需加载 :对于大型应用,可以考虑动态注册 Store
测试策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import { setActivePinia, createPinia } from 'pinia' import { useCounterStore } from '@/stores/counter' describe ('Counter Store' , () => { beforeEach (() => { setActivePinia (createPinia ()) }) it ('should initialize with count 0' , () => { const counterStore = useCounterStore () expect (counterStore.count ).toBe (0 ) }) it ('should increment count' , () => { const counterStore = useCounterStore () counterStore.increment () expect (counterStore.count ).toBe (1 ) }) it ('should calculate double count correctly' , () => { const counterStore = useCounterStore () counterStore.count = 2 expect (counterStore.doubleCount ).toBe (4 ) }) })
与 Vuex 的对比
核心差异
特性
Pinia
Vuex
TypeScript 支持
原生支持,类型推断完善
需要额外类型声明
模块化设计
每个 store 独立,无嵌套
需要通过 modules 嵌套
状态修改
直接修改 state
必须通过 mutations
Actions
支持同步和异步
异步操作需要通过 actions
Mutations
已废弃
必须使用 mutations 修改状态
代码简洁性
更简洁,减少样板代码
相对繁琐
DevTools 支持
完整支持
完整支持
迁移指南
从 Vuex 迁移到 Pinia:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const store = new Vuex .Store ({ state : { count : 0 }, mutations : { increment (state ) { state.count ++ } }, actions : { incrementAsync ({ commit } ) { setTimeout (() => commit ('increment' ), 1000 ) } }, getters : { doubleCount : state => state.count * 2 } }) export const useCounterStore = defineStore ('counter' , { state : () => ({ count : 0 }), actions : { increment ( ) { this .count ++ }, incrementAsync ( ) { setTimeout (() => this .increment (), 1000 ) } }, getters : { doubleCount : state => state.count * 2 } })
Pinia 的优势
现代化设计 :基于 Vue 3 的组合式 API 设计
优秀的 TypeScript 支持 :提供完善的类型推断
简洁的 API :减少了 Vuex 中的样板代码
强大的插件系统 :可以轻松扩展功能
良好的开发体验 :完整的 DevTools 支持和热更新
适用场景
学习建议
掌握基础概念 :理解 Store、State、Getters、Actions 的基本用法
实践组合式 API :推荐使用组合式语法定义 Store
学会插件开发 :利用插件扩展 Pinia 的功能
关注最佳实践 :合理设计状态结构,避免过度使用全局状态
持续学习 :关注 Pinia 的最新特性和最佳实践
Pinia 作为 Vue 官方推荐的状态管理库,正在成为 Vue 生态系统中的标准选择。掌握 Pinia 将有助于构建更加健壮、可维护的 Vue 应用。
参考资源: