在快应用开发中使用 Vuex
什么是“状态管理模式”?
我们先来看一段官方代码:
new Vue({
// state 数据源
data () {
return {
count: 0
}
},
// view 视图
template: `
<div>{{ count }}</div>
`,
// actions 事件
methods: {
increment () {
this.count++
}
}
})
这是一个很简单的增长型计数功能页面,通过事件 increment
,实现 count
增长,然后渲染到界面上去。官方称作为「单向数据流」。
但是,当情况发生改变,例如现在有两个页面 A 和 B,并且还有以下两个要求:
- 要求它们都能对
count
进行操控。 - 要求 A 修改了
count
后,B 要第一时间知道,B 修改后,A 也要第一时间知道。
很容易想到,如果我们把数据源 count
剥离开来,用一个全局变量或者全局单例的模式进行管理,这样在任何页面都可以很容易的取到这个状态了,这就是 Vuex 背后的思想。
但是 vuex 和单纯的全局对象还是有区别的,官方的描述如下:
Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
核心概念
Vuex 主要由五部分组成:State、Getter、Mutation、Action 和 Module,本次主要介绍前四个部分以及一些辅助函数在快应用中的使用:
-
State vuex 使用 state 保存应用中使用到的所有状态,由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态
-
Getter 有时候,我们会发现 State 中的数据,并不是我们直接想要的,而是需要经过相应的处理后,才能满足我们的需求,这时就可以使用 Getter 对 State 中的数据进行处理后再返回。Getter 也可以在计算属性中返回。
-
Mutation 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串类型的事件类型 (type) 和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。注意,我们不能直接调用 mutation,Vuex 规定必须使用store.commit来触发对应 type 的方法。同时,使用 store.commit 时可以传递额外参数,官方称为「载荷」。最后,还有一点非常重要,Mutation 的回调函数必须是同步函数
-
Action Action 类似于 mutation,但是有几点不同:第一:Action 使用 dispatch 触发;第二:Action 提交的是 mutation,而不是直接变更状态;第三:Action 可以包含任意异步操作
-
辅助函数 辅助函数包括 mapState、mapGetters、mapMutations、mapActions,辅助函数能够帮助我们更优雅地使用 Vuex,例如:如果一个组件需要使用到多个状态,我们可能会写出如下的代码:
export default { ... computed: { a () { return store.state.a }, b () { return store.state.b }, c () { return store.state.c }, ... } }
这种写法当然是没问题的,但是总感觉不太简洁,此时,我们就可以借助辅助函数:
import { mapState } from "vuex"; export default { // ... computed: { ...mapState([ // 映射 this.a 为 store.state.a "a", "b", "c" ]) } };
借助 mapState,代码简洁了许多。同样的,其他几个辅助函数的使用方式也是类似的。
简单示例
要在快应用中使用 vuex 首先肯定要安装 vuex,但是由于快应用和 vue 的实现是有差异的,不能直接使用 vuex,需要对 vuex 的源码做一些处理。这里我们直接使用大佬的 quickapp-vuex,使用方式与 vuex 基本相同
npm install quickapp-vuex -S //npm安装
之后,我们就可以在项目中使用 vuex 了。首先,新建一个 store.js 文件:
import Vuex from "quickapp-vuex";
export default new Vuex.Store({
state: {
count: 1
},
getters: {
count(state) {
return "getter" + state.count;
}
},
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
}
}
});
之后,在 app.ux 中引入 store.js,并挂到全局对象上
import store from "path to store.js";
import Vuex from "quickapp-vuex";
Vuex.install(store);
之后我们就能在组件和页面中使用 vuex 了。注意,与在 vue 中使用 vuex 不同,在快应用中使用 vuex 需要包一层Vuex.Component,其他的和 vuex 用法类似
<template>
<div class="demo-page">
<text class="title">欢迎打开{{count}}</text>
<text class="btn" onclick="increment">+</text>
<text class="btn" onclick="decrement">-</text>
</div>
</template>
<script>
import {mapGetters, mapMutations, Component} from 'quickapp-vuex'
export default Component({
computed:{
...mapGetters(['count'])
},
...mapMutations(['increment', 'decrement'])
})
</script>
使用效果如下图:
实际使用--父子组件传值
当父子组件需要依赖同一个状态值时,若只有一个子组件,则可以使用 props 和发消息的方式(已经有点繁琐);如果,子组件又有子组件,子组件的子组件又有子组件(开始套娃)......若还是使用 props 和发消息的方式,复杂度将呈指数级增长。但是,借助 vuex,则能很优雅地处理这种组件层层嵌套的情况。
还是使用简单示例中计数器的例子,首先在父组件中引入子组件
//index.ux
<import name="test1" src="./test1.ux"></import>
<template>
<div class="demo-page">
<text class="title">父组件{{ count }}</text>
<div>
<text class="btn" onclick="increment">+</text>
<text class="btn" onclick="decrement">-</text>
</div>
<test1></test1>
</div>
</template>
<script>
import { mapGetters, mapMutations, Component } from "quickapp-vuex";
export default Component({
computed: {
...mapGetters(["count"])
},
...mapMutations(["increment", "decrement"])
});
</script>
然后在子组件中再引入子组件的子组件
//test1.ux
<import name="test2" src="./test2.ux"></import>
<template>
<div class="demo-page">
<text class="title">子组件{{ count }}</text>
<div>
<text class="btn" onclick="increment">+</text>
<text class="btn" onclick="decrement">-</text>
</div>
<test2></test2>
</div>
</template>
<script>
import { mapGetters, mapMutations, Component } from "quickapp-vuex";
export default Component({
computed: {
...mapGetters(["count"])
},
...mapMutations(["increment", "decrement"])
});
</script>
//test2.ux
<template>
<div class="demo-page">
<text class="title">子组件的子组件{{ count }}</text>
<div>
<text class="btn" onclick="increment">+</text>
<text class="btn" onclick="decrement">-</text>
</div>
</div>
</template>
<script>
import { mapGetters, mapMutations, Component } from "quickapp-vuex";
export default Component({
computed: {
...mapGetters(["count"])
},
...mapMutations(["increment", "decrement"])
});
</script>
基本上是几段差不多的代码的嵌套,但是已经能够说明问题,运行效果如下图:
可以看到,当点击一个层级的组件的按钮时,其他层级的组件的状态也相应发生改变,达到了我们想要的效果。
实际使用--兄弟组件传值
以往在快应用中,若涉及到兄弟组件之间传值,则需要借助父组件。一个子组件先将信息传给父组件,然后父组件再将信息传给另一个子组件,非常麻烦。借助 vuex,我们只需要将两个兄弟组件依赖同一个 state,则当其中一个组件修改 state 时,另一个组件的 state 自动更新,不再需要借助父组件。由于代码和运行结果与父子组件传值类似,就不再赘述了。