Vuex 学习手册

作者: Nomandia | 来源:发表于2018-04-27 19:15 被阅读0次

    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应用的全局对象中。

    1. 一个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)
    });
    
    1. 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中的值。每次调用均相当于执行一次函数,因此不适合大量计算。

    1. 利用getters访问属性
    store.getters.doneList();
    store.getters.getById(1); // {id:1, ...}
    
    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等。

    1. 常见的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
    });
    
    1. 注意事项
    • 初始化vuex对象时就定义好所有的state项
    • 临时增加属性的话会降低可读性,除非是有限范围内的逻辑否则还是定义时就确定
    Vue.set(store, 'newKey', 'newValue'); // 没这么用过,推荐初始化后就不再调整了
    
    • mutation 必须是 ++同步函数++,不能在其中执行异步操作。原因是:mutation被调用后会执行相应的状态捕捉,如果是异步的话,调用时并不会捕获到新的状态,这回导致在必要的判定或UI更新上出现不可预计的错误。这类似于数据库开发中遇到的脏读、幻读等情况。
    1. 利用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不同。

    1. 常见的 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中
    }
    
    1. 利用分发调用 action
    // 一般性
    store.dispatch('inc');
    
    // 带参数
    store.dispatch('incByNum', 1);
    
    // 一对象形式也没问题
    store.dispatch({
        type:'incWithLog',
        num:2
    });
    
    1. mapActions 映射
    import {mapActions} from 'vuex';
    
    export default{
        ...mapActions([
            inc,
            incByNum,
            incWithLog
        ]),
        // 指定键名
        ...mapActions({
            inc:'inc',
            incByNum:'incByNum',
            incWithLog:'incWithLog'
        })
    }
    
    1. 异步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为多个模块,每个模块都可以拥有自己独立的特性。

    1. 一个平凡的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状态
    
    1. 模块嵌套
    // 不怎么用,直接搬运
    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后就需要增加一级目录,否则就是平行世界。一般应用基本上用不到这么复杂的命名空间,过多的模块嵌套反而提升程序复杂性。

    1. 跨级状态调用
    // ...
    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>
    

    看多了文档有些迷茫,整理下来留个底方便查阅。如果你也喜欢的话千万不要吝啬收藏哦~!

    相关文章

      网友评论

        本文标题:Vuex 学习手册

        本文链接:https://www.haomeiwen.com/subject/isuilftx.html