官方解释javascript
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用 集中式存储 管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension
,提供了诸如零配置的 time-travel
调试、状态快照导入导出等高级调试功能。html
状态管理究竟是什么?vue
状态管理模式、集中式存储管理这些名词听起来就很是高大上,让人捉摸不透。其实你能够简单的将其当作把须要多个组件共享的变量所有存储在一个对象里面。而后将这个对象放在顶层的Vue实例中让其余组件可使用。那么多个组件是否是就能够共享这个对象中的全部变量属性了呢?是的java
若是是这样的话为何官方还要专门出一个插件Vuex呢?难道咱们不能本身封装一个对象来管理吗?固然能够,只是咱们要先想一想VueJS带给咱们最大的便利是什么呢?没错,就是响应式。若是你本身封装实现一个对象能不能保证它里面全部的属性作到响应式呢?固然也能够,只是本身封装可能稍微麻烦一些。不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就能够了。vuex
管理什么状态?npm
可是有什么状态是须要咱们在多个组件间共享的呢?若是你作过大型开放必定遇到过多个状态在多个界面间的共享问题。好比用户的登陆状态、用户名称、头像、地理位置信息等。好比商品的收藏、购物车中的物品等。这些状态信息均可以放在统一的地方对它进行保存和管理,并且它们仍是响应式的。promise
OK,从理论上理解了状态管理以后,让咱们从实际的代码再来看看状态管理。网络
理解app
咱们知道,要在单个组件中进行状态管理是一件很是简单的事情。异步
什么意思呢?咱们来看下面的图片。这图片中的三种东西,怎么理解呢?
State:不用多说,就是咱们的状态。(你姑且能够当作就是data中的属性)
View:视图层,能够针对State的变化,显示不一样的信息。(这个好理解)
Actions:这里的Actions主要是用户的各类操做:点击、输入等会致使状态的改变。
实现
在下面案例中,咱们有木有状态须要管理呢?没错就是counter。counter须要某种方式被记录下来,也就是咱们的State。counter目前的值须要被显示在界面中,也就是咱们的View部分。
界面发生某些操做时(咱们这里是用户的点击,也能够是用户的input),须要去更新状态,也就是咱们的Actions,这不就是上面的流程图了吗?
<template> <div id="app"> <div>当前计数:{{ counter }}</div> <button @click="counter+=1">+1</button> <button @click="counter-=1">-1</button> </div> </template> <script> export default { name: "app", data() { return { counter: 0 } } } </script> <style scoped></style>
Vue已经帮咱们作好了单个界面的状态管理,可是若是是多个界面呢?
也就是说对于某些状态(状态1/状态2/状态3)来讲只属于咱们某一个试图,可是也有一些状态(状态a/状态b/状态c)属于多个试图共同想要维护的。
全局单例模式(大管家)
npm install vuex --save
使用Vuex实现一下以前的计数器案例
store/index.js
import VueX from 'vuex' import Vue from 'vue' Vue.use(VueX) const store = new VueX.Store({ state: { count: 0 }, mutations: { increment(state) { state.count++; }, decrement(state) { state.count--; } } }) export default store
将store挂载到Vue实例中
咱们让全部的Vue组件均可以使用这个store对象,来到main.js文件导入store对象,而且放在new Vue中。这样在其余Vue组件中,咱们就能够经过this.$store的方式获取到这个store对象了。
//... import store from '@/store' //... new Vue({ store, render: h => h(App), }).$mount('#app')
组件中使用Vuex的count
<template> <div id="app"> <div>当前计数:{{ count }}</div> <button @click="increment">+1</button> <button @click="decrement">-1</button> </div> </template> <script> export default { name: "app", computed: { count() { return this.$store.state.count } }, methods: { increment() { this.$store.commit('increment') }, decrement() { this.$store.commit('decrement') } } } </script> <style scoped></style>
好的,上面就是使用Vuex最简单的方式了。
咱们来对使用步骤,作一个简单的总结:
new Vue
对象中,这样能够保证在全部的组件中均可以使用到。this.$store.state
属性的方式来访问状态this.$store.commit('mutation中的方法')
来修改状态注意:
① 咱们经过提交mutation的方式,而非直接改变store.state.count
② 这是由于Vuex能够更明确的追踪状态的变化,因此不要直接改变store.state.count的值。
咱们来对这几个概念一一理解
State单一状态树
Vuex提出使用单一状态树, 什么是单一状态树呢?英文名称是Single Source of Truth,也能够翻译成单一数据源。
可是它是什么呢?咱们来看一个生活中的例子。咱们知道在国内咱们有不少的信息须要被记录,好比上学时的我的档案,工做后的社保记录,公积金记录,结婚后的婚姻信息,以及其余相关的户口、医疗、文凭、房产记录等等(还有不少信息)。这些信息被分散在不少地方进行管理,有一天你须要办某个业务时(好比入户某个城市),你会发现你须要到各个对应的工做地点去打印、盖章各类资料信息,最后到一个地方提交证实你的信息无误。这种保存信息的方案不只仅低效并且不方便管理,以及往后的维护也是一个庞大的工做(须要大量的各个部门的人力来维护,固然国家目前已经在完善咱们的这个系统了)。
这个和咱们在应用开发中比较相似:若是你的状态信息是保存到多个Store对象中的,那么以后的管理和维护等等都会变得特别困难。因此Vuex也使用了单一状态树来管理应用层级的所有状态。单一状态树可以让咱们最直接的方式找到某个状态的片断,并且在以后的维护和调试过程当中也能够很是方便的管理和维护。
相似于组件中的计算属性computed
有时候咱们须要从store中获取一些state变化后的状态,好比下面的Store中获取学生年龄大于20的学生个数。
state: { students: [ {id: 110, name: 'polaris',age: 18}, {id: 111, name: 'rose',age: 22}, {id: 112, name: 'jack',age: 34}, {id: 113, name: 'tom',age: 11}, ] },
咱们能够在Store中定义getters
getters: { greateAgesCount: state => { return state.students.filter(s => s.age >= 20).length; } } //若是咱们已经有了一个获取全部年龄大于20岁学生列表的getters, 那么代码也能够这样来写 getters: { greateAgesStudents: state => { return state.students.filter(s => s.age >= 20); }, greateAgesCount: (state, getters) => { return getters.greateAgesStudents.length; } }
//组件中获取getters计算后的值 computed: { greateAgesCount() { return this.$store.getters.greateAgesCount } },
getters默认是不能传递参数的,若是但愿传递参数,那么只能让getters自己返回另外一个函数。好比上面的案例中咱们但愿根据ID获取用户的信息。
getters: { studentById: state => { return id => { return state.students.find(s => s.id === id) } } }
computed: { studentById() { return this.$store.getters.studentById(112) } }
Vuex的store状态的更新惟一方式:提交Mutation
Mutation主要包括两部分:
字符串的 事件类型type
一个 回调函数 handler
,该回调函数的第一个参数就是state。
mutation的定义方式:
//以下:increment就是事件类型,(state) {state.count++;}是回调函数 mutations: { increment(state) { state.count++; }, decrement(state) { state.count--; } }
在某个组件中经过mutation更新state值
increment: function() { this.$store.commit('increment'); }
在经过mutation更新数据的时候,有可能咱们但愿携带一些 额外的参数,参数被称为是mutation的载荷(Payload)
Mutation中的代码:
mutations: { increment(state,n) { state.count += n; }, decrement(state,n) { state.count -= n; } }
在某个组件中经过mutation更新state值
methods: { increment() { this.$store.commit('increment',2) }, decrement() { this.$store.commit('decrement',2) } }
可是若是参数不是一个呢?好比咱们有不少参数须要传递,这个时候咱们一般会以对象的形式传递,也就是payload是一个对象。
changeCount(state,payload) { state.count = payload.count }
changeCount() { this.$store.commit('changeCount',{count: 5}) }
上面的经过commit进行提交是一种普通的方式
Vue还提供了另一种风格, 它是一个包含type属性的对象
changeCount() { this.$store.commit({ type: 'changeCount', count: 100 }) }
Mutation中的处理方式是将整个commit的对象做为payload使用, 因此代码没有改变依然以下:
changeCount(state,payload) { state.count = payload.count }
Vuex的store中的state是响应式的,当state中的数据发生改变时Vue组件会自动更新。
这就要求咱们必须遵照一些Vuex对应的规则
提早在store中初始化好所需的属性
当给state中的对象添加新属性时,,使用下面的方式
Vue.set(obj, 'newProp', 123)
方式二:用新对象给旧对象从新赋值
state中的对象添加新属性的案例
咱们来看一个例子:当咱们点击更新信息时界面并无发生对应改变,如何才能让它改变呢?
import VueX from 'vuex' import Vue from 'vue' Vue.use(VueX) const store = new VueX.Store({ state: { info: { name: 'polaris', age: 18 } }, mutations: { updateInfo(state,payload) { state.info['height'] = payload.height } } }) export default store
<template> <div id="app"> <p>个人我的信息:{{info}}</p> <button @click="updateInfo">更新信息</button> </div> </template> <script> export default { name: "app", computed: { info() { return this.$store.state.info } }, methods: { updateInfo() { this.$store.commit('updateInfo',{height: 1.88}) } } } </script> <style scoped></style>
下面代码的方式一和方式二,均可以让state中的属性是响应式的
mutations: { // updateInfo(state,payload) { // state.info['height'] = payload.height // } updateInfo(state, payload) { //方式一 // Vue.set(state.info,'height',payload.height) //方式二 state.info = {...state.info, 'height': payload.height} } }
咱们也能够响应式的删除某个对象的属性如:
Vue.delete(state.info,'height')
概念
咱们来考虑一个问题,在mutation中咱们定义了不少事件类型(也就是其中的方法名称)。当咱们的项目增大时Vuex管理的状态愈来愈多,须要更新状态的状况愈来愈多,那么意味着Mutation中的方法愈来愈多。方法过多使用者须要花费大量的经历去记住这些方法甚至是多个文件间来回切换查看方法名称,甚至若是不是复制可能还会出现写错的状况。
如何避免上述的问题呢?在各类Flux实现中,一种很常见的方案就是 使用常量替代Mutation事件的类型 。咱们能够将这些常量放在一个单独的文件中方便管理以及让整个app全部的事件类型一目了然。
具体怎么作呢?咱们能够建立一个文件 mutation-types.js
, 而且在其中定义咱们的常量。
定义常量时咱们可使用ES2015中的风格,使用一个常量来做为函数的名称。
代码
一般状况下Vuex要求咱们Mutation中的方法必须是同步方法。
主要的缘由是当咱们使用devtools时,利用devtools帮助咱们捕捉mutation的快照,可是若是是异步操做那么devtools将不能很好的追踪这个操做何时会被完成。即若是Vuex中的代码咱们使用了异步函数,你会发现state中的info数据一直没有被改变由于它没法追踪到。因此一般状况下不要在mutation中进行异步的操做。
mutations: { updateInfo(state) { setTimeout(() => { state.info.name = "GG"; },1000) } },
前面咱们强调不要再Mutation中进行异步操做,可是某些状况咱们确实但愿在Vuex中进行一些异步操做,好比网络请求必然是异步的,这个时候怎么处理呢?
Action相似于Mutation,可是是用来代替Mutation进行异步操做的。
Action的基本使用代码以下
mutations: { updateInfo(state) { // setTimeout(() => { // state.info.name = "GG"; // },1000) state.info.name = "GG"; } }, actions: { actUpdateInfo(context) { setTimeout(() => { context.commit('updateInfo'); },1000) } }
context是什么?context是和store对象具备相同方法和属性的对象,也就是说咱们能够经过context去进行commit相关的操做,也能够获取context.state等。可是注意这里它们并非同一个对象,为何呢? 咱们后面学习Modules的时候再具体说。
这样的代码是否画蛇添足呢?咱们定义了actions,而后又在actions中去进行commit,这不是脱裤放屁吗?事实上并非这样,若是在Vuex中有异步操做那么咱们就能够在actions中完成了。
在Vue组件中, 若是咱们调用action中的方法,那么就须要使用dispatch,一样的dispatch也是支持传递payload
methods: { updateInfo() { // this.$store.commit('updateInfo'); this.$store.dispatch('actUpdateInfo'); } }
const obj = { name: 'why', age: 18, height: 1.88 }; //顺序可变 const {age, name, height} = obj; console.log(name);
在Actions中使用对象的解构写法
getters 和 mutations 固然也可使用对象的解构写法
actions: { actUpdateInfo({commit}) { setTimeout(() => { commit('updateInfo'); },1000) } }
不清楚promise的用法请回看第八章
引入
当咱们的store中异步操做执行结束后,是否可以提醒一下调用者已经成功执行了呢?
咱们能够这样实现:
actions: { actUpdateInfo(context,success) { setTimeout(() => { context.commit('updateInfo'); success(); },1000) } },
methods: { updateInfo() { this.$store.dispatch('actUpdateInfo',() => { console.log('执行成功!'); }); } }
可是这样就不能传入其余参数了,那咱们再换种写法!
actions: { actUpdateInfo(context,payload) { setTimeout(() => { context.commit('updateInfo',payload.message); console.log(payload.message); payload.success(); },1000) } },
methods: { updateInfo() { this.$store.dispatch('actUpdateInfo', { message: '我是携带的参数', success: () => { console.log('执行成功!'); } }); } }
虽然能够实现,可是回调的信息和携带的参数写到一块儿去了,这种作法是不够优雅的,下面咱们经过Promise实现!
使用Promise
前面咱们学习ES6语法的时候说过Promise常常用于异步操做。在Action中咱们能够将异步操做放在一个Promise中,而且在成功或者失败后调用对应的resolve或reject。
actions: { actUpdateInfo(context, payload) { return new Promise((resolve, reject) => { setTimeout(() => { context.commit('updateInfo'); console.log(payload); resolve('执行成功!'); }, 1000) }) } },
methods: { updateInfo() { this.$store.dispatch("actUpdateInfo", '我是携带的信息').then(res => { console.log(res); }); }, },
Module是模块的意思,为何在Vuex中咱们要使用模块呢?
Vue使用单一状态树,那么也意味着不少状态都会交给Vuex来管理。当应用变得很是复杂时store对象就有可能变得至关臃肿。为了解决这个问题Vuex容许咱们将store分割成模块(Module),而每一个模块拥有本身的state,mutations,actions,getters等。
咱们按照什么样的方式来组织模块呢?看下面代码
//注意:模块中mutation和getters接收的第一个参数state,context是局部状态对象。 const moduleA = { state: { name: 'polaris' }, mutations: { updateName(state) { state.name = 'GG'; } }, actions: { actUpdateName(context) { setTimeout(() => { context.commit('updateName') },1000) } }, getters: { fullName(state) { return state.name + "hahaha"; } } } const moduleB = { state: { name: 'rose' }, mutations: {}, actions: {}, getters: {} } const store = new Vuex.store({ modules: { a: moduleA, b: moduleB } })
//state,调用时必须加上模块名,不一样模块间能够有相同的值 this.$store.state.a.name //获取moduleA的状态中的值 this.$store.state.b.name //获取moduleB的状态中的值 //mutations,不一样模块间能够有相同的值可是不要这么写,由于外部会同时调用不一样模块的mutations方法 updateName() { this.$store.commit('updateName'); //依次去模块中找 } //getters,不一样模块间不能有相同的值,会报错 this.$store.getters.fullName //依次去模块中找 //actions,不一样模块有相同的mutations方法时,会同时调用不一样模块的mutations方法 actUpdateName() { this.$store.dispatch('actUpdateName') } //=> 总结:除了state,各个模块中的其余内容不要重名!