Vuex 与 Pinia 状态管理
Vue 状态管理库的使用与原理对比
问题
解释 Vuex 和 Pinia 的状态管理原理,包括 State、Getter、Mutation、Action 的作用和使用方式。
解答
Vuex 基本概念
Vuex 是 Vue 2/3 的集中式状态管理库,采用单向数据流。
// store/index.js
import { createStore } from 'vuex'
const store = createStore({
// State: 存储应用状态
state() {
return {
count: 0,
todos: []
}
},
// Getters: 计算派生状态(类似计算属性)
getters: {
doubleCount(state) {
return state.count * 2
},
completedTodos(state) {
return state.todos.filter(todo => todo.done)
},
// 可以访问其他 getter
completedCount(state, getters) {
return getters.completedTodos.length
}
},
// Mutations: 同步修改状态的唯一方式
mutations: {
increment(state) {
state.count++
},
addTodo(state, todo) {
state.todos.push(todo)
},
setTodos(state, todos) {
state.todos = todos
}
},
// Actions: 处理异步操作,提交 mutation
actions: {
async fetchTodos({ commit }) {
const res = await fetch('/api/todos')
const todos = await res.json()
commit('setTodos', todos)
},
// action 可以调用其他 action
async addTodoAsync({ commit, dispatch }, todo) {
await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(todo)
})
commit('addTodo', todo)
}
}
})
export default store
<!-- 组件中使用 -->
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">+1</button>
<button @click="fetchTodos">加载</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
// 映射 state
...mapState(['count', 'todos']),
// 映射 getters
...mapGetters(['doubleCount', 'completedTodos'])
},
methods: {
// 映射 mutations
...mapMutations(['increment', 'addTodo']),
// 映射 actions
...mapActions(['fetchTodos'])
}
}
</script>
Pinia 基本概念
Pinia 是 Vue 3 推荐的状态管理库,API 更简洁,移除了 Mutation。
// stores/counter.js
import { defineStore } from 'pinia'
// Option Store 写法
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
todos: []
}),
getters: {
doubleCount: (state) => state.count * 2,
completedTodos: (state) => state.todos.filter(t => t.done)
},
actions: {
// 直接修改状态,无需 mutation
increment() {
this.count++
},
async fetchTodos() {
const res = await fetch('/api/todos')
this.todos = await res.json()
}
}
})
// Setup Store 写法(更灵活)
export const useCounterStore = defineStore('counter', () => {
// state
const count = ref(0)
const todos = ref([])
// getters
const doubleCount = computed(() => count.value * 2)
// actions
function increment() {
count.value++
}
async function fetchTodos() {
const res = await fetch('/api/todos')
todos.value = await res.json()
}
return { count, todos, doubleCount, increment, fetchTodos }
})
<!-- 组件中使用 -->
<template>
<div>
<p>Count: {{ counter.count }}</p>
<p>Double: {{ counter.doubleCount }}</p>
<button @click="counter.increment()">+1</button>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
const counter = useCounterStore()
// 解构时保持响应式
const { count, doubleCount } = storeToRefs(counter)
// actions 可以直接解构
const { increment } = counter
</script>
响应式原理
// 简化版 Pinia 实现原理
function defineStore(id, options) {
return function useStore() {
// 单例模式:同一个 store 只创建一次
if (!storeMap.has(id)) {
const store = createStore(id, options)
storeMap.set(id, store)
}
return storeMap.get(id)
}
}
function createStore(id, options) {
// 使用 reactive 创建响应式 state
const state = reactive(options.state())
// getters 转换为 computed
const getters = {}
Object.keys(options.getters || {}).forEach(key => {
getters[key] = computed(() => options.getters[key](state))
})
// actions 绑定 this 到 store
const actions = {}
Object.keys(options.actions || {}).forEach(key => {
actions[key] = options.actions[key].bind({ ...state, ...getters })
})
return reactive({
...state,
...getters,
...actions
})
}
Vuex vs Pinia 对比
| 特性 | Vuex | Pinia |
|---|---|---|
| Mutation | 必须通过 mutation 修改 | 无,直接在 action 中修改 |
| TypeScript | 支持较弱 | 完整类型推断 |
| 模块化 | 需要 modules 配置 | 天然多 store |
| 体积 | ~10KB | ~1KB |
| DevTools | 支持 | 支持 |
| Vue 版本 | 2 & 3 | 3(有 Vue 2 插件) |
关键点
- State: 响应式数据源,通过
reactive()实现 - Getter: 派生状态,基于
computed()实现缓存 - Mutation (Vuex): 同步修改状态的唯一方式,便于 DevTools 追踪
- Action: 处理异步逻辑,Pinia 中可直接修改状态
- Pinia 优势: 无 mutation、更好的 TS 支持、更小体积、更简洁 API
目录