Vuex 是一种状态管理模式,集中式存储和管理应用的所有组件的状态。
对比 Pinia
由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态。
// 创建一个 Counter 组件
// 每当 `store.state.count` 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。
const Counter = {
template: `{{ count }}`,
computed: {
count () {
return store.state.count
}
}
}
mapState:把state属性映射到computed身上。
当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余,我们可以使用 mapState
辅助函数帮助我们生成计算属性。
import { mapState } from 'vuex';
export default{
computed:{
...mapState("loginModule",["userinfo"])
}
}
就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
Getter 接受 state 作为其第一个参数,也可以接受其他 getter 作为第二个参数。
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
})
Getter 会暴露为 store.getters
对象,你可以以属性的形式访问这些值。
注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
通过让 getter 返回一个函数,来实现给 getter 传参,本质是利用闭包的形式。在你对 store 里的数组进行查询时非常有用。
注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
mapGetters
辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
import { mapGetters } from 'vuex'
export default {
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
])
}
}
如果你想将一个 getter 属性另取一个名字,使用对象形式
...mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
每个 mutation 都有一个字符串的事件类型和 一个 handler 回调函数。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法。
store.commit('increment')
你可以向 store.commit
传入额外的参数,即 mutation 的 载荷(payload)。
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读。
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
store 中的状态是响应式的,当我们变更状态时,监视状态的 Vue 组件也会自动更新。这意味着 mutation 也需要与使用 Vue 一样遵守一些注意事项:
// 1. 最好提前在 store 中初始化好所有所需属性。
// 2. 在对象上添加新属性时。
Vue.set(obj, 'newProp', 123) // 添加属性
state.obj = {...state.obj , newProp:123 } // 新对象替换老对象
使用 mapMutations
辅助函数将组件中的 methods 映射为 store.commit
调用。
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
}),
...mapMutations('loginModule',['setUser']), // 模块
}
}
Action 类似于 mutation,不同在于 Action 提交的是 mutation,而不是直接变更状态,而且 Action 可以包含任意异步操作。
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象。因此你可以调用 context.commit
提交一个 mutation,或者通过 context.state
和 context.getters
来获取 state 和 getters。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Action 通过 store.dispatch
方法触发:
store.dispatch('increment')
Actions 支持同样的载荷方式和对象方式进行分发
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
使用 mapActions
辅助函数将组件的 methods 映射为 store.dispatch
调用。
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。
每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
对于模块内部的 action,局部状态通过 context.state
暴露出来,根节点状态则为 context.rootState
:
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true
的方式使其成为带命名空间的模块。
当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
// loginModule.js
export default {
namespaced:true,
state:{
userinfo:{
user:'',
token:''
}
},
mutations:{
//设置用户信息
setUser(state,payload){
state.userinfo = payload;
},
//清空
clearUser(state){
state.userinfo={
user:'',
token:''
}
}
},
}
// index.js
import Vue from 'vue'
import Vuex from 'vuex'
import loginModule from './modules/loginModule'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
loginModule
}
})
在 store 创建之后,你可以使用 store.registerModule
方法注册模块。
import Vuex from 'vuex'
const store = new Vuex.Store({ /* 选项 */ })
// 注册模块 `myModule`
store.registerModule('myModule', {
// ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
相关方法:
store.state.myModule
和 store.state.nested.myModule
访问模块的状态。store.unregisterModule(moduleName)
来动态卸载模块。注意,你不能使用此方法卸载静态模块(即创建 store 时声明的模块)。store.hasModule(moduleName)
方法检查该模块是否已经被注册到 store。在严格模式下,无论何时发生了状态变更,只要不是由 mutation 引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
严格模式会深度监测状态树来检测不合规的状态变更,请确保在发布环境下关闭严格模式,以避免性能损失。
const store = new Vuex.Store({
// ...
strict: process.env.NODE_ENV !== 'production'
})
当在严格模式中使用 Vuex 时,在属于 Vuex 的 state 上使用 v-model
会比较棘手:
<input v-model="obj.message">
假设这里的 obj
是在计算属性中返回的一个属于 Vuex store 的对象,在用户输入时,v-model
会试图直接修改 obj.message
。在严格模式中,由于这个修改不是在 mutation 函数中执行的, 这里会抛出一个错误。
方法是使用带有 setter 的双向绑定计算属性:
mutations: {
updateMessage (state, message) {
state.obj.message = message
}
}
// login.vue
<input v-model="message">
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
在页面加载时读取sessionStorage里的状态信息,在页面刷新时将vuex里的信息保存到sessionStorage。
created() {
//在页面加载时读取sessionStorage里的状态信息
if (sessionStorage.getItem("store")) {
this.$store.replaceState(
Object.assign(
{},
this.$store.state,
JSON.parse(sessionStorage.getItem("store"))
)
);
}
//在页面刷新时将vuex里的信息保存到sessionStorage里
window.addEventListener("beforeunload", () => {
sessionStorage.setItem("store", JSON.stringify(this.$store.state));
});
}
vuex-along的实质也是localStorage 和 sessionStorage,只不过存储过程由第三方库完成;
配置 vuex-along
在 store/index.js
中最后添加以下代码:
import VueXAlong from 'vuex-along' //导入插件
export default new Vuex.Store({
//modules: {
//controler //模块化vuex
//},
plugins: [VueXAlong({
name: 'store', //存放在localStroage或者sessionStroage 中的名字
local: false, //是否存放在local中 false 不存放 如果存放按照下面session的配置
session: { list: [], isFilter: true } //如果值不为false 那么可以传递对象 其中 当isFilter设置为true时, list 数组中的值就会被过滤调,这些值不会存放在seesion或者local中
})]
});