大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
Pinia 状态管理
这一课你将能做到
- 用
defineStore创建全局状态仓库 - 使用
state、getters、actions组织状态逻辑 - 在组件中正确读取和修改状态
- 实现模块化状态管理
- 配置状态持久化(localStorage)
为什么要学
组件多了之后,数据共享变成噩梦:A 组件的数据 B 组件要用,C 组件改了 D 组件要同步。props 传不动,事件总线太乱。Pinia 就是「全局数据管家」,集中管理所有共享状态。
人话:Pinia 就像「公司共享文件夹」。每个部门(组件)都可以往里面放文件(state)、读文件(getters)、修改文件(actions),不用部门之间互相传纸条(props/emits)。
先建立图像
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2
},
actions: {
increment() { this.count++ }
}
})
<script setup>
import { useCounterStore } from './stores/counter'
const store = useCounterStore()
</script>
<template>
<p>{{ store.count }} / {{ store.double }}</p>
<button @click="store.increment()">+1</button>
</template>
跟着做
步骤 1:安装与注册
npm install pinia
// main.js
import { createPinia } from 'pinia'
createApp(App).use(createPinia()).mount('#app')
步骤 2:创建 Store
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
token: '',
isLogin: false
}),
getters: {
upperName: (state) => state.name.toUpperCase()
},
actions: {
login(name, token) {
this.name = name
this.token = token
this.isLogin = true
},
logout() {
this.$reset()
}
}
})
步骤 3:组件中使用
<script setup>
import { useUserStore } from '../stores/user'
const user = useUserStore()
function handleLogin() {
user.login('张三', 'token123')
}
</script>
<template>
<div v-if="user.isLogin">
<p>欢迎,{{ user.name }}({{ user.upperName }})</p>
<button @click="user.logout()">退出</button>
</div>
<button v-else @click="handleLogin">登录</button>
</template>
步骤 4:组合式风格(推荐)
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() { count.value++ }
return { count, double, increment }
})
组合式风格更灵活,TypeScript 支持更好。
步骤 5:模块化拆分
// stores/index.js
export { useUserStore } from './user'
export { useCartStore } from './cart'
export { useProductStore } from './product'
每个模块一个文件,职责清晰,便于维护。
步骤 6:持久化到 localStorage
// stores/user.js
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
export const useUserStore = defineStore('user', () => {
const token = ref(localStorage.getItem('token') || '')
watch(token, (val) => {
localStorage.setItem('token', val)
})
function setToken(val) { token.value = val }
return { token, setToken }
})
刷新页面后 token 自动恢复。
讲清楚要点
- defineStore 的第一个参数是 id:必须唯一,用于 DevTools 和 SSR。
- state 是函数:返回初始状态,避免多实例共享引用。
- getters 是计算属性:有缓存,依赖不变时不重新计算。
- actions 可以是异步的:直接在 action 里发请求,组件调用即可。
- store.$reset():重置 state 到初始值,但组合式风格需要自己实现。
- 解构 store 会丢失响应性:要用
storeToRefs或直接用store.xxx。
你可能会懵的地方
-
Pinia 和 Vuex 有什么区别? Pinia 更轻量(1KB)、API 更简洁、没有 mutations、TypeScript 支持更好、支持组合式风格。Vue 官方推荐新项目用 Pinia。
-
什么时候该用 Pinia,什么时候用 props? 跨多个层级共享数据用 Pinia;父子组件直接传数据用 props。
-
actions 和 mutations 的区别? Pinia 没有 mutations,actions 直接改 state。Vuex 强制 mutations 同步、actions 异步,Pinia 没这个限制。
-
storeToRefs 是干嘛的? 从 store 解构 state 和 getters 时保持响应性。
const { count } = store丢失响应性,const { count } = storeToRefs(store)保持。 -
服务端渲染(SSR)怎么用 Pinia? 每个请求创建独立的 pinia 实例,避免状态污染。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 直接解构 store 的 state | 丢失响应性 | 用 storeToRefs 或直接用 store.xxx |
| 在组件里直接修改 store.state.xxx | 无法追踪,DevTools 不记录 | 调用 store 的 action |
| 所有状态都放一个 store | 臃肿难维护 | 按模块拆分多个 store |
| state 写成对象而非函数 | 多实例共享引用 | state: () => ({ ... }) |
| 异步操作写在组件里 | 逻辑分散,难复用 | 写在 store 的 action 里 |
卡点急救
- store 数据不更新:检查是否用 storeToRefs 解构,或是否直接修改了 state 而非通过 action。
- 刷新页面数据丢失:配置持久化,或用插件
pinia-plugin-persistedstate。 - TypeScript 报错:组合式风格需要显式 return 所有要暴露的属性和方法。
面试常问
-
Pinia 相比 Vuex 的优势? 更轻量、API 更简洁、无 mutations、更好的 TS 支持、支持组合式、DevTools 集成更好。
-
Pinia 的 state 为什么要是函数? 避免多实例/多请求间共享引用,确保每次创建独立的 state 对象。
-
怎么在 Pinia 中实现模块化? 每个模块一个文件,用 defineStore 定义独立 store,通过 id 区分。
自测三问
storeToRefs解决什么问题?- Pinia 的 actions 和 Vuex 的 actions 有什么区别?
- 什么场景下应该用 Pinia 而不是 props?
小结一句话
Pinia 是 Vue 3 官方推荐的状态管理,defineStore 定义仓库,state/getters/actions 组织逻辑,模块化拆分保持可维护性,掌握它就掌握了大型 Vue 应用的数据管理。
延伸
- Pinia 官方文档
- 下一课:数据请求——fetch、axios、拦截器、封装 useFetch。