Vue3 Pinia
1.介绍
- Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态,用来代替Vuex
- 由来:
- Vuex 在大型项目中容易变成一个“巨型中心仓库”,即使使用 modules,也存在配置复杂、命名空间繁琐等问题
- Pinia 让每个业务模块拥有自己的“小仓库”,既独立又互通,真正做到了“高内聚、低耦合”
2.引入和安装
- 全局引入和安装
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
3.定义store
- 使用 defineStore 定义一个 store
推荐store名称使用 use... 这一类风格
参数一是唯一标识
参数二可以选择选项式(对象语法),也可以选择组合式(function)
import { defineStore } from 'pinia'
const useCounterStore = defineStore('counter',{})
- 选项式
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0, name: 'Eduardo' }),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
- 组合式
组合式需要借用 vue 的 ref, computed等方法创造数据,并且最后需要 return 创建的数据
//store/counter.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(1)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
4.使用 Store
- 组合式
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
</script>
<template>
<main>
<div>
<button @click="store.count++">{{ store.count }}</button>
</div>
<div>
<button @click="store.increment()">{{ store.count }}</button>
</div>
<h2>{{ store.doubleCount }}</h2>
</main>
</template>
- 选项式(未验证)
import { useUserStore } from '@/stores/user'
export default {
setup() {
const user = useUserStore()
return { user }
},
// 之后通过 this.user.xxx 进行读取
}
5.Store 解构与响应式陷阱
- 在使用 Pinia 时,一个 store 通常包含多个状态(state)、计算属性(getters)和方法(actions)。为了方便,我们可能想直接解构出某个状态,而不是每次都写 store.xxx
const store = useCounterStore()
const { count } = store // ❌ 危险!
❌问题:解构出来的 count 会丢失响应性
此问题仅影响 state 和 getters(数据属性),不影响 actions(函数本身不需要响应式)
- Pinia 提供了 storeToRefs 工具函数,专门用于安全地解构 store 的响应式属性
storeToRefs()它不是“拷贝数据”,而是“创建指向同一响应式源的引用”,操作 count++ 会直接关联到 store.count
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
const { count, doubleCount } = storeToRefs(store) // ✅ 响应式解构!
// 在 setup 或组合式函数中可直接使用
console.log(count.value) // 注意:返回的是 ref,需 .value
</script>
<template>
<main>
<div>
<button @click="count++">{{ count }}</button>
</div>
<div>
<button @click="store.count++">{{ store.count }}</button>
</div>
</main>
</template>
6.组合式Store
- 解析官方案例:为什么store 相互使用时,不能直接在 setup 函数中直接互相读取对方的 state
import { ref } from 'vue'
import { defineStore } from 'pinia'
export const useX = defineStore('x', () => {
console.log('useX setup')
const y = useY()
console.log('useX steup y.name', y.name)
function doSomething() {
// ✅ 读取 computed 或 action 中的 y 属性
const yName = y.name
// ...
}
console.log('useX setup after')
return {
name: ref('I am X'),
}
})
export const useY = defineStore('y', () => {
console.log('useY setup')
const x = useX()
//此时useX的setup还未执行完毕,没有return,拿不到其name
console.log('useY steup x.name', x.name)
function doSomething() {
// ✅ 读取 computed 或 action 中的 x 属性
const xName = x.name
// ...
}
console.log('useY setup after')
return {
name: ref('I am Y'),
}
})
<script setup lang="ts">
import { useX, useY } from '@/stores/some'
var storeX = useX()
</script>
- 输出结果
useX setup
useY setup
useY setup x.name undefined
useY setup after
useX setup y.name I am Y
useX setup after
X 先被调用(入口),但 Y 先执行完;
所以 Y 在初始化时读不到 X 的状态(因为 X 还没 return),
而 X 在初始化后期能读到 Y 的完整状态(因为 Y 已经 return 了)
- 结论:根本不会死循环,defineStore 内部有单例缓存机制 —— 每个 store 只初始化一次

浙公网安备 33010602011771号