Vue3状态管理工具(Vuex与Pinia)
以下内容基于B站教学视频总结,鉴于推荐先使用Vuex再讲解Pinia,所以笔记先从Vuex写起:
Vuex 是什么?(官方文档) | Vuex (vuejs.org)
安装
npm install vuex@next --save --save 选项可以将对应的包加入到package.json的依赖中,所以当其他用户部署时,可以自动安装依赖,并且可以在package.json中,自动读取使用的版本号。
作为插件引入
首先创建/store/index.js文件
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
import store from './store/index'
// 这里把 store 作为插件注入,后续组件中才能 useStore()
// 这一步是全局注册
// 少了这一步就拿不到仓库实例
createApp(App).use(router).use(store).mount('#app')创建完成后,在Vue+vite中,需要在main.js文件中,将vuex引入
编写Vuex主要代码
在/store/index.js中,需要实现createStore并抛出。
import { createStore } from "vuex";
const store = createStore({
//实例化仓库对象
state() {
return {
// 这里是全局共享状态,多个组件都可以读取
todoListArray : ['vue', 'vite', 'vuex']
}
},
// 仓库中的方法
mutations: {
addTodo (state, todo) {
// 约定 mutation 里做同步修改,便于追踪状态变更来源
// state 是当前仓库状态快照
// todo 是 commit 传入的 payload
state.todoListArray.push(todo)
},
}
})
export default store或者也可以将mutations等方法,编写为mutations.js再引入。 详细业务逻辑可以参考:vuex/examples/composition/todomvc/store/mutations.js at main · vuejs/vuex (github.com)
仓库中变量的读取
读取变量相对简单,只需要从"vuex"中import {useStore} from "vuex" ,并使用计算属性,读取对应的变量即可:
<script setup>
import {useStore} from "vuex"
import { computed } from 'vue'
const store = useStore();
const lists = computed(() => {
// 使用 computed 包一层,模板消费时保持响应式
// store.state 变化后
// lists 会自动重算
return store.state.todoListArray
})
</script>更新仓库中的变量
在/store/index.js文件中,我们曾创建过相关方法:
// 仓库中的方法
mutations: {
addTodo (state, todo) {
state.todoListArray.push(todo)
},
......
}那么更新时,我们也需要调用方法去更新,而不是直接修改仓库里的变量。 调用的方法为
store.commit('addTodo', valRef.value) // 同步 对应mutations方法
store.dispatch('addTodo', valRef.value) // 异步 对应actions方法传参中,valRef.value变量,对应着仓库mutations的addTodo方法中第二个传参,即todo。 完整代码为:
<script setup>
import { useStore } from 'vuex'
import {ref,computed} from "vue"
const valRef = ref("")
const store = useStore();
const lists = computed(()=>{
// 读取 Vuex 全局状态,页面会自动跟随更新
return store.state.todoListArray;
})
function add(e) {
if (valRef.value === "")
return;
// 通过vuex的相关方法传出参数
store.commit('addTodo', valRef.value) // 同步
// store.dispatch('addTodo', valRef.value) // 异步
// 不建议直接写 store.state.todoListArray.push(...),
// 会绕开 mutation 约定,后续排查变更来源会很痛苦
// commit 会进入 mutation
// devtools 也能记录这次变更
valRef.value = "" // 输入input框置空
}
</script>
<template>
<div>
<input type="text" v-model="valRef">
<button @click="add">提交</button>
</div>
</template>更多代码样例可参考:vuex/examples/composition/todomvc/components/TodoItem.vue at main · vuejs/vuex (github.com)
补充,选项式的编写方式可以参考:回顾一下Vue组件通讯的三种方式 - 掘金 (juejin.cn)
一个更贴近业务的 Vuex 场景
例如后台管理系统里。 筛选条件在列表页、导出弹窗、统计面板都要复用。 这时可以放进 Vuex 的 state。 每次改筛选只通过 mutation。 这样页面联动会更稳定,状态来源也更清楚。
Pinia
Pinia是Vuex状态管理工具的替代品 优势:
- 提供更加简单的api(去掉了mutation)
- 提供了组合式风格的api
- 去掉了modules的概念, 每一个store都是一个独立的模块
- 搭配TypeScript一起使用提供可靠的类型推断
从这里开始可以把理解切到另一个层面。 如果说 Vuex 更强调“流程规范”。 那么 Pinia 更强调“开发体验和组合式风格”。 两者都能做状态管理,差异主要在写法和维护习惯。
使用npm install pinia --save即可安装
Vue3+vite中引入
官方文档: 开始 | Pinia (vuejs.org)
// 首先, 创建一个pinia实例, 并将其传递给应用:
import {createPinia} from "pinia";
const pinia = createPinia()
// 和 Vuex 一样,仍然通过 app.use() 注入
createApp(App).use(pinia).mount('#app')创建Pinia的Store
创建目录store,并创建一个js文件
import {defineStore} from "pinia";
export const useTodoListArrayStore = defineStore('todoList', {
// id 要保证唯一,后续 devtools 会用它识别 store
state : ()=> ({
// state 写成函数
// 每次实例化都会拿到新对象
todoListArray : ['vue', 'vite', 'pinia']
}),
actions: {
addTodo(val) {
// Pinia 中 actions 既能写同步,也能写异步
// this 指向当前 store 实例
this.todoListArray.push(val);
}
}
})或者使用更加组合式api风格的写法 :
// 注意export抛出结果
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
// 组合式写法里,getter 通常直接用 computed
const doubleCount = computed(() => count.value * 2)
function increment() {
// 直接改 ref
// Pinia 会追踪到这次更新
count.value++
}
// 哪些状态和方法要对外暴露,就 return 哪些
return { count, doubleCount, increment }
})使用Pinia
pinia的使用要比vuex来着直观一些,鉴于我们已经在js文件中导出来我们的对象,我们可以在其他文件中,直接引入该对象,比如上文中的useTodoListArrayStore。
<script setup>
import {useTodoListArrayStore} from "../../store/piniaTodoList.js";
// 这个调用拿到的是当前 store 实例
const piniaStore = useTodoListArrayStore();
</script>
// 而使用时,与vuex不同,这里不需要computed属性,直接使用piniaStore对象即可,比如
<template>
<!-- 这里直接访问 store 状态,不需要再包 computed -->
<li v-for="(item,index) in piniaStore.todoListArray" :key="index">{{ item }}
</li>
</template>而写入或者修改对象,也十分容易,相同的,我们首先在其他文件中引入
import {useTodoListArrayStore} from "../../store/piniaTodoList.js";
const piniaStore = useTodoListArrayStore();引入完成后,可以直接通过piniaStore调用到上文中编写的actions相关方法 例如:
// 通过pinia的方式传参
// 和调用普通对象方法几乎一致,心智负担更低
// 这里不需要 commit
piniaStore.addTodo(valRef.value)一个更贴近业务的 Pinia 场景
例如电商首页。 用户信息、购物车数量、主题色偏好会在多个区域复用。 可以拆成多个 Pinia store。 例如 useUserStore、useCartStore、useThemeStore。 每个 store 只关注自己的职责,目录结构会更清晰。
$patch 的使用
$patch() 是 Pinia 中用来批量更新状态的方法。它允许传递一个对象,这个对象的属性会合并到 store 的现有状态中,从而更新对应的状态值。可以选择传递部分属性来更新部分状态,而不需要替换整个状态对象。
// 假设有一个 Pinia store
const counter = useCounterStore();
// 使用 $patch 更新 count 属性
// 适合一次改多个字段,避免多次触发更新逻辑
// 对象形式会做浅合并
counter.$patch({ count: counter.count + 1 });如果是一次更新多个值,也可以这样写。
counter.$patch({
count: counter.count + 1,
lastUpdateTime: Date.now(),
source: 'button-click'
})getters实现
使用computed() 方法, 返回一个函数的返回结果
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
// 类似getter
// count 改变时,doubleCount 会自动重新计算
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
// 注意需要return回去
return { count, doubleCount, increment }
})export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
},
})异步action
const list = ref([])
const getList = async () => {
// action 内可以直接写异步请求
// 失败时建议补 try/catch 和错误状态
// 成功后直接写回 store 状态
const res = await axios.get(API_URL)
list.value = res.data.data.channels
}
return {list, getList}更完整一点的写法通常是这样。
const list = ref([])
const loading = ref(false)
const errorMsg = ref('')
const getList = async () => {
loading.value = true
errorMsg.value = ''
try {
// 进入请求前先清空旧错误
const res = await axios.get(API_URL)
// 成功后写入列表
list.value = res.data.data.channels
} catch (err) {
// 失败后只更新错误态
errorMsg.value = '列表加载失败'
} finally {
// 无论成功失败都关闭 loading
loading.value = false
}
}
return { list, loading, errorMsg, getList }storeToRefs
使用storeToRefs()函数可以辅助保持数据(state+gatter)的响应式解构 如果使用如下代码对counterStroe进行解构赋值, 将导致响应式丢失
这里有一个容易踩坑的点。 storeToRefs 只会处理 state 和 getters。 不会把 actions 转成 ref。
- 方法可以直接通过对
counterStore进行结构而获得 const {increment} = counterStore
const {count, doubleCount} = counterStore如果想要实现响应式的解构, 则可以:
import {storeToRefs} from 'pinia'
// 这里返回的是 ref
// 解构后仍保持响应式连接
const {count, doubleCount} = storeToRefs(counterStore)Vuex 的优势在于流程清晰和历史项目沉淀。 Pinia 的优势在于写法直观和组合式契合度。
更多关于Pinia的内容,可以参考: 简介 | Pinia (vuejs.org)Pinia🍍还不会用?请看这个TodoList小Demo! - 掘金 (juejin.cn)
