第十一节:Vuex
1、了解vuex
了解vuex之前先思考一下vue-router进行加载的组件,他们既不是父子组件,也不是非父子组件,而是毫无关联的组件。那么他们之间要如何通讯呢???
简单的方式就是创建一个组件之间都可以访问的公共仓库,这个仓库其实就是一个对象。这个对象包含变量,函数等参数。
store 模式:
- 创建一个仓库。
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { count:0 }, getters: { }, mutations: { }, actions: { }, modules: { } })
- 组件中读取仓库中的数据
<!-- getstore.vue --> <template> <div>{{ count }}</div> </template> <script> // 引入仓库对象 import store from 'store.js'; export default { data(){ return { count:store.count // 将仓库中的数据赋值给本地数据 } } } </script>
- 组件中修改仓库中的数据
<!-- setstore.vue --> <template> <button @click="add">++</button> </template> <script> // 引入仓库对象 import store from 'store.js'; export default { methods:{ add(){ store.count++;// 直接对仓库中的数据进行修改 } } } </script>
这样的方式简单便捷,适用于小型项目。但是想在中大型项目中,设置到的数据非常多,这样做可能会变得很麻烦。所以更好的在管理这些数据的的时候,vuex应运而生!
2、安装配置
npm 安装指令:
npm install --save vuex
插件配置
为了便于管理,在src下单独新建一个vuex的文件夹,以及index.js的文件,所有公用的配置写在index.js中 然后再main.js中引用。
vuex基本配置
// src/vuex/index.js import Vue from 'vue'; import Vuex from 'vuex'; // 注册插件 Vue.use(Vuex); // 定义状态仓库 const state = { count:0 // 一条状态 } // 定义状态方法,用于操作状态仓库中的数据 const mutations = { add(state){ state.count++;// 自增 }, reduce(state){ state.count++;// 自减 } } // 实例化store对象 export default new Vuex.Store({ state, mutations });
在
main.js
中导入store对象// main.js // The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue'; import App from './App'; import router from './router'; // 导入store对象 import store from './vuex'; Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', components: { App }, template: '<App/>', router, store// 注册 })
3、使用Vuex
基本使用(state,mutations)
获取状态:从仓库中取出数据
语法:
这时候在任意的子组件中通过 this.$store.state.* 就能获取存储在仓库中的数据
案例:
// Home.veu <template> <div> <p>{{count}}</p> </div> </template> <script> export default { compount:{ count(){ // 获取数据存储到本地属性的时候不能直接赋值,需要使用计算属性来检测数据变化 return this.$store.state.count; } } } </script>
修改数据:当需要对数据进行更新的时候,必须通过
this.$store.commit(mutationName[ ,args...])
调用mutations里面定义方法的方式,而不是对数据进行直接的修改。这样式为了能够跟踪到数据的变化。// Home.veu <template> <div> <button @click="$store.commit('add')">+</button> <button @click="$store.commit('reduce')">-</button> <p>{{count}}</p> </div> </template> <script> export default { data () { return { // count:this.$store.state.count } }, computed:{ count(){ return this.$store.state.count; } } } </script>
- 这时候在任意组件中使用仓库中的数据的时候,都会同步发生改变。
// User.vue <template> <div> <p>{{$store.state.count}}</p> </div> </template>
commit
方法能够传递参数,可以在配置文件的 mutations 配置的方法中接收。但是需要注意的是,mutations 配置的方法中,第一个参数为 state 对象,正真的参数需要从第二参数器开始获取。// Home.veu <template> <div> <button @click="$store.commit('add')">+</button> <button @click="$store.commit('reduce')">-</button> <!-- 传递参数 --> <input type="text" @change="$store.commit('input',$event.target.value)" /> <p>{{count}}</p> </div> </template> <script> export default { computed:{ count(){ return this.$store.state.count; } } } </script> vuex/index.js import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const state = { count:0 } const mutations = { add(state){ state.count++; }, reduce(state){ state.count--; }, input(state,val){ console.log(state);// state 对象 state.count = val;// 获取到的参数 } } export default new Vuex.Store({ state, mutations });
mapState:当需要导入多个状态属性到本地的时候重复的去写计算器属性非常麻烦,所以vuex提供了一个 mapState 的辅助方法
在需要使用的组件中导入
mapState
方法使用
mapState
方法,该方法返回一个对象,参数传入一个对象,或者一个数组。对象的属性为需要注册的属性。// Home.veu <template> <div> <button @click="$store.commit('add')">+</button> <button @click="$store.commit('reduce')">-</button> <p>{{count}}</p> </div> </template> <script> import {mapState} from 'vuex';// 1. 导入 export default { // 2. 传递一个对象值 computed:mapState({ count:store=>store.count,// 设置函数为值,函数的第一个参数为store对象 count:'count', // 设置字符串,为 store=>store.count 的简写格式 count:function(){ console.log(this); // 函数内部的this指向组件实例对象 } }) // 2. 传递一个数组 computed:mapState(['count']) // 等同于 count:this.state.count } </script>
当计算属性中需要注册其他非 store 中的数据时,需要把 mapState 返回的对象进行合并,返回一个新的对象.
Object.assign()
:合并参数中的对象,返回一个新的对象// Home.veu <template> <div> <button @click="$store.commit('add')">+</button> <button @click="$store.commit('reduce')">-</button> <p>{{ count }}</p> <p>{{ x }}</p> </div> </template> <script> import {mapState} from 'vuex'; export default { computed:Object.assign( // 本地对象 { x(){return 'xxxxx'} }, // store对象 mapState({ count:'count' }) ) } </script>
...
:es6展开运算符export default { computed: { x(){return 'xxxxx'}, // store对象 将store对象展开添加到父对象中 ...mapState({ count:'count' }) } }
getter、action:vuex中另外的两个核心概念。
getters:vuex中的计算器属性
getter可以和计算器属性一样监听 state 的中的某个属性然后同步的计算更新返回新的计算后数据 // vuex/index.js import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const state = { count:0, } const mutations = { add(state){ state.count++; }, reduce(state){ state.count--; } } // getter 对象 const getters = { doubleCount(state){ return state.count * 2;// 将 count 的值*2 } } export default new Vuex.Store({ state, mutations, getters// 注册getter });
通过
store.getters.*
获取getter的数据// Home.veu <template> <div> <button @click="$store.commit('add')">+</button> <button @click="$store.commit('reduce')">-</button> <p>{{count}}</p> <!-- 获得 count乘2 的数值 --> <p>{{dobuleCount}}</p> </div> </template> <script> export default { computed:{ count(){ return this.$store.state.count; }, doubleCount(){ return this.$store.getters.count;// 获取getter数据 } } } </script>
action:action和mutation类似,优点在于可以执行异步操作(mutation里是不能执行异步操作的)。使用的方式和 mutation 基本一致但是,最大的不同点在于,更新数据是在 action 注册的函数里面调用commit来实现的
// vuex/index.js import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const state = { count:0, } const mutations = { add(){ state.count++; }, reduce(){ state.count--; } } const getters = { dobuleCount(){ return state.count * 2; } } // action对象 const actions = { asyncAdd(context){ setTimeout(()=>{ context.commit('add');// },1000) } } export default new Vuex.Store({ state, mutations, getters, actions // 注册action });
通过
store.dispatch()
触发// Home.veu <template> <div> <button @click="$store.commit('add')">+</button> <button @click="$store.commit('reduce')">-</button> <!-- 触发action 1s 后更新数据 --> <button @click="$store.dispatch('asyncAdd')">+(async)</button> <p>{{count}}</p> </div> </template> <script> export default { computed:{ count(){ return this.$store.state.count; } } } </script>
4、vuex模块系统
如果使用单一的状态对象,当应用比较庞大的时候,会变得非常臃肿难以维护。所以当很多模块需要进行维护的时候,可以将store划分为单独的子模块,子模块中还可以继续包含子模块
4.1、基础模块
案例:
// 定位模块
// vuex/index.js
// 子模块 a
const moduleA = {
state:{count:0},
mutations:{
add(state){
state.count ++;
}
},
getters:{
doubleCount(state){
return state.count * 2;
}
},
actions:{
asyncAdd(context){
setTimeout(()=>{
context.commit('add');
},1000);
}
},
modules:{
childA:{// 子模块 a 的子模块
state:{
str:'hello'
}
}
}
}
// 子模块 b
const moduleB = {
state:{count:10},
mutations:{
add(state){
state.count ++;
}
},
getters:{
doubleCountB(state){
return state.count * 2;
}
},
actions:{
asyncAdd(context){
setTimeout(()=>{
context.commit('add');
},1000);
}
}
}
export default new Vuex.Store({
modules:{// 注册子模块
moduleA,
moduleB
}
});
-
读取state
<template> <div id="app"> <p>a-count---{{ $store.state.moduleA.count }}</p> <p>b-count---{{ $store.state.moduleB.count }}</p> </div> </template>
-
读取getter:读取getter的时候需要注意,目前的情况下和非模块的方式一样的去读取就可以了,当有不同模块中出现相同的getter的时候则会报错来保证唯一性。
<template> <div id="app"> <p>a-doubleCount---{{ $store.getter.doubleCount }}</p> <p>b-doubleCount---{{ $store.getter.doubleCountB }}</p> </div> </template>
-
触发mutation和action:同样,目前情况下,触发方式和非模块方式一致。且触发所有同名mutation或action
<button @click="$store.commit('add')">+</button> <button @click="$store.dispatch('asyncAdd')">+(async)</button>
4.2、命名空间
以上的方式感觉混乱,完全没有模块的概念。模块之间是否能够单独的触发mutation action? 单独的获取设置 getter 又不会产生冲突?!这时候我们只需要打开一个开关,在模块中添加一个属性
namespaced:true
这时候,模块就是一个单独的空间。
- 模块设置
// vuex/index.js
// 子模块 a
const moduleA = {
namespaced:true,// 设置为单独的命名空间
state:{count:0},
mutations:{
add(state){
state.count ++;
}
},
getters:{
doubleCount(state){
return state.count * 2;
}
},
actions:{
asyncAdd(context){
setTimeout(()=>{
context.commit('add');
},1000);
}
},
modules:{
childA:{// 子模块 a 的子模块
state:{
str:'hello'
}
}
}
}
// 子模块 b
const moduleB = {
namespaced:true,// 设置为单独的命名空间
state:{count:10},
mutations:{
add(state){
state.count ++;
}
},
getters:{
doubleCountB(state){
return state.count * 2;
}
},
actions:{
asyncAdd(context){
setTimeout(()=>{
context.commit('add');
},1000);
}
}
}
export default new Vuex.Store({
modules:{// 注册子模块
moduleA,
moduleB
}
});
-
读取getter:这时候读取getter基本方式不变,但是属性名要带上模块前缀
<template> <div id="app"> <p>a-doubleCount---{{ $store.getter['moduleA/doubleCount'] }}</p> <p>b-doubleCount---{{ $store.getter['moduleB/doubleCount'] }}</p> </div> </template
-
触发mutation和action:同样带上模块前缀
<button @click="$store.commit('moduleA/add')">+</button> <button @click="$store.dispatch('moduleB/asyncAdd')">+(async)</button>