Vuex 学习手册
Vuex摘要
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
详细教程请参考 vuex官方文档。
基础结构
const store = new Vuex.Store({
state:{
// 可存取的状态, 通过此接口可以暴露状态值
todos:[
{id:1, name:'Task A', done:true},
{id:2, name:'Task B', done:true},
{id:2, name:'Task C', done:false}
]
},
getters:{
getById(id){
return this.todos.filter(todo=>todo.id==id);
},
// 包装器
doneList(){
// 计算属性,返回todos中done==true的项目
return this.todos.filter(item=>item.done)
}
},
mutations:{
//
inc(state){
state.cnt++;
}
},
actions:{
add(){
// 触发更新事件修改状态值
this.dispatch('inc');
}
}
});
结构解析
state
state vuex中保存状态(键值),这些值类似于受保护的全局变量。state原则上不应该直接修改,其作为一个module存在于Vue应用的全局对象中。
- 一个vuex的模板代码
// 在spa通常这样来启用一个vuex
import Vue from 'vue'
import Vuex from 'vuex'
// App模板
import App from './App.vue';
// 挂载vuex组件
Vue.use(Vuex);
// 初始化一个Vuex对象,此过程通常放到独立的js当中
const store = new Vuex({
state:{},
mutations:{},
actions:{}
// etc
});
// 创建Vue对象
const app = new Vue({
el:'#app',
store,
// ... 其他项目
render:h=>h(App)
});
- App.vue 中调用vuex的示例
<template>
<!-- u structure -->
</template>
<script>
// 简要代码
export default {
name:'My Powerful Application',
computed:{
add(){
// this.$store 即返回vuex
return ++ this.$store.state.cnt;
}
}
// ...
}
</script>
<style>
/** something styles */
</style>
getters
getters 相当于java中的封装概念,即需要对状态进行初级包装时设定具有适当逻辑的方法来访问封闭state中的值。每次调用均相当于执行一次函数,因此不适合大量计算。
- 利用getters访问属性
store.getters.doneList();
store.getters.getById(1); // {id:1, ...}
- 利用mapGetters辅助函数访问属性
import {mapGetters} from 'vuex'
export default {
// ...
computed:{
// 直接返回列表
...mapGetters:([
getById,
doneList
]),
// 指定返回的键
mapGetters:({
byId:'getById',
done:'doneList'
})
}
}
mutation
mutation vuex中状态更改的入口,类似于触发一个事件,每个事件则对应一个调整方法,比如一个登陆行为可以触发修改token、应用state状态等。所有的state都应该提供一个修改的入口以便调用。mutation并非只是简单的修改一个状态值,同时还会触发相关的状态事件,比如同步ui等。
- 常见的mutation定义
const store = new Vuex({
// ...
mutations:{
// state 为当前vuex对象的state集合
SET_TOKEN:(state, token)=>{
state.code = token;
},
SET_AVATAR:(state, avatar)=>{
state.avatar = avatar;
}
}
});
// 未知地带调用, 状态应立即被修改
store.commit('SET_TOKEN', new_token);
// 对象风格
store.commit({
type:'SET_TOKEN',
new_token
});
- 注意事项
- 初始化vuex对象时就定义好所有的state项
- 临时增加属性的话会降低可读性,除非是有限范围内的逻辑否则还是定义时就确定
Vue.set(store, 'newKey', 'newValue'); // 没这么用过,推荐初始化后就不再调整了
- mutation 必须是 ++同步函数++,不能在其中执行异步操作。原因是:mutation被调用后会执行相应的状态捕捉,如果是异步的话,调用时并不会捕获到新的状态,这回导致在必要的判定或UI更新上出现不可预计的错误。这类似于数据库开发中遇到的脏读、幻读等情况。
- 利用mapMutations映射到vue中
import {mapMutations} from './store'; // store为自定义的vuex
//...
export default{
// 正常的姿势,注意是映射名是字符串
...mapMutations([
'SET_TOKEN',
'SET_AVATAR'
])
// 或者指定键名
...mapMutations({
setToken: 'SET_TOKEN',
setAvatar: 'SET_AVATAR'
})
}
action
action 提供了异步修改state的接口,这点与mutation不同。
- 常见的 action 定义
const store = new Vuex({
state:{
cnt:0
},
mutation:{
inc(state){
state.cnt ++;
},
add(state, num){
state.cnt += num;
}
},
actions:{
inc(context){
context.commit('inc')
},
// ES6中提供了参数简化的写法
incByNum({commit}, num){
// {commit} 映射了context中的commit方法, 等同于的store.commit,详见下文
commit('add', num);
},
// 如果需要的话这样也成
incWithLog({commit, state}, num){
commit('add', num);
console.log(state.cnt);
}
}
});
// 调用action时可传入的对象还有:
{
state, // 等同于 store.state, 如果在module中时则为局部状态
rootState, // 等同于 store.state, 只在module中可用
commit, // 等同于 store.commit
dispatch, // 等同于 store.dispatch
getters, // 等同于 store.getters
rootGetters // 等同于 store.getters 只存在于module中
}
- 利用分发调用 action
// 一般性
store.dispatch('inc');
// 带参数
store.dispatch('incByNum', 1);
// 一对象形式也没问题
store.dispatch({
type:'incWithLog',
num:2
});
- mapActions 映射
import {mapActions} from 'vuex';
export default{
...mapActions([
inc,
incByNum,
incWithLog
]),
// 指定键名
...mapActions({
inc:'inc',
incByNum:'incByNum',
incWithLog:'incWithLog'
})
}
- 异步action
// 搬运个示例
actions:{
actionA({commit}){
// 这里返回一个Promise对象
return new Promise((resolve, reject)=>{
setTimeout(()=>{
commit('someMutation');
resolve();
});
}, 1000)
}
}
// 完事你可以
store.dispatch('actionA').then(()=>{
// do something async
});
// async await, 即需要Promise被resolve后执行,如:
actions:{
async actionA:({commit}){
commit('someMutation', await doSomething())
}
// 这种情况下 doSomething 应该返回Promise对象并且触发了resolve才会相应someMutation方法
}
async await 这里说明下,async方法必须返回Promise对象,并且对象的状态被resolved后 await方法才能执行,且await修饰的函数必须出现在async内。
这里参考此文。
个人理解,这点相当于约束async函数唯一同步函数,不过是必须要等待resolve。
这里在搬运一参考文中的代码加深下理解:
//我们仍然使用 setTimeout 来模拟异步请求
function sleep(second, param) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(param);
}, second);
})
}
async function test() {
let result1 = await sleep(2000, 'req01');
let result2 = await sleep(1000, 'req02' + result1);
let result3 = await sleep(500, 'req03' + result2);
console.log(`
${result3}
${result2}
${result1}
`);
}
test();
//req03req02req01
//req02req01
//req01
// 需要说明下rejected状态的处理,即增加一个try/catch包裹
async function tryMe(){
try{
await doSometing(); // 返回一个 Promise 对象
} catch(e){
processError(e);
}
}
另外,ES6中的Promise对象详解请参考 《ECMAScript6入门》
module
module 分割vuex为多个模块,每个模块都可以拥有自己独立的特性。
- 一个平凡的module
const moduleA = {
state:{...},
mutations:{...},
actions:{...}
};
const moduleB = {
state:{...},
mutations:{...},
actions:{...}
};
// 构造一个vuex
const store = new Vuex({
modules:{
moduleA,
moduleB
}
// 说明下这里的简写相当于
modules:{
moduleA:moduleA,
moduleB:moduleB
// 或者重命名
a:moduleA,
b:moduleB
}
});
// 用一个
store.state.moduleA // moduleA状态
- 模块嵌套
// 不怎么用,直接搬运
const store = new Vuex({
modules:{
// 一级
account:{
namespace:true,
state:{...},
getters:{
profile(){} // ->getters['account/profile']
},
actions:{
login(){} // ->dispatch('account/login')
},
mutations:{
login(){} // ->commit('account/login')
}
},
// 二级
modules:{
// 集成父级命名空间
profile:{
state:{...},
getters:{
view(){} // ->getters['account/view']
}
},
posts:{
namespace:true,
state:{...},
getters:{
// 嵌套了二级
hot(){} // ->getters['account/posts/hot']
}
}
}
}
});
这里每当指定了namespace:true后就需要增加一级目录,否则就是平行世界。一般应用基本上用不到这么复杂的命名空间,过多的模块嵌套反而提升程序复杂性。
- 跨级状态调用
// ...
modules:{
moduleA:{
namespace:true,
getters:{
getA(state, getters, rootState, rootGetters){
getters.getB; // moduleA/getB
rootGetters.getB // getB
},
getB: state=>{...}
},
mutations:{
SET_TOKEN:(state, token){
state.token = token;
}
},
actions:{
actionA({dispatch, commit, getters, rootGetters}){
getters.getB // moduleA/getB
rootGetters.getB // getB
dispatch('actionB') // moduleA/getB
dispatch('actionB', null, {root:true}) // getB
// 注意,null指的是payload,也就是参数表
commit('SET_TOKEN') // moduleA/actionA
commit('SET_TOKEN', new_token, {root:true}) // actionA
},
actionB(ctx, payload){
// ...
},
actionC:{
root:true, // 强制设为root级方法
handle(namespacedCtx, payload){
// ...
// TODO 此需要测试下多级嵌套的问题
}
}
}
}
}
// ...
个人觉得跨级调用会产生很多可读性问题,看看示例知道意思就好
严格模式
严格模式下,任何不是由 mutation 函数引起的state改变都将报错
const store = new Vuex({
// ...
strict:true
// 与开发环境结合,注意安装 cross-env 插件
strict:process.env.NODE_ENV !== 'production'
})
表单处理
这里推荐一个双向绑定的示例
<input v-model="message"/>
<script>
// ..
export default{
name:'App',
computed:{
get(){
return this.$store.state.ff.message;
},
set(v){
// 调用store的mutation修改
this.$store.commit('updateMessage', v)
this.$store.dispatch('someAction')
}
}
}
</script>
看多了文档有些迷茫,整理下来留个底方便查阅。如果你也喜欢的话千万不要吝啬收藏哦~!