目录
1.概念
Vuex是一个专为Vue.js应用程序而开发的状态管理模式,其采用集中式存储管理应用的所有组件状态,并以相应的规则来保证状态以一种可预测的方式发生变化,同时Vuex也集成到Vue的官方测试工具devtools extension,提供了一系列对应的测试功能;
通俗来讲,就是N个组件共享同一个变量,类似于多线程同步的场景,当这个变量发生变化后,凡是引用该变量的N个组件都会自动进行刷新操作,这里官网的一个图片比较形象:
当State发生变化,然后Vuex刷新到View上,然后Vue监听View上的变化,然后再修改State;
1.1.官网地址
https://vuex.vuejs.org/zh/
1.2.Vuex与Vue之间事件发布
State变化后,Vue.render渲染到Vue组件,当Vue组件发生事件后,通过dispatch分发到Vuex.actions,然后再由actions提交到mutations中,再由mutations挂载到state;
1.3.什么状态适合共享
多个组件需要共享的状态,例如:用户登录状态、用户名称、购物车、商品收藏等;
1.4.Vuex组成
state、getters、mutations、actions、modules
1.5.Vuex安装
npm install vuex --save
2.State
存放各种各样的“状态”,这里state又被叫做“单一状态树”,通俗来讲,就是state就是唯一数据源,确保数据源的唯一性,同时官方建议整个前段工程中,Vuex.Store实例也只有一个,避免多个Vuex.Store同时存在;(可以理解为Vue的data)
3.Getters
类似于Vue组件的计算属性,通俗来讲,如果state中的属性无法直接使用,例如:需要对属性进行加减乘数运算、或者拼接、分割运算等,此时就需要getters;(可以理解为Vue的computed)
这里有几个注意事项/使用场景:
①getters中定义的方法,其默认带有state参数,该state参数就是该实例的state对应的对象;
②以“getters”对象为参数,来传入自定义方法中;
③调用方负责传递参数;
4.Mutations
通过官网的说明,我们可以得知更新Vuex的state唯一方式:提交mutaion;
知识点1:commit提交mutations有2种方式:普通方式、type方式提交;
知识点2:当type方式commit的时候,所有的参数都会被Vuex当做对象来接受,Vuex称之为:载荷payload;
知识点3:鉴于mutations负责修改state的,Vuex中state是响应式的,当state中的数据发生改变时,Vue组件会自动更新;要想达到“响应式”的效果,必须遵循如下规则:
①提前在state中初始化所需的一切属性;
②当给state对象新增属性时,使用下面方式添加:
使用Vue.set(obj,’newProp’,newPropValue),或者,用新对象对旧对象重新赋值;(CLI4中,不用这2种方式,直接新增属性也能达到响应式效果)
知识点4:mutations中定义的方法,其默认带有state参数,该state参数就是该实例的state对应的对象;
知识点5:Vuex官方建议使用“常量类型”来为方法命名;
知识点6:Vuex要求mutations中的方法必须是同步方法;当我们使用devtools时,可以用devtools捕获mutation快照,如果是异步操作的话,devtools就不能很好的跟踪这个操作在什么时候会被完成;
(可以把mutations理解为组件的methods)
5.Actions
正是由于mutations无法编写异步方法,为了解决异步方法问题,这里发明了actions;
由于actions中定义的是异步任务,比较常见的就是下载APP,例如手机一边下载数据,一边更新进度条来告诉用户,当前的下载进度;
知识点1;相比getters/mutations方法,actions中定义的方法,其第一个参数是context;
知识点2;这里可以使用new Promise来优雅的实现类似这种一边下载,一边更新UI进度条的效果;
6.Modules
由于整个前端工程就一个Vuex实例,这就意味着很多内容都要写在state、getters、mutations等,例如:家电模块的state/getters/mutations等、床上用品模块的state/getters/mutations等、个人用品模块的state/getters/mutations等;
这就意味着,在一个state、getters、mutations标签内部会包含各种业务模块的信息,这就意味着一方面代码量庞大,另外一方面各种业务混杂;
为了解决这种混杂的问题,于是发明了modules;modules组成内容如下:
modules:{ 模块A: { state:{}, mutations:{}, actions:{}, getters:{}, modules:{} }, 模块B: { state:{}, mutations:{}, actions:{}, getters:{}, modules:{} }, ...................... } |
知识点1:module子模块mutations/getters方法参数中的state指的是当前子模块的state对象;
知识点2:module子模块actions方法参数中的context指的是当前子模块对象;
知识点3:要想访问上级Vuex对象中的内容,则可以通过context.rootGetters和context.rootState来获取,下面是打印的context对象:
7.项目代码
①新建src/store目录,下面新建对应的文件:
mutations-types.js
// 定义常量类型,这样就可以保证整个前端工程所引用的方法名都是统一的,不用每次都修改多个文件
let INCREMENT = 'increment'; let DECREMENT = 'decrement'; let INCREMENTCOUNT = 'incrementCount'; let INCREMENTCOUNT2 = 'incrementCount2'; let UPDATEINFO = 'updateInfo'; let AUPDATEINFO = 'aUpdateInfo'; let UPDATEMODULESINFO = 'updateModulesInfo'; let UPDATEMODULESINFOASYNC = 'updateModulesInfoAsync';
export { INCREMENT,DECREMENT,INCREMENTCOUNT,INCREMENTCOUNT2,UPDATEINFO,AUPDATEINFO,UPDATEMODULESINFO,UPDATEMODULESINFOASYNC } |
index.vue
<template>
</template>
<script> // 注册Vuex import Vue from 'vue' import Vuex from 'vuex' import * as MutationsType from './mutations-types'
Vue.use(Vuex);
const carModule = { state:{ type: '奥迪', name: 'A8', size: 2.0, weight: '2.0T' }, getters:{ // 这里的state,指的是carModule的state carName1(state){ return state.type + state.name + '-carName1'; }, // 这里的state,指的是carModule的state,这里的getters,指的是carModule的getters carName2(state,getters){ return getters.carName1 + '-carName2'; }, // 这里的state,指的是carModule的state,这里的getters,指的是carModule的getters,这里的rootGetters,指的是Vuex.Store的getters carName3(state,getters,rootState){ // console.log(rootState); return getters.carName2 + rootState.firstname + rootState.lastname + '-carName3'; } }, mutations:{ // 这里的state,指的是carModule的state [MutationsType.UPDATEMODULESINFO](state,name){ state.type = name; } }, actions:{ // 这里的context指的是carMoule [MutationsType.UPDATEMODULESINFOASYNC](context,payload){ console.log(context); console.log(context.rootGetters.username); console.log(payload); return new Promise((resolve, reject) => { setTimeout(() => { context.commit(MutationsType.UPDATEMODULESINFO,payload.type.name); console.log('carModule-Promise方式异步任务执行完毕,准备回调'); resolve('carModule-Promise方式异步任务回调'); },1000); }); } }, modules:{} }
export default new Vuex.Store({ state: { count: 100, firstname: '张', lastname: '三' }, mutations: { // mutations中定义的方法,其默认带有state参数,该state参数就是该实例的state对应的对象 // 这里使用常量类型 [MutationsType.INCREMENT](state){ state.count++; }, // 这里使用常量类型 [MutationsType.DECREMENT](state){ state.count--; }, // 调用方传递参数,这种调用方传递的参数称为载荷(payload),如果传递的参数比较多,可以先把参数组装成对象,然后把对象传递进来 // 这里使用常量类型 [MutationsType.INCREMENTCOUNT](state,count){ state.count += count; }, // 提交风格2:调用方使用type风格进行commit的时候,Vuex会把参数看做成一个对象,因此,想要获取对象中的属性就必须使用"."运算符 // 这里使用常量类型 [MutationsType.INCREMENTCOUNT2](state,payload){ state.count += payload.count; }, [MutationsType.UPDATEINFO](state,name){ state.firstname = name; } }, actions: { // 这里定义的方法,默认参数都是context,而不是之前的state // 这里,通过调用方传入一个object,而object的success和fail属性都是函数,来实现通知调用方actions的执行情况 // [MutationsType.AUPDATEINFO](context,payload){ // setTimeout(() => { // context.commit(MutationsType.UPDATEINFO,payload.message.name); // console.log(payload.success); // console.log(payload.fail); // },2000); // } // 这里进行了改进,相比用object,这里用了Promise.then方式来回调 [MutationsType.AUPDATEINFO](context,payload){ return new Promise((resolve, reject) => { setTimeout(() => { context.commit(MutationsType.UPDATEINFO,payload.message.name); console.log('Promise方式异步任务执行完毕,准备回调'); resolve('Promise方式异步任务回调'); },1000); }); } }, getters: { // getters中定义的方法,其默认带有state参数,该state参数就是该实例的state对应的对象 username(state){ return state.firstname + state.lastname; }, // 这里以“getters”对象为参数,来传入自定义方法中 usernameLength(state,getters){ return getters.username.length; }, // 调用方负责传递参数 usernameByZhang(state,getters){ // return function (str) { // return getters.username.startsWith(str); // } // 使用箭头函数进行简写 return str => getters.username.startsWith(str); } }, modules: { carModule } }); </script>
<style scoped>
</style> |
②在入口main.js中挂载Vuex:
main.js
import Vue from 'vue' import App from './App' import store from './store'
Vue.config.productionTip = false
/* 这里记得挂载store,否则this.$store失败,同时注意与import中大小写一致 */ new Vue({ el: '#app', store, render: h => h(App) }) |
③在自定义component中使用Vuex:
component1.vue
<template> <div> <h1>下面是modules模块的内容</h1> <h2>{{$store.state.carModule.name}}</h2> <h2>{{$store.getters.carName1}}</h2> <h2>{{$store.getters.carName2}}</h2> <h2>{{$store.getters.carName3}}</h2> <button @click="updateType('BMW')">更新modules.carModule中的type(同步修改)</button> <button @click="updateTypeAsyn('BMW')">更新modules.carModule中的type(异步修改)</button> <h2>{{message}}</h2> <h2>{{$store.state.count}}</h2> <h2>{{$store.getters.username}}</h2> <h2>{{$store.getters.usernameLength}}</h2> <h2>{{$store.getters.usernameByZhang('张')}}</h2> <button @click="addition">++</button> <button @click="addCount(5)">+5</button> <button @click="addCount2(5)">+5</button> <button @click="subtraction">--</button> <button @click="aUpdate('马六')">更新state中的firstname</button> </div> </template>
<script>
import * as MutationsType from '../store/mutations-types'
export default { name: "component1", data(){ return { message: '------------这是自定义组件1------------' } }, methods: { addition(){ // 这里使用常量类型 this.$store.commit(MutationsType.INCREMENT); }, subtraction(){ // 这里使用常量类型 this.$store.commit(MutationsType.DECREMENT); }, addCount(count){ // 提交风格1:调用方传递参数 // 这里使用常量类型 this.$store.commit(MutationsType.INCREMENTCOUNT,count); }, addCount2(count){ // 提交风格2:调用方传递参数 // 这里使用常量类型 this.$store.commit({ type: MutationsType.INCREMENTCOUNT2, count }); }, // 这里是通过object内部封装函数的方式来实现回调actions // aUpdate(msg){ // this.$store.dispatch(MutationsType.AUPDATEINFO,{ // message: {name:msg}, // success: () => console.log('操作成功'), // fail: () => console.log('操作失败') // }); // } // 这里进行了改进,相比用object,这里用了Promise.then方式来回调; aUpdate(msg){ this.$store.dispatch(MutationsType.AUPDATEINFO,{ message: {name:msg} }).then(data => console.log('回调结束,返回值是:' + data),err => console.log(err)); }, updateType(name){ // 这里,Vuex会遍历所有的mutations,直到找到一个方法名与MutationsType.UPDATEMODULESINFO一致的为止,不管其是位于modules中,还是Vuex.Store中 this.$store.commit(MutationsType.UPDATEMODULESINFO,name); }, updateTypeAsyn(name){ // 这里,Vuex会遍历所有的actions,直到找到一个方法名与MutationsType.UPDATEMODULESINFOASYNC一致的为止,不管其是位于modules中,还是Vuex.Store中 this.$store.dispatch(MutationsType.UPDATEMODULESINFOASYNC,{ type: {name:name} }) .then(data => console.log('carModule-回调结束,返回值是:' + data),err => console.log(err)); } } } </script>
<style scoped>
</style> |
App.vue
<template> <div id="app"> <h1>下面是modules模块的内容</h1> <h2>{{$store.state.carModule.name}}</h2> <h2>{{$store.getters.carName1}}</h2> <h2>{{$store.getters.carName2}}</h2> <h2>{{$store.getters.carName3}}</h2> <button @click="updateType('BMW')">更新modules.carModule中的type(同步修改)</button> <button @click="updateTypeAsyn('BMW')">更新modules.carModule中的type(异步修改)</button> <h2>{{message}}</h2> <h2>{{$store.state.count}}</h2> <h2>{{$store.getters.username}}</h2> <h2>{{$store.getters.usernameLength}}</h2> <h2>{{$store.getters.usernameByZhang('张')}}</h2> <button @click="addition">++</button> <button @click="addCount(5)">+5</button> <button @click="addCount2(5)">+5</button> <button @click="subtraction">--</button> <button @click="aUpdate('王五')">更新state中的firstname</button> <component1></component1> </div> </template>
<script> import component1 from './components/component1'
import * as MutationsType from './store/mutations-types'
export default { name: 'App', data(){ return { message: '------------这是App组件------------' } }, components:{ component1 }, methods: { addition(){ // 这里使用常量类型 this.$store.commit(MutationsType.INCREMENT); }, subtraction(){ // 这里使用常量类型 this.$store.commit(MutationsType.DECREMENT); }, addCount(count){ // 调用方传递参数 // 这里使用常量类型 this.$store.commit(MutationsType.INCREMENTCOUNT,count); }, addCount2(count){ // 提交风格2:调用方传递参数 // 这里使用常量类型 this.$store.commit({ type: MutationsType.INCREMENTCOUNT2, count }); }, // 这里是通过object内部封装函数的方式来实现回调actions // aUpdate(msg){ // this.$store.dispatch(MutationsType.AUPDATEINFO,{ // message: {name:msg}, // success: () => console.log('操作成功'), // fail: () => console.log('操作失败') // }); // } // 这里进行了改进,相比用object,这里用了Promise.then方式来回调; aUpdate(msg){ this.$store.dispatch(MutationsType.AUPDATEINFO,{ message: {name:msg} }).then(data => console.log('回调结束,返回值是:' + data),err => console.log(err)); }, updateType(name){ // 这里,Vuex会遍历所有的mutations,直到找到一个方法名与MutationsType.UPDATEMODULESINFO一致的为止,不管其是位于modules中,还是Vuex.Store中 this.$store.commit(MutationsType.UPDATEMODULESINFO,name); }, updateTypeAsyn(name){ // 这里,Vuex会遍历所有的actions,直到找到一个方法名与MutationsType.UPDATEMODULESINFOASYNC一致的为止,不管其是位于modules中,还是Vuex.Store中 this.$store.dispatch(MutationsType.UPDATEMODULESINFOASYNC,{ type: {name:name} }) .then(data => console.log('carModule-回调结束,返回值是:' + data),err => console.log(err)); } } } </script>
<style> </style> |
8.项目目录优化改造
一个Vuex有getters、mutations、actons、modules、state对象,可以把这些对象单独抽取成一个js文件,然后统一import即可,更新后的目录结构如下:
下面是各个抽取后的文件内容:
state.js
export default { count: 100, firstname: '张', lastname: '三' } |
mutations.js
import * as MutationsType from "./mutations-types";
export default { // mutations中定义的方法,其默认带有state参数,该state参数就是该实例的state对应的对象 // 这里使用常量类型 [MutationsType.INCREMENT](state){ state.count++; }, // 这里使用常量类型 [MutationsType.DECREMENT](state){ state.count--; }, // 调用方传递参数,这种调用方传递的参数称为载荷(payload),如果传递的参数比较多,可以先把参数组装成对象,然后把对象传递进来 // 这里使用常量类型 [MutationsType.INCREMENTCOUNT](state,count){ state.count += count; }, // 提交风格2:调用方使用type风格进行commit的时候,Vuex会把参数看做成一个对象,因此,想要获取对象中的属性就必须使用"."运算符 // 这里使用常量类型 [MutationsType.INCREMENTCOUNT2](state,payload){ state.count += payload.count; }, [MutationsType.UPDATEINFO](state,name){ state.firstname = name; } } |
actions.js
import * as MutationsType from "./mutations-types";
export default { // 这里定义的方法,默认参数都是context,而不是之前的state // 这里,通过调用方传入一个object,而object的success和fail属性都是函数,来实现通知调用方actions的执行情况 // [MutationsType.AUPDATEINFO](context,payload){ // setTimeout(() => { // context.commit(MutationsType.UPDATEINFO,payload.message.name); // console.log(payload.success); // console.log(payload.fail); // },2000); // } // 这里进行了改进,相比用object,这里用了Promise.then方式来回调 [MutationsType.AUPDATEINFO](context,payload){ return new Promise((resolve, reject) => { setTimeout(() => { context.commit(MutationsType.UPDATEINFO,payload.message.name); console.log('Promise方式异步任务执行完毕,准备回调'); resolve('Promise方式异步任务回调'); },1000); }); } } |
getters.js
export default { // getters中定义的方法,其默认带有state参数,该state参数就是该实例的state对应的对象 username(state){ return state.firstname + state.lastname; }, // 这里以“getters”对象为参数,来传入自定义方法中 usernameLength(state,getters){ return getters.username.length; }, // 调用方负责传递参数 usernameByZhang(state,getters){ // return function (str) { // return getters.username.startsWith(str); // } // 使用箭头函数进行简写 return str => getters.username.startsWith(str); } } |
Src/store/modules/carModule.js
import * as MutationsType from "../mutations-types";
export default { state:{ type: '奥迪', name: 'A8', size: 2.0, weight: '2.0T' }, getters:{ // 这里的state,指的是carModule的state carName1(state){ return state.type + state.name + '-carName1'; }, // 这里的state,指的是carModule的state,这里的getters,指的是carModule的getters carName2(state,getters){ return getters.carName1 + '-carName2'; }, // 这里的state,指的是carModule的state,这里的getters,指的是carModule的getters,这里的rootGetters,指的是Vuex.Store的getters carName3(state,getters,rootState){ // console.log(rootState); return getters.carName2 + rootState.firstname + rootState.lastname + '-carName3'; } }, mutations:{ // 这里的state,指的是carModule的state [MutationsType.UPDATEMODULESINFO](state,name){ state.type = name; } }, actions:{ // 这里的context指的是carMoule [MutationsType.UPDATEMODULESINFOASYNC](context,payload){ console.log(context); console.log(context.rootGetters.username); console.log(payload); return new Promise((resolve, reject) => { setTimeout(() => { context.commit(MutationsType.UPDATEMODULESINFO,payload.type.name); console.log('carModule-Promise方式异步任务执行完毕,准备回调'); resolve('carModule-Promise方式异步任务回调'); },1000); }); } }, modules:{} } |
最后是入口文件index.vue
<template>
</template>
<script> // 注册Vuex import Vue from 'vue' import Vuex from 'vuex' import * as MutationsType from './mutations-types' import getters from './getters' import mutations from './mutations' import actions from './actions' import state from './state' import carModule from './modules/carModule'
Vue.use(Vuex);
export default new Vuex.Store({ state, mutations, actions, getters, modules: { carModule } }); </script>
<style scoped>
</style> |