本文基于 Vue3 的 composition API 来展开 Vuex 和 Pinia 的用法比较
Pinia传送门
Vuex传送门
Vuex
状态管理的核心概念
- 状态- 驱动应用的数据源;
- 视图 - 以声明方式将状态映射到视图;
- 操作 - 响应在视图上的用户输入导致的状态变化
下面是源自Vuex 官网提供的,“单向数据流”的简单示意:
但是,我们的应用中遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
- 多个视图依赖于同一个状态;
- 来自不同的视图的行为需要变更同一个状态。
为了解决上述两个问题,Vuex 做了如下的优化,也是Vuex 的状态管理机制:
Vuex 把组件的共享状态抽取出来,以一个全局单例模式管理,在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在数的哪个位置,任何组件都能获取状态或者触发行为;Vuex 通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态之间的独立性。使我们的代码变得更结构化且更易维护。
Vuex 和单纯的全局对象的区别
Vuex 的核心就是 store(仓库),可以把它看成是一个容器,存储应用中大部分的状态(state),Vuex 和单纯的全局对象存在如下区别:
- Vuex 的状态存储是响应式的。当Vue 组件从 store 中读取状态时,store 发生变化,那么相应的组件也会得到更新;
- 你不能直接修改 store 中的状态,改变store 中的状态的唯一途径是调用 commit 触发 mutation,也就是说你的 Vue 组件中只写 commit ,不要直接调用 mutation 中的方法,这样的好处是可以方便的跟踪每一个状态的变化,有利于vuedevtool 调试
Vuex 的适用场景
适用于中大型的单页应用,如果你的应用足够简单,那么不建议使用 Vuex,一个简单的 store 模式就足够用了。
Vuex 的使用-最简单的实例
步骤一:安装 Vuex
npm install vuex@next --save
或者使用 yarn:
yarn add vuex@next --save
步骤二:创建一个 store
引用 Vuex 的官方demo
import { createApp } from 'vue'
import { createStore } from 'vuex'
// 创建一个新的 store 实例
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
}
})
const app = createApp({ /* 根组件 */ })
// 将 store 实例作为插件安装
app.use(store)
步骤三:在Vue 组件中的使用- 顺便了解一下 state
import { useStore} from "vuex";
const store = useStore();
//改变store 中某个状态的值,只需要
store.commit('increment')//通过 commit 触发状态变更,入参传入 mutation 中的函数名称即可
//获取store 中的某个状态的值
//一般情况下我们使用计算属性来实现
const myCount = computed(()=> store.state.count);//需要先 import {computed} from vue
console.log(store.state.count);//直接通过 store.state.某个状态名称 即可
注意:一定要使用 commit(‘mutation中的方法名’) 来改变 state 中的数据,不要通过store.state.count = 9 这种方式
Getter
关于state 想必通过上面的简单例子已经了解了。我们引出 getters 的概念:有时候我们需要从 store 中的 state 中的数据派生出一些状态,例如对列表进行过滤并计数功能,多个组件都要使用这个功能,如果在每个组件里都写一遍,肯定是冗余的,getters 则为我们解决了这个问题,它等效于 computed 可以任务是 store 的计算属性。
const store = createStore({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos (state) {
return state.todos.filter(todo => todo.done)
}
}
});
//在组件中调用的方式跟 state 一样,可以通过访问属性的形式访问这些值:
store.getters.doneTodos
Mutation
上文反复提到,修改 Vuex 的 store 中的状态的唯一方法是 提交(commit)mutation。是因为Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型(type)和一个回调函数(handler)。这个回调函数就是我们实际进行状态更改的地方,并且它接受 state 作为第一个参数,第二个参数可以传入自己需要传入的数据。
我们再来回顾一下上文 步骤二中的这个片段:
const store = createStore({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
Action
上面的 Mutation 和 State 看上去已经能够满足我们对 store 的使用了啊,为什么会有 Action 这个东西呢?原因在于 Vuex 把 Mutation 中的函数设计为了 同步函数,主要是出于 devtools 的考虑。那这样的话,你可以理解 Mutation 中是不能做接口请求回调了。于是Action 就来了,它的出现主要是帮助我们处理异步操作,根据异步回调的结果然后再通过 commit 来调用 mutation 去修改想要修改的状态。
注意:Action提交的是mutation,而不是直接更改状态
const store = createStore({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters
总结Action 和 Mutation 的区别:
- Action 提交的是 mutation,而不是直接更改状态;
- Action 可以包含任意异步操作
关于 Pinia 见下一篇文章:Vue3 中使用 Vuex 和 Pinia 对比之 Pinia的用法