一、核心功能详解
1. State:状态定义与操作
-
定义 state
使用函数返回初始状态,确保每个实例独立。state: () => ({ user: null, isLoggedIn: false })
-
访问与修改 state
- 直接读取:
store.count
- 修改:
store.count = 10
或store.$patch()
批量更新
// 批量更新 store.$patch({ count: 10, name: 'Alice' }) // 使用函数形式(适用于复杂逻辑) store.$patch((state) => { state.count++ state.name = 'Bob' })
- 直接读取:
-
替换整个 state
store.$state = { count: 0 }
2. Getters:派生状态
-
支持参数、缓存、访问其他 getter
-
第一个参数是
state
getters: { // 基础 getter doneTodos: (state) => state.todos.filter(t => t.done), // 带参数的 getter getTodoById: (state) => (id) => state.todos.find(t => t.id === id) }
使用:
const todo = store.getTodoById(1)
3. Actions:业务逻辑中心
-
可包含同步或异步操作
-
可调用其他 action 或提交 state 变更
-
this
指向 storeactions: { async fetchUser(id) { try { const res = await fetch(`/api/users/${id}`) this.user = await res.json() this.isLoggedIn = true } catch (error) { console.error('Fetch failed:', error) } }, logout() { this.user = null this.isLoggedIn = false } }
二、高级特性
1. Store 之间的相互调用
// stores/user.js
export const useUserStore = defineStore('user', { ... })
// stores/cart.js
export const useCartStore = defineStore('cart', {
actions: {
async checkout() {
const user = useUserStore()
if (!user.isLoggedIn) {
throw new Error('Please log in first')
}
// 执行结算逻辑
}
}
})
⚠️ 注意:避免循环依赖。
2. 持久化插件(pinia-plugin-persistedstate)
自动将状态持久化到 localStorage。
npm install pinia-plugin-persistedstate
// main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
在 store 中启用:
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),
persist: true // 或配置具体选项
})
如果是选项api
配置选项
3. 插件系统(自定义插件)
可用于日志、持久化、事件追踪等。
pinia.use(({ store }) => {
// 添加自定义属性
store.$reset = () => {
store.$state = store.$options.state()
}
// 响应状态变化
store.$subscribe((mutation, state) => {
console.log('State changed:', mutation, state)
})
})
三、注意事项与最佳实践
✅ 推荐做法
-
按功能拆分 Store
如useUserStore
、useCartStore
,避免单一巨型 store。 -
使用 TypeScript 提升类型安全
export const useUserStore = defineStore('user', { state: (): UserState => ({ user: null }), getters: { fullName: (state): string => state.user?.name || '' } })
-
避免在 getters 中执行副作用
getters 应是纯函数,不应修改状态或发起请求。 -
Action 中处理异步逻辑
所有 API 调用、复杂逻辑应放在 actions 中。 -
合理使用
$patch
批量更新
减少不必要的渲染。
⚠️ 常见陷阱
-
解构 store 会失去响应性
❌ 错误:const { count, increment } = useCounterStore() // count 不再响应式
✅ 正确:
const store = useCounterStore() // 使用 store.count
-
不要直接替换 state 引用(除非用
$state
)// ❌ 错误 this.state = { count: 10 } // ✅ 正确 this.$state = { count: 10 }
-
避免在 setup 外使用 store(SSR 注意)
在setup()
或onMounted
中调用useStore()
。 -
热重载配置(开发环境)
if (import.meta.hot) { import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot)) }
-
TypeScript 类型推断问题
使用defineStore
时,建议显式定义类型或使用返回类型推断。
四、与 Vuex 的对比
特性 | Pinia | Vuex |
---|---|---|
模块系统 | 自然模块化 | 需手动注册模块 |
Mutations | 无(直接修改) | 必须通过 mutations |
Actions | 支持同步/异步 | 同上 |
TypeScript | 原生支持 | 需额外配置 |
API 风格 | Composition 风格 | 选项式为主 |
体积 | 更小 | 相对较大 |
五、组合式 API 风格的基本语法
使用 defineStore()
并传入一个函数,该函数内部使用 ref
、computed
、watch
等响应式 API 来组织状态和逻辑。
// stores/counter.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// state: 使用 ref 定义响应式状态
const count = ref(0)
const name = ref('Vue User')
// getters: 使用 computed 定义派生状态
const doubleCount = computed(() => count.value * 2)
const welcomeMessage = computed(() => `Hello, ${name.value}!`)
// actions: 定义方法(可同步或异步)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function $reset() {
count.value = 0
}
async function fetchUser() {
const res = await fetch('/api/user')
const user = await res.json()
name.value = user.name
}
// 必须 return 暴露给外部使用的状态和方法
return {
// state
count,
name,
// getters
doubleCount,
welcomeMessage,
// actions
increment,
decrement,
fetchUser,
$reset
}
})
✅ 注意:在组合式 API 风格中,必须显式 return 你希望在组件中使用的变量和函数,否则无法访问。
六、在组件中使用组合式 Store
1. 使用 <script setup>
(推荐)
<script setup>
import { useCounterStore } from '@/stores/counter'
// 使用 store
const counter = useCounterStore()
</script>
<template>
<div>
<h2>{{ counter.welcomeMessage }}</h2>
<p>Count: {{ counter.count }}</p>
<p>Double: {{ counter.doubleCount }}</p>
<button @click="counter.increment">+1</button>
<button @click="counter.decrement">-1</button>
<button @click="counter.fetchUser">Fetch User</button>
<button @click="counter.$reset">Reset</button>
</div>
</template>
2. 解构 Store(保持响应性)
由于 counter.count
是一个 ref
,在模板中可以直接使用。但在 JS 中解构时,需要使用 storeToRefs
来保持响应性。
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
const counter = useCounterStore()
// ❌ 错误:直接解构会失去响应性
// const { count, doubleCount } = counter
// ✅ 正确:使用 storeToRefs
const { count, doubleCount, name } = storeToRefs(counter)
const { increment, decrement } = counter // actions 不需要 ref 包装
// 现在可以在 setup 中安全使用
console.log(count.value) // 0
</script>
🔥
storeToRefs
只包装 state 和 getters,不包装 actions,因为 actions 是方法。
Pinia 是 Vue 3 状态管理的未来。它通过简洁的 API、强大的 TypeScript 支持和灵活的架构,极大提升了开发效率和代码可维护性。
建议:
- 新项目直接使用 Pinia
- 老项目可逐步从 Vuex 迁移到 Pinia
- 结合插件生态(如持久化、日志)提升功能
Pinia 不仅是一个状态管理库,更是 Vue 3 开发哲学的体现:简洁、灵活、高效。