Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- state,驱动应用的数据源;
- view,以声明方式将 state 映射到视图;
- actions,响应在 view 上的用户输入导致的状态变化。
以下是一个表示“单向数据流”理念的简单示意:
但是上面这种单向数据流在一些特定条件下,也会表现出劣势;
当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:以及需要以下需求时
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态
对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。
对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。
所以为了应对以上问题,Vuex就诞生了
官方描述:Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式 (opens new window)就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
Vuex 的Api 文档流程图:
npm install vuex@next --save
import Vue from 'vue'
import Vuex from 'vuex' //引入Vuex
Vue.use(Vuex); // 应用Vuex到Vue实例上
const store = new Vuex.Store({
//这里面配置托管的数据状态
})
export default store;
import store from './store'
new Vue({
store,
render: h => h(App)
}).$mount('#app')
//直接定义一个 store ,store 是一个对象
let state = {
numberdata: 0
}
const store = new Vuex.Store({
//并把state 挂载给store 进行集中管理
state,
})
export default store;
<h1>{{ daname }}</h1> //使用计算属性
<h1>{{ $store.state.numberdata }}</h1> // 使用插值语法直接从store 身上读取
<script>
export default {
computed: {
daname() { //使用计算属性代理接收
return this.$store.state.numberdata;
},
},
};
</script>
目的:简短缩写 ,插值语法拿取 state 中的代码
<h1>{{ sum}}</h1>
<h1>{{ name}}</h1>
<script>
import { mapState } from "vuex";
export default {
computed: {
//这是我们自己定义使用计算属性代理接收,但是当数据过多时,拿取依然会显得劣势
//daname() {
// return this.$store.state.numberdata;
// },
...mapState({ //mapState调用传入一个对象,对象的 key 就是需要用到到属性名,value就是与之对应的 state 中的属性,类型是String。
sum: "numberdata",
name: "dataname",
}),
},
};
</script>
<h1>{{ dataname}}</h1>
<h1>{{ numberdata}}</h1>
<script>
import { mapState } from "vuex";
export default {
computed: {
...mapState(["dataname", "numberdata"]), //当我们想要用到的属性名 与 state 中的属性名,完全一致时,就可以更简化一点,调用写成数组的形式。
},
};
</script>
作用:用来处理改变数据状态前,对数据的状态筛选判断,以及开启异步任务的中转站。
组件中触发:
methods: {
add() { //事件中通过 $store.dispatch 触发 Action ,参数一为,要触发的 Action 中对应的事件名,参数二,为携带的参数
this.$store.dispatch("increase", 123);
},
},
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
let actions = {
increase(context , datnum) { //接收两个参数 datnum 为接收的参数。
mentet.commit("ADD", datnum);
}
// setTimeout(() => { 在actions 中开启异步任务
// mentet.commit("ADD", datnum)
// }, 5000)
}
const store = new Vuex.Store({ //同样需要把 actions 挂载到 store 上进行管理
state,
actions,
})
通过 mapActions 把 Actions 中的函数 映射到 methods 中,,详情可见下 mapMutations 同理!
methods: {
add(){ //默认写法
this.$store.dispatch("increase",123)
}
...mapActions(["increase"]), //数组映射写法
...mapActions({ add: "increase" }), //对象映射写法,
},
触发方式
let actions = {
increase(context , datnum) {
context.commit("ADD", datnum); // 在 actions 通过参数一 身上的 commit 触发
}
// setTimeout(() => {
// mentet.commit("ADD", datnum)
// }, 5000)
}
组件中
methods: { //也可以在组件中,直接触发 Mutation
add() {
this.$store.commit("ADD", this.num);
},
},
描述:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
let mutations = {
ADD(a, b) { //定义操作更改数据的具体方法(函数) 接受 state 作为第一个参数,参数一 可以直接拿到store 身上的数据,
a.numberdata += b;
}
}
const store = new Vuex.Store({ //同样需要把 mutations 挂载到 store 上进行管理 mutations
state,
actions,
mutations,
})
说明:如果在 Action 中没有业务逻辑处理,则可以不需要在 Action 中触发 mutations ,可以在组件中,直接触发对话 mutations ,把 mutations 中的函数,映射到 methods中
<button @click="add(100)">+</button>
<button @click="ADD(100)">+</button>
<button @click="newsum(100)">+</button>
methods: {
add() { //默认写法
this.$store.commit("ADD", this.num); //在组件中,直接触发 Mutation
},
//mapMutations 数组 写法
...mapMutations(["ADD"]); //可以通过 mapMutations 自动生成回调,使用数组 写法时,需保证 指定 事件名,和 Mutation 中的关联事件名一致。
// 对象写法,key 为 指定事件名,value 为 关联事件名。 mapMutations 生成事件身上,默认可以接收一个参数,在调用事件时,可以传递参数。
...mapMutations({
newsum: "ADD",
}),
},
注意:
如果在 mutations 中 更改数据时,用到了,外部传入的值, 那么在 mapMutations 生成的事件 调用时,必须要传入一个值,不然会把一个事件对象当成参数传递过去。
当需要对store 中的数据进行加工处理时,可以使用 getters 可以认为是 store 的计算属性
let getters = {
machining(state) { //Getter 接受 state 作为其第一个参数
return state.numberdata * 100; //返回被处理过后的数据。
}
}
const store = new Vuex.Store({
state,
actions,
mutations,
getters, //挂载到 store
})
组件中读取数据时
<h1>{{ $store.getters.machining}}</h1>
同上, mapGetters 使用和 mapState 同理
概述:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
将 State, Actions, Mutations, Getters 按照不同的业务分类,给分别抽离出去,为了防止,多模块之间的状态污染,需要在自己对应的状态中,开启一个命名空间
export default {
namespaced: true, //开启命名空间
state: {
numberdata: 0,
dataname: "store中的数据"
},
actions: {
increase(mentet, datnum) {
mentet.commit("ADD", datnum)
}
},
mutations: {
ADD(a, b) {
a.numberdata += b;
}
},
getters: {
machining(state) {
return state.numberdata * 100;
}
},
}
import Vue from 'vue'
import Vuex from 'vuex'
import Case from './modules/case';//再引入抽离出去的状态,
Vue.use(Vuex)
const store = new Vuex.Store({
modules: { //并把引入的 状态放到 modules下,进行 模块化托管
Case
}
})
export default store
//方法一:在 mapState, mapActions, mapMutations, mapGetters ,方法调用中,第一个参数,就可以指定,我们需要访问哪个模块下的数据状态,为String
<script>
import { mapState, mapActions, mapMutations, mapGetters } from "vuex";
export default {
computed: {
...mapState("Case", {
sum: "numberdata",
name: "dataname",
}),
...mapState("Case", ["dataname", "numberdata"]),
...mapGetters("Case", ["machining"]),
},
methods: {
...mapActions("Case", ["increase"]),
...mapActions("Case", { add: "increase" }),
...mapMutations("Case", {
newsum: "ADD",
}),
},
};
</script>
<script>
//使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数
import { createNamespacedHelpers } from "vuex";
const { mapState, mapActions, mapMutations, mapGetters } =
createNamespacedHelpers("Case"); //createNamespacedHelpers 函数调用,传入一个参数用来指定绑定哪个模块,然后能从返回值身上结构,拿到指定当前模块下的,辅助函数,
export default {
computed: {
...mapState({ //同理,由于 使用了 createNamespacedHelpers ,所以这里的,辅助函数,不用再传入第一个,指定模块参数了。
sum: "numberdata",
name: "dataname",
}),
...mapState(["dataname", "numberdata"]),
...mapGetters(["machining"]),
},
methods: {
...mapActions(["increase"]),
...mapActions({ add: "increase" }),
...mapMutations({
newsum: "ADD",
}),
},
};
</script>
开启模块化+命名空间后,在组件中如果不使用辅助函数,用默认方式读取,修改状态时,有些许变化。
computed: {
sum(){ // 读取 模块下的state 中的数据
return this.$store.state.Case.numberdata
},
name() { //读取getters 中的数据
return this.$store.getters["Case/machining"];
},
},
methods: {
newsum() { //触发 actions
this.$store.dispatch("Case/increase", 300);
},
add() { //触发 mutations
this.$store.commit("Case/ADD", 700);
},
},
其实 Vuex 一直都在围绕着 数据的 “共享” 一词展开,官方的 api 封装 已较完善;官方也比较推荐模块化开发,使得代码可维护性大大提高。