vuex 版本为^2.3.1
,按照我自己的理解来整理vuex。
关于 state
每个vuex 应用只有一个 store 实例,所以使用起来不会太复杂,对于定位错误状态和操作会很方便。
简单用法:在vuex 的计算属性中返回vuex 的状态
最基本的使用方式,通过在vue文件里面初始化 vuex 的 store 来进行操作 vuex 的数据:如下例子:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
})
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count
}
}
}
const app = new Vue({
el: '#app',
components: { Counter },
template: `
<div class="app">
<button @click="increment">+</button>
<button @click="decrement">-</button>
<counter></counter>
</div>
`
,
methods: {
increment () {
store.commit('increment')
},
decrement () {
store.commit('decrement')
}
}
})
- 每当
store.state.count
变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。
- 然而,这种模式导致组件依赖全局状态单例。在模块化的构建系统中,在每个需要使用 state 的组件中需要频繁地导入,并且在测试组件时需要模拟状态。
我以为,当项目发展到多个模块,多个组件和子组件混合的时候,在这种场合,单纯的值传递方式会很麻烦,因为组件或者模块之间的变量是独立的,对于一些全局使用的属性类似 token,cookie 之类的东西,或者是一些多个模块之间共享的属性。
所以 vuex 提供一个新的方式来将 vuex 的 store 存放到根组件下,通过 store 选项,将store从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)
):
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
})
const app = new Vue({
el: '#app',
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store
访问到。让我们更新下 Counter 的实现:
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
通过这种方式可以在实际应用中,将 vuex 的初始化分离出去其他模块化文件,然后在 vue初始化的引用相关 vuex 的文件即可,然后通过this.$store
全局调用 vuex 的 store 来实现数据存储。
我整理了一下完整的例子,这是 jsrun的例子:
jsrun.net/qWqKp
高级用法:mapState 辅助函数
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键。(其实就是自动转换了一些语法输出)
import { mapState } from 'vuex'
需要先引入才可以使用
mapState使用前后对比:
computed: {
count () {
return this.$store.state.count
}
}
computed: mapState({
count: state => state.count,
})
如果在大批量的类似这种的计算属性的话,使用 mapState 会更加便捷,而且不只是普通字符串,函数也可以转换,确实是比较方便的。
这里会有一个疑问,mapState到底做了什么事情,怎么将代码转为 vue 能识别的代码?所以需要参考 vuex 源代码中关于mapState的部分:(我找了当前 vuex 版本3.01的源代码:github.com/vuejs/vuex/…)
这是normalizeMap的代码:
function normalizeMap (map) {
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
}
这是mapState的代码:
var mapState = normalizeNamespace(function (namespace, states) {
var res = {};
normalizeMap(states).forEach(function (ref) {
var key = ref.key;
var val = ref.val;
res[key] = function mappedState () {
var state = this.$store.state;
var getters = this.$store.getters;
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
};
});
return res
});
- states参数在这里只有2种,一种是对象
{}
,一种是数组[]
。因为normalizeMap只做了这2个判断。
- states 会先通过normalizeMap进行序列化,转换为一个数组,数组元素是
{ key, val: key }
这种结构的。
- 这里需要注意一下:如果传入的是对象里面只有函数,如下例的
countPlusLocalState
,那么被 object.keys
获取的 key 是函数的名字。
[
{
count: state => state.count,
}
]
[
{
key,
val: key
},
]
{
count: state => state.count,
}
[
{
key,
val: map[key]
},
]
normalizeMap(states)
格式化之后会使用 forEach 遍历这个数组:
- 如果 val 是一个函数,则直接调用这个 val 函数,把当前 store 上的 state 和 getters 作为参数,返回值作为 mappedState 的返回值。
- 否则直接把
this.$store.state[val]
作为 mappedState 的返回值。
res["count"] = this.$store.state['state => state.count']
res["countPlusLocalState"] = this.$store.state['state => state.count']
- 最终能够形成一个新的数组对象结构,并返回。
- 因为最终生成的数据就是 computed 的数据格式,所以直接将这个对象传给 computed 就可以直接使用了。
这是mapState经过源代码解析前和后的对比:
computed: mapState({
count: state => state.count,
countAlias: 'count',
countPlusLocalState (state) {
return state.count + this.localCount
}
})
computed: {
count() {
return this.$store.state.count
},
countAlias() {
return this.$store.state['count']
},
countPlusLocalState() {
return this.$store.state.count + this.localCount
}
}
组件仍然保有局部状态
使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。
关于 getter
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性),就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
store.getters.doneTodos
getters: {
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount
getters: {
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2)
mapGetters 辅助函数
mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
参考 vuex 源代码,类似的处理也是跟...mapState
类似:
export function mapGetters(getters) {
const res = {}
normalizeMap(getters).forEach(({key, val}) => {
res[key] = function mappedGetter() {
if (!(val in this.$store.getters)) {
console.error(`[vuex] unknown getter: ${val}`)
}
return this.$store.getters[val]
}
})
return res
}
相比 mapState 来说,他简单一点。
唯一的区别就是它的 val 不能是函数,只能是一个字符串,而且会检查 val in this.$store.getters
的值,如果为 false 会输出一条错误日志。
关于三个点...mapstate 处理
其实三个点就是es6的扩展运算符。
可以将一个数组转为用逗号分隔的参数序列,也可以将对象进行展开,如果应用到 mapstate 身上就是:
需要注意:vue的 computed 是对象,里面的属性是对象属性。
computed: {
now: function () {
return Date.now()
}
...mapGetters([
'doneTodosCount',
'anotherGetter',
])
doneTodosCount:function(){},
anotherGetter:function(){}
}
参考: