在使用Vue框架的时候,经常会面临着组件间传值的问题,这时候就需要使用Vuex作为其状态管理器。
简单回顾一下,传统的Vue组件常用的通信的方式有:
- 父子通信: 父→子传值(
props
),子→父传值(events($emit)
);父调用子方法ref;provide/indect
- 兄弟通信:
eventBus
中央事件总线,担任组件之间通信桥梁 - 跨级嵌套通信:
bus
;provide/indect
(Vue2.2 新增api),bus机制同上,子组件绑定方法,触发时发布eventBus
,每个子组件做订阅监控,触发在created里面的方法执行;provide / inject
API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
(关于详细的常用组件通信方式介绍,之后会写文章专门介绍~)
在大型的复杂项目中,可能会存在着多级组件的嵌套,需要实现一个组件更改某个数据,多个组件便可以自动获取更改后的数据进行业务逻辑处理,这时就轮到今天的主角Vuex闪亮出场了。
一、Vuex是什么?
Vuex就是一个专门为Vue.js应用程序开发的状态管理模式,类似于React中的Redux。
Vuex的组成部分:
- state, 驱动应用的数据源
- view, 以声明的方式将state映射到视图
- actions,响应在view上的用户输入导致的状态变化
当多个组件共享状态时,上述的单向数据流的简易性就会很容易被破坏:1)多个视图依赖于同一状态,来自不同视图的行为需要变更同一状态
Vuex的设计思想:
借鉴了Flux、Redux,将数据存放到全局的store,再将store
挂载到每个vue实例组件中,利用Vue.js的细粒度数据响应机制进行高效的状态更新
二、Vuex的使用
import Vuex from 'vuex';
Vue.use(Vuex); // 1.Vue的插件机制,安装Vuex
let store = new Vuex.Store({ // 2.实例化store,调用install方法
state:{
count: 1
},
getters,
modules,
mutations,
actions,
plugins
});
new Vue({ // 3.注入store,挂载Vue实例
store,
render: h => h(app)
}).$mount('#app')
Vuex只是一个状态管理器,存储的数据时响应式的,并不会保存,刷新之后就回到了初始的状态,如果想保存改变的数据,最好结合LocalStorage
(需要注意,Vuex的数据都是以数组形式保存,而Localstorage只支持字符串,所以记得使用JSON转换:Array → String : JSON.stringify()
;String → Array: JSON.parse()
)
let defaultCity = "上海"
try{ // 用户关闭了本地存储,在外层加一个try...catch
if(!defaultCity){
defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
}
}catch(e){}
export default new Vuex.Store({
state:{
city: defaultCity
},
mutations:{
changeCity(state, city){
state.city = city
try{
window.localStorage.setItem('defaultCity', JSON.stringify(state.city))
// 数据改变的时候拷贝一份到LocalStorage中
}catch(e){}
}
}
})
三、Vuex的原理
1.Vuex的原理:
Vuex实现了一个单向的数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation
进行,Mutation
同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action
,但 Action 也是无法直接修改 State
的,还是需要通过 Mutation
来修改 State
的数据。最后,根据 State
的变化,渲染到视图上。
2.Vuex中各个模块的详细介绍:
- State:页面状态管理容器 集中存储 Vue 组件中 data 对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用 Vue 的细粒度数据响应机制来进行高效的状态更新。
getter()
: store的计算属性,state对象读取方法,包含在render
中。Vue组件通过它读取全局state对象,getter返回的值会根据他的依赖被缓存起来,且只有当他的依赖值发生改变时才会被重新计算。
- Actions: 操作行为处理模块 组件中的
$store.dispatch('action 名称',data)
来触发,然后由commit()来触发mutation的调用,间接更新state(因此actions更像是一个装饰器,提交mutation但不直接变更state)。负责处理 Vue Components 接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台 API 请求的操作就在这个模块中进行,包括触发其他 action 以及提交 mutation 的操作。该模块提供了 Promise 的封装,以支持 action 的链式触发。dispatch()
: 操作行为触发方法,唯一能执行action
的方法
- Mutations: 状态改变操作方法由
actions
中的commit('mutation 名称')
来触发。Vuex 修改 state 的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些 hook 暴露出来,以进行 state 的监控等。commit()
: 状态改变提交方法,唯一能执行mutation
的方法 ,store.commit()
更改state存储的状态
- Module:store分割的模块:将store分成模块(modules)每个模块拥有自己的
state、mutation、action、getter
、甚至是嵌套子模块——从上至下进行同样方式的分割,从而提升代码的可维护性
action 类似于 mutation,区别在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作,而Mutation只能且必须是同步操作。
3.Vuex中两个重要的问题:
-
store
如何挂载注入到组件中? -
state
和getters
如何映射到各个组件实例中响应式更新状态?
Q1:store挂载注入组件
**A:**vuex是利用vue的mixin混入机制,在beforeCreate钩子前混入vuexInit方法,vuexInit
方法实现了store注入vue组件实例,并注册了vuex store的引用属性$store。
Q2:state和getters如何映射到组件实例中响应式更新状态
A: 通过分析源码中的resetStoreVM核心方法,可以发现:
- Vuex的state状态是响应式,是借助Vue的data是响应式,将state存入vue实例组件的data中
- Vuex的getters则是借助Vue的计算属性computed来实现数据实时监听
说了半天,Vuex还是比较适合逻辑偏复杂的应用,不然说实在难免会有些小题大做~
四、Vuex的高级使用
场景: 点击某个地方选择城市,所有涉及到城市的地方都变化
安装vuex: npm install vuex --save
新建src/store/index.js
,并在其中引用:
import Vue from 'vue'
import Vuex from 'vuex'
基本使用示例:
- 某组件中触发状态改变的函数
handleClick = function(city){
this.$store.commit('changeCity', city)
}
-
在store/index.js下面设置状态的改变
为了易于管理和维护,分离开state,mutations① store/index.js:
import Vue from 'vue' import Vuex from 'vuex' import state from './state' import mutations from './mutations' Vue.use(Vuex) export default new Vuex.Store({ state, mutations })
② store/state.js:
let defaultCity = '上海' try { if (localStorage.city) { defaultCity = localStorage.city } } catch (e) {} export default { city: defaultCity }
③ store/mutations.js:
export default { changeCity (state, city) { state.city = city try { localStorage.city = city } catch (e) {} } }
-
在组件中调用
this.$store.state.city
高级使用示例:
vuex还为我们提供了非常高级的api——mapState、mapMutations、mapActions
mapState
——把vuex中的数据映射到组件的computed
的计算属性中
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['city'])
}
}
建立了这样的映射之后,组件中使用数据时的this.$store.state.city
就可以替换为this.city
除了数组的映射,还可以实现对象的映射,eg:
computed: {
...mapState({
currentCity: 'city'
})
}
调用只需要this.currentCity
此外,还有简单的方法mapMutations
,用法同mapState
import { mapState, mapMutations } from 'vuex'
methods: {
handleCityClick (city) {
// dispatch派发一个名字为changeCity的action,配合vuex中的actions
// this.$store.commit('changeCity', city)
this.changeCity(city) // 使用mapMutations之后可以这样调用
this.$router.push('/')
},
...mapMutations(['changeCity'])
},
对于功能复杂的应用,可以使用Mudules
对代码进行拆分,实现vuex的高级使用
作为状态管理工具,Vuex采用集中式储存管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变换
Vuex并没有限制代码结构,但是,规定了一些需要遵守的规则:
1)应用层级的状态应该集中到单个的store对象中
2)提交 mutation是更改状态的唯一方法,并且这个过程是同步的
3)异步逻辑都应该封装到action里面