15.Vuex
Vuex
15.1 Vuex 理解
Vuex 是一个全局的共享存储区域,相当于是一个数据仓库
Vuex 是为了保存组件之间的共享数据而诞生的,如果组件之间要有共享数据,可以直接挂载到 Vuex 中,而不必通过父子组件直接的传值了,而私有的数据则不需要挂载到 Vuex 中,只有需要共享的数据才放在 Vuex 中,私有的数据只需要放在组件的 data 中即可
多组件共享状态的问题
多个视图依赖于同一状态
来自不同视图的行为需要变更同一状态
以前的解决办法:
a. 将数据以及操作数据的行为都定义在父组件
b. 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递)
- vuex 就是用来解决这个问题的
状态自管理应用
这个状态自管理应用包含以下几个部分:
state: 驱动应用的数据源
view: 以声明方式将 state 映射到视图
actions: 响应在 view 上的用户输入导致的状态变化(包含 n 个更新状态的方法)
以下是一个表示“单向数据流”理念的简单示意:
Vuex 的结构模型
15.2 核心概念
15.2.1 state
vuex 管理的状态对象
它应该是唯一的
const state = { xxx: initValue }
15.2.2 mutations
包含多个直接更新 state 的方法(回调函数)的对象
谁来触发: action 中的 commit('mutation 名称')
只能包含同步的代码, 不能写异步代码
const mutations = {
方法名 (state, {data1}) {
xxxxx // 更新 state 某个属性的操作
}
}
参数:
可选:{
state, // 等同于 `store.state`,若在模块中则为局部状态
rootState, // 等同于 `store.state`,只存在于模块中
commit, // 等同于 `store.commit`
dispatch, // 等同于 `store.dispatch`
getters, // 等同于 `store.getters`
rootGetters // 等同于 `store.getters`,只存在于模块中
}
{data1}: 参数,可以为任意类型,但最后都会包装成一个对象
15.2.3 actions
包含多个事件回调函数的对象
通过执行: commit()来触发 mutation 的调用, 间接更新 state
谁来触发:
组件中: this.$store.dispatch('action 名称', data)
data 为参数
- 可以包含异步代码(定时器, ajax)
const actions = {
action 名称 ({commit, state}, data) {
commit('对应mutations的名字', {data})
}
}
第一个参数:
可选:{
state, // 等同于 `store.state`,若在模块中则为局部状态
rootState, // 等同于 `store.state`,只存在于模块中
commit, // 等同于 `store.commit`
dispatch, // 等同于 `store.dispatch`
getters, // 等同于 `store.getters`
rootGetters // 等同于 `store.getters`,只存在于模块中
}
第二个参数: 可以为任意类型,但最后都会包装成一个对象
15.2.4 getters
包含多个计算属性(get)的对象
谁来读取: 组件中: $store.getters.xxx
使用
const getters = {
mmm(state) {
return;
},
};
注意:
只能写相等于 get()的方法,不能写 set()的功能
所以如果需要 set 的功能,必须到组件中写
15.2.5 modules
包含多个 module
一个 module 是一个 store 的配置对象
与一个组件(包含有共享数据)对应
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;
},
},
};
15.3 项目结构
Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
- 异步逻辑都应该封装到 action 里面。
只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。
对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
15.4 表单处理
当在严格模式中使用 Vuex 时,在属于 Vuex 的 state 上使用 v-model
会比较棘手:
<input v-model="obj.message" />
假设这里的 obj
是在计算属性中返回的一个属于 Vuex store 的对象,在用户输入时,v-model
会试图直接修改 obj.message
。在严格模式中,由于这个修改不是在 mutation 函数中执行的, 这里会抛出一个错误。
用“Vuex 的思维”去解决这个问题的方法是:给 <input>
中绑定 value,然后侦听 input
或者 change
事件,在事件回调中调用一个方法:
<input :value="message" @input="updateMessage" /> // ... computed: {
...mapState({ message: state => state.obj.message }) }, methods: { updateMessage
(e) { this.$store.commit('updateMessage', e.target.value) } }
下面是 mutation 函数:
// ...
mutations: {
updateMessage (state, message) {
state.obj.message = message
}
}
双向绑定的计算属性
必须承认,这样做比简单地使用“v-model
+ 局部状态”要啰嗦得多,并且也损失了一些 v-model
中很有用的特性。另一个方法是使用带有 setter 的双向绑定计算属性:
<input v-model="message" /> // ... computed: { message: { get () { return
this.$store.state.obj.message }, set (value) {
this.$store.commit('updateMessage', value) } } }
15.5 实例
15.2.1 主要功能对象
import Vue from "vue";
import Vuex from "vuex";
// 在一个模块化的打包系统中,必须显式地通过 Vue.use() 来安装 Vuex:
Vue.use(Vuex);
// 使用Vue.Store
export default new Vuex.Store({
// 状态对象
state: {
count: 0,
}, // 包含多个更新state函数的对象,mutations对象里的方法可以直接操作state对象
mutations: {
// 工程中一般这些模块都是分开文件写,所以传入state参数的写法比较好
// 如果有其他参数则可以往后写
INCREMENT(state) {
state.count++;
},
DECREMENT(state) {
state.count--;
},
}, // 包含多个对应事件回调函数的对象,actions对象里的方法用来接收组件信息,同时传递给mutations
actions: {
// 如果有其他参数则可以往对象后面写,称作载荷(payload)
increment({ commit }) {
commit("INCREMENT");
},
decrement({ commit }) {
commit("DECREMENT");
},
incrementIfOdd({ commit, state }) {
if (state.count % 2 !== 0) {
commit("INCREMENT");
}
},
incrementAsync({ commit }) {
// 在action里直接可以写异步代码
setInterval(() => {
commit("INCREMENT");
}, 1000);
},
}, // 包含多个getter计算属性的函数对象
getters: {
evenOrOdd: function(state) {
if (state.count % 2 !== 0) {
return "Odd";
} else {
return "Even";
}
},
},
});
15.2.2 组件绑定
<template>
<div>
<!-- 任何一个组件里都会有一个$store对象,里面有state,getters属性 -->
<p>Count {{ $store.state.count }} & count is {{ evenOrOdd }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>
</div>
</template>
<script>
export default {
computed: {
evenOrOdd: function() {
return this.$store.getters.evenOrOdd;
},
},
methods: {
increment() {
// dispatch()去找到对应actions里面的方法
// 参数为('对应的方法名',载荷)
this.$store.dispatch("increment");
},
decrement() {
this.$store.dispatch("decrement");
},
incrementIfOdd() {
this.$store.dispatch("incrementIfOdd");
},
incrementAsync() {
this.$store.dispatch("incrementAsync");
},
},
};
</script>
<style></style>
15.2.3 辅助函数
<template>
<div>
<!-- 任何一个组件里都会有一个$store对象,里面有state,getters属性 -->
<p>Count {{ count }} & count is {{ evenOrOdd }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>
</div>
</template>
<script>
// 这些分别为常用的组件绑定的辅助函数
import { mapGetters, mapActions, mapState } from "vuex";
export default {
computed: {
// 等价于mapGetters()返回值为{evenOrOdd(){return this.$store.getters['evenOrOdd']}}
...mapGetters(["evenOrOdd"]), // 等价于mapState()返回值{count(){return this.$store['count']}}
...mapState(["count"]),
},
methods: {
// 其中这三个方法也可以传入一个对象而非数组,例如:
// mapActions({Increment: 'increment'})
// 把 `this.Increment` 映射为 `this.$store.getters.increment`
// 意思就是给getters里的属性取一个不同于increment的名字
...mapActions([
"increment",
"decrement",
"incrementIfOdd",
"incrementAsync",
]),
},
};
</script>
<style></style>