由于多个状态分散的跨越在许多组件和交互间各个角落,大型应用复杂度也经常逐渐增长。为了解决这个问题,Vue 提供 vuex:我们有受到 Elm 启发的状态管理库。vuex 甚至集成到 vue-devtools,无需配置即可访问时光旅行。
状态管理
状态的初始化
状态管理,我们应该并不陌生。
举个例子,超市里新进了一批商品,管理员给这些商品分类,建立索引,然后按照顺序放入货架的过程就是最简单的状态管理。
let goods1 = {
category: 'fruit',
name: 'apple',
quantity: 5
}
let goods2 = {
category: 'supplies',
name: 'toothbrush',
quantity: 5
}
let goods3 = {
category: 'clothes',
name: 'sweater',
quantity: 5
}
简单归类后 :
let shop = {
goods: {
fruit: [{ name: 'apple', quantity: 5 }],
supplies: [{ name: 'toothbrush', quantity: 5 }],
clothes: [{ name: 'sweater', quantity: 5 }]
}
}
这样,当我们需要某一商品时,很容易根据类目检索到这个商品 :
console.log(shop.goods.fruit.find(f => f.name === 'apple'))
//-> { name: 'apple', quantity: 5 }
状态的改变
当有顾客前来购买商品时,我们需要类似的操作来减少被购买商品的数量 :
shop.goods.fruit.find(f => f.name === 'apple').quantity --
然而在成千上万的交易量背后,你不知道这些商品被购买的详细情况,你甚至不知道上周卖出了多少苹果,你也就无从得知下周该进多少。
所以你需要一个账目来记录商品购买明细 :
let account = {
appleSold (value) {
console.log("apple sold " + value)
shop.goods.fruit.find(f => f.name === 'apple').quantity -= value
}
}
当卖出苹果时,POS机“滴”一声,记录生成了 :
account.appleSold (5)
//-> apple sold 5
最简单的store
于是,我们得到了一个最简单的store :
let shop = {
goods: {
fruit: [{ name: 'apple', quantity: 5 }],
supplies: [{ name: 'toothbrush', quantity: 5 }],
clothes: [{ name: 'sweater', quantity: 5 }]
},
account: {
appleSold (value) {
console.log("apple sold " + value)
shop.goods.fruit.find(f => f.name === 'apple').quantity -= value
},
funcN () { }
}
}
由此可知,状态管理可以帮助我们更友好的改变状态,同时,跟踪状态变化的轨迹。
Vue(x) er 须知
开始
Vuex 官方文档:
https://vuex.vuejs.org/zh-cn/getting-started.html
Vuex最核心的概念 :
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
下面对此拓展:
对象引用
下面这两段代码将输出什么? 先不要往下看, 自己写一下
let store = {
state: {
msg: "welcome"
}
}, copy = store.state;
store.state = {
hello: "world"
};
console.log(Object.keys(copy));
let store = {
state: {
msg: 'welcome'
}
}, copy = store.state;
store.state.hello = "world";
console.log(Object.keys(copy))
结果如下(如果你都答对了,那么理解和上手Vuex将会很轻松) :
//-> ["msg"]
//-> ["msg", "hello"]
提交和分发
vuex 只是一个工具,或许过了这段时间,过了这个项目,你就不会再用它。
我们要记住的是它留给我们的启示:
不要直接更改状态, 而是通过提交(commit)和分发(dispatch)的方法通知管理者改变对象状态,这是大型项目和复杂状态管理的最佳实践。
Vuex 核心概念
一个完整的 Vuex Store
/**
* index.js
*/
import axios from 'axios'
const store = new Vuex.Store({
state: {
counter: 0
},
getters: {
counter: state => state.counter
},
// 可处理异步请求, dispatch 触发
actions: {
askPermission ({commit}) {
axios.get('/url').then((res) => {
if(res.data.permission)
commit('addCounter')
}).catch((err) => {
console.log('Error: in process "Ask permission".\n Detailed: ' + err)
})
}
},
// 同步, commit 触发
mutations: {
addCounter (state) {
state.counter ++
}
}
})
PS: 仔细研究一下 dispatch & actions, commit & mutations, 是否有一种似曾相识的感觉?
Look, 看这对 emit & on (事件机制),同样的事件类型,同样的回调函数。
State
单一状态树
Vuex使用单一状态树,一个state对象包含全部应用层状态,使得一个应用只有唯一数据源(SSOT, Single Source of Truth)
这对模块化并不造成影响
state: {
moduleA: {
},
moduleB: {
}
}
Getter
state: {
prop: ''
}
你可以使用store.state.prop直接读取状态的值, 当然也可以使用Getter :
getters: {
prop = state => state.prop
}
使用Getter的好处在于,你可以从state中派生出一些状态 :
getters: {
prop = state => state.prop,
fixedProp = state => state.prop || '暂无'
}
Mutation
Vuex 中的 mutation 类似于事件,有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler),回调函数的接受state作为第一个参数,我们在这里修改状态(state)
state: {
counter: 0
},
mutations: {
addCounter (state) {
state.counter ++
},
addCounter (state, payload) {
state.counter += payload.value
}
}
通过 commit 通知状态变化
store.commit('addCounter')
store.commit('addCounter', {value: 1})
Action
类似于mutation,不同在于
- 只能通过 commit mutation 通知状态变化
- mutation 只能包含同步操作,而 action 可以包含异步操作(比如, 在这里可以执行ajax请求)
actions: {
askPermission ({commit}) {
axios.get('/url').then((res) => {
if(res.data.permission)
commit('addCounter')
}).catch((err) => {
console.log('Error: in process "Ask permission".\n Detailed: ' + err)
})
},
askPermission ({commit}, payload) {
axios.get('/url', { params:payload }).then((res) => {
if(res.data.permission)
commit('addCounter')
}).catch((err) => {
console.log('Error: in process "Ask permission".\n Detailed: ' + err)
})
}
}
通过 dispatch 通知状态变化
store.dispatch('askPermission')
store.dispatch('askPermission', { author: "lonelydawn" })
Module
Vuex 允许我们将store分割成模块,每个模块拥有自己的state, mutation, action, getter, 甚至是嵌套子模块 :
const store = new Vuex.Store({
modules: {
a: {
state: {},
mutations: {
addCounter(state) {}
},
actions: {},
getters: {}
},
b: {
namespaced: true, // 建立命名空间
state: {},
mutations: {
addCounter(state) {}
},
actions: {}
}
}
})
store.state.a
store.state.b
// 提交 给 模块 a 的 mutations
store.commit('addCounter')
// 提交 给 模块 b 的 mutations
store.commit('b/addCounter')
最后
Vuex 的基本用法已经介绍完了。
相关内容 :
官方文档: https://vuex.vuejs.org/zh-cn/
官方实例: https://github.com/vuejs/vuex/tree/dev/examples
在下列内容中, 我将 演示如何使用 vue + vuex 以及其他常用组件从入门到实战。