1、Pinia入门:Pinia是什么、为什么选择Pinia、Pinia与Vuex的对比、Pinia的核心概念

Pinia是什么?一个更懂你的状态管理库

说实话,我第一次接触Pinia的时候,心里想的是:「又来一个新玩具?」

但用了一个下午之后,我彻底改观了。Pinia是Vue生态里最新的状态管理方案,由Vue核心团队成员Eduardo San Martin Morote开发。你可以把它理解成Vuex的「精神继承者」——但更轻、更快、更符合直觉。

我个人的习惯是,每次新项目启动前,都会先看看社区里最新的最佳实践。2022年以后,我几乎所有的Vue3项目都换成了Pinia。为什么?因为它的API设计实在太舒服了。

一句话总结:Pinia是一个基于Composition API的状态管理库,它让你用写普通JavaScript的方式管理全局状态。

为什么选择Pinia?三个让我「回不去」的理由

我在项目中遇到过不少状态管理的坑。Vuex的Mutation让我觉得啰嗦,Module的嵌套让人头晕。Pinia的出现,说白了就是解决了这些痛点。

1. 完整的TypeScript支持

你想想看,以前用Vuex的时候,要配TypeScript得写多少类型声明?光是一个模块的State、Getter、Mutation、Action,就得定义四套类型。Pinia不一样,它天然就是类型安全的。你写一个store,TypeScript自动推断出所有类型。

// Vuex 时代:手动定义类型
interface State {
  count: number
}
const store = new Vuex.Store<State>({...})

// Pinia 时代:自动推断
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  return { count }
})
// count 自动就是 Ref<number> 类型

2. 告别Mutation,拥抱Action

我记得刚学Vuex的时候,最困惑的就是:为什么改个数据还要分Mutation和Action?一个同步一个异步,但实际开发中经常搞混。Pinia直接砍掉了Mutation,所有状态变更都在Action里完成。

我的建议:如果你是从Vuex迁移过来的,把Mutation里的逻辑直接搬到Action里就行。少了一层心智负担,代码反而更清晰。

3. 模块化不再嵌套

Vuex的Module是嵌套的,你创建一个user模块,访问的时候要写 store.state.user.info.name。Pinia是扁平化的,每个store都是独立的,直接 useUserStore().info.name。嗯,这里要注意,扁平化带来的好处是:代码拆分更自由,你甚至可以按功能拆成多个文件。

Pinia与Vuex的对比:一张表看懂

对比维度 Vuex 4.x Pinia
API风格 Options API(类似Vue2组件) Composition API(类似Vue3 setup)
TypeScript支持 需要手动声明类型 天然类型安全,自动推断
Mutation 有,必须通过Mutation修改状态 无,直接在Action中修改
模块化 嵌套Module,命名空间需手动开启 扁平化Store,每个store独立
DevTools支持 支持,但调试体验一般 支持,时间旅行、状态快照更流畅
代码体积 约9KB(gzip后) 约1KB(gzip后)
学习成本 需要理解Mutation、Action、Module等概念 几乎零成本,会写Composition API就会用

为什么会这样?说白了,Pinia是站在Vuex的肩膀上重新设计的。它保留了Vuex的核心思想(集中式状态管理),但去掉了那些被实践证明「不那么必要」的约束。

Pinia的核心概念:三个词搞定

Pinia的核心概念只有三个:StateGetterAction。你想想看,这不就是Vue组件里的data、computed、methods吗?没错,Pinia就是把组件级别的状态管理,提升到了全局级别。

State:数据从哪里来?

State就是存储数据的地方。在Pinia里,它就是一个函数,返回一个对象。我个人习惯用setup语法(也就是函数式写法),因为更灵活。

export const useUserStore = defineStore('user', () => {
  // State
  const name = ref('张三')
  const age = ref(25)
  const hobbies = ref(['编程', '读书'])

  // 返回
  return { name, age, hobbies }
})

Getter:数据怎么加工?

Getter相当于计算属性。它依赖State,并且有缓存。我在项目中经常用Getter来处理需要派生的数据,比如格式化日期、过滤列表等。

export const useUserStore = defineStore('user', () => {
  const name = ref('张三')
  const age = ref(25)

  // Getter
  const userInfo = computed(() => `${name.value},${age.value}岁`)
  const isAdult = computed(() => age.value >= 18)

  return { name, age, userInfo, isAdult }
})

避坑指南:我曾经在Getter里直接修改State,结果导致UI更新异常。记住,Getter是只读的,永远不要在里面做赋值操作。想修改数据?去Action里做。

Action:数据怎么改?

Action是Pinia里最核心的部分。它既可以同步修改State,也可以处理异步逻辑。你不需要像Vuex那样区分同步和异步,全部写在Action里就行。

export const useUserStore = defineStore('user', () => {
  const name = ref('张三')
  const age = ref(25)

  // Action
  function updateName(newName: string) {
    name.value = newName
  }

  async function fetchUser() {
    const res = await api.getUser()
    name.value = res.name
    age.value = res.age
  }

  return { name, age, updateName, fetchUser }
})

你看,Action就是一个普通的函数。同步也好,异步也罢,想怎么写就怎么写。嗯,这里要注意,Action里的 this 指向store实例,但如果你用setup语法,直接用变量就行,不需要 this

一个小例子:从零搭建计数器

光说不练假把式。我们用一个最简单的计数器来感受一下Pinia的用法。

// stores/counter.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  // State
  const count = ref(0)

  // Getter
  const doubleCount = computed(() => count.value * 2)

  // Action
  function increment() {
    count.value++
  }

  function decrement() {
    count.value--
  }

  function reset() {
    count.value = 0
  }

  return { count, doubleCount, increment, decrement, reset }
})
// 在组件中使用
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()
</script>

<template>
  <div>
    <p>计数:{{ counter.count }}</p>
    <p>双倍:{{ counter.doubleCount }}</p>
    <button @click="counter.increment">+1</button>
    <button @click="counter.decrement">-1</button>
    <button @click="counter.reset">重置</button>
  </div>
</template>

是不是很简单?你甚至可以直接在模板里修改State:counter.count++。这在Vuex里是绝对不允许的,但Pinia觉得——你是个成熟的开发者了,应该知道自己什么时候该做什么。

我的经验:虽然Pinia允许直接修改State,但我建议还是统一用Action。原因有二:一是方便调试,DevTools能记录每次变更;二是后期维护时,你能快速定位到所有修改逻辑。

总结一下

Pinia不是什么高深莫测的东西。它就是Vuex的「简化版+增强版」——去掉了Mutation和嵌套Module,加上了完整的TypeScript支持和Composition API风格。说白了,它就是让你用更少的代码,做更多的事。

下一章,我们会手把手搭建一个Pinia项目,从安装配置到第一个Store,让你真正上手。到时候你会发现,原来状态管理可以这么轻松。