Pinia是Vue官方推荐的状态管理库,原文说明它始于2019年的一次实验,目标是提供带组合式API的Vue状态管理方案;目前同时支持Vue 2和Vue 3,且不强制使用组合式API。它主要用于跨组件、跨页面共享状态,并提供Devtools、热更新、时间旅行调试、TypeScript自动补全和服务端渲染支持。与Vuex相比,Pinia最明显的变化是取消mutations:修改状态不再必须commit,action可以直接改state,同步和异步逻辑都放在action中,原文给出的代码量减少幅度为30%到50%。下面按安装挂载、Store定义、组件调用、Vuex对比和选型迁移来整理。
一、安装与挂载
安装命令:- npm install pinia
- # 或
- yarn add pinia
- # 或
- pnpm add pinia
复制代码
创建实例并挂载到Vue应用:- // src/store/index.js
- import { createPinia } from 'pinia'
- const pinia = createPinia()
- export default pinia
- // src/main.js
- import { createApp } from 'vue'
- import App from './App.vue'
- import pinia from './store'
- const app = createApp(App)
- app.use(pinia)
- app.mount('#app')
复制代码
这里的关键是createPinia()生成实例,再通过app.use(pinia)安装;后续组件里使用的Store都依赖这个挂载过程。
二、Option Store写法
Option Store接近Vue选项式API,适合从Vuex迁移的开发者。state返回数据对象,getters基于state派生并带缓存,actions放同步或异步方法。示例:- import { defineStore } from 'pinia'
- export const useCounterStore = defineStore('counter', {
- state: () => ({
- count: 0,
- name: 'Pinia'
- }),
- getters: {
- doubleCount: (state) => state.count * 2,
- message: (state) => `${state.name} says count is ${state.count}`
- },
- actions: {
- increment() {
- this.count++
- },
- decrement() {
- this.count--
- },
- async fetchCount() {
- const res = await fetch('/api/count')
- this.count = await res.json()
- }
- }
- })
复制代码
组件中用useCounterStore()实例化,再直接访问state、getters和actions:- <template>
- <div>
- <p>Count: {{ counter.count }}</p>
- <p>Double: {{ counter.doubleCount }}</p>
- <p>{{ counter.message }}</p>
- <button @click='counter.increment'>+</button>
- <button @click='counter.decrement'>-</button>
- <button @click='counter.fetchCount'>获取远程数据</button>
- </div>
- </template>
- <script setup>
- import { useCounterStore } from '@/store/counter'
- const counter = useCounterStore()
- </script>
复制代码
三、Setup Store写法
Setup Store直接使用ref、computed和普通函数,适合习惯Composition API的开发者。原文明确:ref()对应state,computed()对应getters,function()对应actions;必须返回所有state属性,否则会影响SSR和Devtools。示例:- import { defineStore } from 'pinia'
- import { ref, computed } from 'vue'
- export const useCounterStore = defineStore('counter', () => {
- const count = ref(0)
- const name = ref('Pinia')
- const doubleCount = computed(() => count.value * 2)
- const message = computed(() => `${name.value} says count is ${count.value}`)
- function increment() {
- count.value++
- }
- function decrement() {
- count.value--
- }
- async function fetchCount() {
- const res = await fetch('/api/count')
- count.value = await res.json()
- }
- return { count, name, doubleCount, message, increment, decrement, fetchCount }
- })
复制代码
组件调用方式与Option Store一致,仍然先useCounterStore(),再访问counter.count、counter.increment等。区别主要在Store定义侧:Setup Store更灵活,但返回列表要完整。
四、选项式API组件中的映射
如果Vue 3项目仍使用选项式API,可以用mapState、mapActions映射:- <script>
- import { mapState, mapActions } from 'pinia'
- import { useCounterStore } from '@/store/counter'
- export default {
- computed: {
- ...mapState(useCounterStore, ['count', 'doubleCount'])
- },
- methods: {
- ...mapActions(useCounterStore, ['increment', 'decrement'])
- }
- }
- </script>
复制代码
这样组件内可以按原有选项式API方式使用state、getters和actions,不必全部改成script setup。
五、与Vuex的核心差异
原文的对比可以归纳为几个关键点:Vuex 4修改状态必须通过commit mutation,Pinia取消mutation,直接在action中修改;Vuex采用单一Store树并通过modules划分,Pinia是多Store设计,每个Store独立存在,通过import引用;TypeScript方面,Vuex需要大量手动类型声明,Pinia自动类型推导;模块化方面,Vuex依赖namespaced: true和嵌套路径,Pinia天然模块化;样板代码Vuex更多,Pinia更简洁,原文给出的减少幅度是30%到50%;学习曲线上Vuex较陡,Pinia接近Vue组件写法;热更新Pinia更友好。
Vuex 4的典型写法:- 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
- }
- })
- // 组件中
- this.$store.commit('increment')
- this.$store.dispatch('incrementAsync')
复制代码
Pinia对应写法:- export const useCounterStore = defineStore('counter', {
- state: () => ({ count: 0 }),
- actions: {
- increment() { this.count++ },
- async incrementAsync() {
- await delay(1000)
- this.count++
- }
- },
- getters: {
- doubleCount: state => state.count * 2
- }
- })
- // 组件中
- counter.increment()
- counter.incrementAsync()
复制代码
优缺点方面,原文认为Pinia语法简洁、无需mutations、样板代码少、TypeScript类型推导开箱即用、多Store天然模块化、轻量且热更新体验更好;不足是社区生态相对较新,插件不如Vuex丰富,学习资料也相对少。Vuex的优点是社区成熟、插件和工具丰富、学习资料多、严格单向数据流在大型项目里更可控;缺点是语法复杂、样板代码多、TypeScript支持不够友好,需要大量手动声明。
六、选型与迁移建议
新Vue 3项目优先选Pinia,原文给出的理由是Vue官方推荐、更现代、更简洁。Vue 2项目升级可以逐步迁移,也可以直接使用Pinia,因为Pinia同时支持Vue 2。大型遗留项目如果已经深度使用Vuex,建议渐进式迁移,允许Vuex与Pinia共存;原文提到的做法是新功能用Pinia,旧模块逐步迁移,并创建一个“桥接Store”,把Vuex中频繁使用的核心状态优先迁移到Pinia。中小型项目不一定需要状态管理库,ref/reactive配合provide/inject可能已经足够。
总结:Pinia的核心改动是取消mutations、让action直接修改状态并处理异步,同时用多Store和TypeScript推导降低样板代码;Option Store、Setup Store和选项式API映射三种用法覆盖了不同组件风格。对Vue 3新项目,按原文结论应优先考虑Pinia;对Vuex存量项目,则按渐进迁移和共存的思路处理更稳妥。 |