一、问题html
能够想象到在简单的 父子
,子父
组件之间的通讯是很轻松的,经过 props
和 events
便可实现;可是每每咱们的应用可能不仅有这么简单的层级关系,在多层跨级组件若是经过 props
去传递,那意味着一层一层的往子组件传递,最终你可能不知道当前组件的数据最终来自哪一个父组件(固然你能够逆着方向一层一层往上找),经过 events
事件机制显然也存在着相似的问题。若是你以为这样也能够接受,你可能不须要 Vuex;但若是你在想有没有什么好的模式优雅的去解决,你能够继续阅读下面的部分。vue
二、vuexjava
vue提供了另一个相似 Redux 的解决方案 Vuex,一个集中式状态管理的库;也就是说,你可能不须要 Vuex,它只是对你应用状态进行管理的一个库。vuex
vuex关键词:缓存
集中式状态管理模式(注意是强调管理应用的全部组件的状态)异步
可预测(前提是以相应的规则做为保证)ide
三、集中式状态管理模式函数
在说集中式管理模式以前,咱们能够先来想一想常见的处理方式是怎样的,即每一个组件维护自身的数据和状态,自给自足,分而治之;其思路大体以下:工具
定义组件自身的初始数据
在组件内获取异步数据
根据数据渲染更新视图
但事情并不是那么完美,因为这种方式封装的组件的内部实现聚合了异步请求的数据和自身的状态,真正组装复用起来是存在必定问题的。好比:
在同一可视区域的冗余请求数
不一样层级组件的数据共享问题
集中式状态管理模式则以一个全局单例模式管理应用的状态,相似于全局对象,但不彻底同样。
Vuex 的状态管理存储是响应式的:就是当你的组件使用到了 Vuex 的某个状态,一旦它发生改变了,全部关联的组件都会自动更新相对应的数据。
不能直接修改 Vuex 的状态:修改 Vuex 的状态惟一途径是提交(commit) mutations 来实现修改。
Vue Components:Vue组件。HTML页面上,负责接收用户操做等交互行为,执行dispatch方法触发对应action进行回应。
dispatch:操做行为触发方法,是惟一能执行action的方法。
actions:操做行为处理模块。负责处理Vue Components接收到的全部交互行为。包含同步/异步操做,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操做就在这个模块中进行,包括触发其余action以及提交mutation的操做。该模块提供了Promise的封装,以支持action的链式触发。
commit:状态改变提交操做方法。对mutation进行提交,是惟一能执行mutation的方法。
mutations:状态改变操做方法。是Vuex修改state的惟一推荐方法,其余修改方式在严格模式下将会报错。该方法只能进行同步操做,且方法名只能全局惟一。操做之中会有一些hook暴露出来,以进行state的监控等。
state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局惟一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components经过该方法读取全局state对象。
流程:Vue组件接收交互行为,调用dispatch方法触发action相关处理,若页面状态须要改变,则调用commit方法提交mutation修改state,经过getters获取到state新值,从新渲染Vue Components,界面随之更新
集中式存储管理应用的全部状态,按照字面意思是将全部的状态都集中式管理,也就是存到 Vuex 的全局单一 store 中,显然咱们是不能这样去理解的,应该视应用场景而定的,大体也能够分为如下几种:
对于用户输入的状态,好比控制模态框的显示隐藏,咱们通常在组件内处理消化;对于须要须要跨组件通讯的,则能够存储在全局的 store 中,咱们能够将这一类状态称之为本地状态(local state)。
对于服务端传过来的数据状态,按照大多数的实践是存储在全局的 store 中,这样能够在任意的组件中均可以使用;固然,也能够只将多组件的共享的数据存储在全局的 store 中,单个组件须要的数据内部处理消化,组件销毁时对应的数据状态也会销毁。
说明:
(1)能够直接操做state
<a href="javascript:;" @click="$store.state.show = true">点击</a>
在严格模式下,不管什么时候发生了状态变动且不是由 mutation 函数引发的,将会抛出错误。
(2)若是在 mutations 里执行异步操做会发生什么事情 , 实际上并不会发生什么奇怪的事情 , 只是官方推荐 , 不要在 mutationss 里执行异步操做而已。
(3)多个 state 的操做 , 使用 mutations 会来触发会比较好维护 , 那么须要执行多个 mutations 就须要用 action 了
actions: { switch_dialog (context) { // 这里的context和咱们使用的$store拥有相同的对象和方法
context.commit('switch_dialog') // 你还能够在这里触发其余的mutations方法
},
(4)不少时候 , $store.state.dialog.show 、$store.dispatch('switch_dialog') 这种写法又长又臭 , 很不方便 , 咱们没使用 vuex 的时候 , 获取一个状态只须要 this.show , 执行一个方法只须要 this.switch_dialog 就好了 , 使用 vuex 使写法变复杂了 ?
<template>
<el-dialog :visible.sync="show"></el-dialog>
</template>
<script> import {mapState} from 'vuex' export default { computed:{ //这里的三点叫作 : 扩展运算符
...mapState({ show: state => state.dialog.show }), } } </script>
mapState
函数返回的是一个对象。
(5)、getter:返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被从新计算。
(6)、mapGetters
辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
import { mapGetters } from 'vuex' export default { // ...
computed: { // 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([ 'doneTodosCount', 'anotherGetter', // ...
]) } }
(7)、使用常量替代 Mutation 事件类型
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex' import { SOME_MUTATION } from './mutation-types' const store = new Vuex.Store({ state: { ... }, mutations: { // 咱们可使用 ES2015 风格的计算属性命名功能来使用一个常量做为函数名
[SOME_MUTATION] (state) { // mutate state
} } })
(8)mapMutations
你能够在组件中使用 this.$store.commit('xxx')
提交 mutation,或者使用 mapMutations
辅助函数将组件中的 methods 映射为 store.commit
调用(须要在根节点注入 store
)。
import { mapMutations } from 'vuex' export default { // ...
methods: { ...mapMutations([ 'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]), ...mapMutations({ add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
}) } }
(9)Action
Action 函数接受一个与 store 实例具备相同方法和属性的 context 对象,所以你能够调用 context.commit
提交一个 mutation,或者经过 context.state
和 context.getters
来获取 state 和 getters。当咱们在以后介绍到 Modules 时,你就知道 context 对象为何不是 store 实例自己了。
写法一:
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } } })
参数解构写法:
actions: { increment ({ commit }) { commit('increment') } }
(10)mapActions
你在组件中使用 this.$store.dispatch('xxx')
分发 action,或者使用 mapActions
辅助函数将组件的 methods 映射为 store.dispatch
调用(须要先在根节点注入 store
):
import { mapActions } from 'vuex' export default { // ...
methods: { ...mapActions([ 'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]), ...mapActions({ add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
}) } }
(11)module
const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
# 模块的局部状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
const moduleA = { state: { count: 0 }, mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态
state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } } }
一样,对于模块内部的 action,局部状态经过 context.state
暴露出来,根节点状态则为 context.rootState
:
const moduleA = { // ...
actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) { if ((state.count + rootState.count) % 2 === 1) { commit('increment') } } } }
对于模块内部的 getter,根节点状态会做为第三个参数暴露出来:
const moduleA = { // ...
getters: { sumWithRootCount (state, getters, rootState) { return state.count + rootState.count } } }
2019.3.22更新:
使用action 来分发 (dispatch) 事件通知 store 去改变,这样约定的好处是,咱们可以记录全部 store 中发生的 state 改变,同时实现能作到记录变动 (mutation)、保存状态快照、历史回滚/时光旅行的先进的调试工具。