Vue現代化の使用方法(四)--Vuex

16578 ワード

コンポーネント内ではdata属性でデータを共有することができ、親子コンポーネントもpropsでデータ共有することができるが、兄弟がコンポーネント間のデータ共有であれば、Vuexを借りて、Vuexは大木の主幹に似ており、各コンポーネントは独立した枝に似ており、コンポーネント間ではVuexを通じてつながりを形成し、共有する必要があるデータをVuexに置く限り、すべての関連コンポーネントが参照にアクセスでき、コンポーネント間でデータ共有が形成されます.
npmによるインストール
npm install --save vuex
きほんこうせい
Vuexの設計構想によると、そのコア(Store)は以下の4つの部分を含む.
  • state
  • getters
  • mutations
  • actions

  • state
    //  index.js       
    import Vuex from 'vuex'; //   Vuex
    Vue.use(Vuex); //   Vuex
    
    const store = new Vuex.Store({
        state: {
            count: 0
        },
        mutations: {
            increment (state) {
                state.count++
            }
        }
    }); //         
    
    let vm = new Vue({
        el: "#app",
        store, //        
        render: h => h(app)
    });
    ...
    //  index.vue            
    computed: {
        count () {
            return this.$store.state.count
        }
    }
    ...
    //  index.vue          ,       header.vue  
    

    index.vue :{{count}}

    ... // header.vue ,

    header.vue count :{{count}}

    ... //

    index.vue :0

    header.vue count :0


    上記の例ではindexで見ることができます.jsが設定公共倉庫(store)のstate内容はindex.vueとheader.vueで共有し、this.$を呼び出すstore.state.countが参照することで、コンポーネント間でのデータ共有が可能になります.
    計算プロパティにデータを配置し、
    前例ではthis.$store.state.countは倉庫の中値にアクセスします.この書き方は冗長に見えますが、VuexはここでmapState関数を提供してこの問題を最適化しました.
    //   mapState,            ,         ,        ,                   
    //        
    import { mapState } from 'vuex';
    ...
    //              
    computed: mapState({
        count (state) {
            return state.count;
        },
        fullName () {
            return `${this.firstName}-${this.lastName}`;
        }
    }),
    ...
    //                  
    count: state => state.count,
    fullName () {
        return `${this.firstName}-${this.lastName}`;
    }
    ...
    //   mapState               ,  this.count     store.sate.count
    computed: mapState(['count']),
    ...
    //                ,      {{co}}
    ...mapState({
        co: 'count'
    })
    ...
    //                   ,                    
    computed: {
        fullName () {
            return `${this.firstName}-${this.lastName}`;
        },
        ...mapState(['count'])
    },
    

    getters
    gettersはstateに対する計算属性です
    // index.js         
    const store = new Vuex.Store({
        state: {
            count: 0,
            name: 'lin ken',
            address: '  XX XX  ',
        },
        getters: {
            userInfo (state) {
                return `  :${state.name};   :${state.address}`;
            }
        }
    });
    ...
    // index.vue          
    userInfo () {
        return this.$store.getters.userInfo;
    },
    ...
    // index.vue        
    

    {{userInfo}}


    mapStateと同様にgettersにもmapGettersの使い方がmapStateと似ています
    //   mapGetters
    import { mapState,mapGetters } from 'vuex';
    ...
    //                  getters       
    fullName () {
        return `${this.firstName}-${this.lastName}`;
    },
    ...mapGetters(['userInfo']),
    ...mapState(['count']),
    ...
    //        userInfo    ,mapGetters        ,      
    ...mapGetters({
        cusInfo: 'userInfo'
    })
    ...
    //        
    

    {{cusInfo}}


    mutation
    mutationはVuex設定がstate値を変更できる唯一の場所であり、Vuexはここでこれらの値の変化について傍受追跡を行ったはずである.
    // index.js            
    mutations: {
        increment (state) {
            state.count++
        }
    }
    ...
    // index.vue         
    

    index.vue :{{co}}

    ... // index.vue methods addCount () { this.$store.commit('increment'); // commit mutations increment }

    このときページをクリックするとindexに表示されます.vueとheader.vueで参照されるcount値が変更されました
    //   commit  mutations ,       
    //  index.js           
    increment (state, payload) {
        state.count += payload.addNum;
    }
    ...
    //  index.vue           
    addCount () {
        this.$store.commit('increment', {addNum:100}); //   mutations
    }
    ...
    //             ,            
    // commit      type  ,        type     
    this.$store.commit({
        type: 'increment',
        addNum:100
    })
    

    コンポーネント内でcommitを介してmutationsをトリガーする場合、文字列incrementを使用します.これには、プロジェクトが大きい場合、協力する人が多い場合、ページでmutationsを勝手に呼び出すと、何の役に立つか分からない問題が隠されています.
    //        mutation-types.js
    export const INCREMENT = 'INCREMENT'; //  index.js        count   mutations
    ...
    //  index.js      
    import { INCREMENT } from './mutation-types';
    ...
    //  store  mutations     
    mutations: {
        [INCREMENT] (state, payload) {
            state.count += payload.addNum;
        }
    }
    ...
    //  index.vue   mutation-types.js,         
    addCount () {
        this.$store.commit({
            type: INCREMENT,
            addNum:100
        })
    }
    

    これにより、共通の場所でmutationsを管理し、注釈がはっきり書かれている場合、プロジェクト担当者は各mutationsの役割をはっきり知ることができます.
    mapState,mapGetters,mutationsに対してもmapMutationsがある
    import { mapState, mapGetters, mapMutations } from 'vuex';
    ...
    //  methods       ,mapMutations   methods 
    //        ,    this.INCREMENT();
    methods: {
        ...mapMutations([INCREMENT]),
        addCount () {
            this.INCREMENT({addNum: 100});
        }
    

    mutationsは同期関数でなければなりません
    公式解釈ではコールバック関数を使用するとstateの変化を明確に傍受できないため、mutationsに対する関数は同期関数のみであり、この問題に対してVuexはactionsを設定しているようです
    actions
  • actionsはmutationsをコミットし、dispatchによって
  • をトリガする.
  • actionsは非同期関数
  • をサポートする
    //  index.js            
    // context             ,     context.commit      mutation,     context.state   context.getters     state   getters
    actions: {
        increment (context, payload) {
            context.commit(INCREMENT, payload);
        }
    } 
    ...
    //       ,         
    //   state,getters          
    increment ({commit}, payload) {
        commit(INCREMENT, payload);
    }
    ...
    //  index.vue          ,actionst  dispatch  
    addCount () {
        this.$store.dispatch('increment', {addNum: 100});
    }
    ...
    //  commit  ,            
    this.$store.dispatch({type: 'increment', addNum: 100});
    ...
    //     mapActions    
    //   mapActions  
    import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
    ...
    //  methods      
    ...mapActions(['increment']),
    addCount () {
        this.increment({addNum: 100});
    }
    

    私の理解では、actionsはmutationsの強化だと思いますので、実際の使用では、actionsに関連論理を追加し、stateを更新するためにmutationsをトリガーすることに慣れています.
    Modules
    上の例ではindexですjsのパブリックウェアハウスでstoreを用いて関連操作を行うと,プロジェクトが非常に大きい場合,すべてのコンポーネントで使用されるstateを1つのstroeに置くとこのstoreが大きすぎることが予想され,この問題を解決するためにstroeを機能的に複数の小さなstroeを分割して組み合わせて使用することができる.
    //  index.js,      userStore schoolStroe                  
    const userStore = {
        state: {
            name: 'lin ken from userStore',
            address: '  XX XX  ',
        }
    };
    
    const schoolStore = {
        state: {
            department: '    ',
            classroom: 'C509'
        }
    };
    //  Vuex.Store modules ,  modules          
    const store = new Vuex.Store({
        modules: {
            userStore,
            schoolStore
        },
        state: {
            count: 0,
        },
        getters: {
            userInfo (state) {
                return `  :${state.name};   :${state.address}`;
            }
        },
        mutations: {
            [INCREMENT] (state, payload) {
                state.count += payload.addNum;
            }
        },
        actions: {
            increment ({commit}, payload) {
                commit(INCREMENT, payload);
            }
        }
    });
    ...
    //  index.vue           ,        
    userStoreInfo () {
        return `  :${this.userStore.name};  :${this.userStore.address}`;
    },
    schoolStoreInfo () {
        return `  :${this.schoolStore.department};  :${this.schoolStore.classroom}`;
    },
    ...mapState(['count', 'schoolStore', 'userStore'])
    

    実際のプロジェクトでは、サブウェアハウスを異なるファイルの下に配置して管理する可能性があります.
    //             userStore.js schoolStore.js
    // userStore.js    
    export const userStore = {
        state: {
            name: 'lin ken from userStore',
            address: '  XX XX  ',
        }
    };
    ...
    // schoolStore.js    
    export const schoolStore = {
        state: {
            department: '    ',
            classroom: 'C509'
        }
    };
    ...
    //  index.js             
    import {userStore} from './userStore';
    import {schoolStore} from './schoolStore';
    

    このような分割管理により,各サブウェアハウスを容易に組み合わせることができ,メンテナンスと使用が容易である.Vuexはサブウェアハウスを実装する際にstateをモジュール名詞で区切るだけであり,他のgetters,actionsは統合して使用されモジュールで区切られない.
    // userStore.js     
    export const userStore = {
        state: {
            name: 'lin ken from userStore',
            address: '  XX XX  ',
        },
        getters: {
            rootCount (state, getters, rootState) {
                return rootState.count;
            }
        },
        mutations: {
            changeName (state, payload) {
                state.name = payload.name;
            }
        },
        actions: {
            changeName ({commit}, payload) {
                commit('changeName', payload);
            }
        }
    };
    ...
    // index.vue          
    ...mapGetters(['rootCount']),
    userStoreInfo () {
        return `  :${this.userStore.name};  :${this.userStore.address};from rootState:${this.rootCount}`;
    }
    
    ...
    // index.vue       
    ...mapActions(['increment', 'changeName']),
    addCount () {
        this.changeName({name: 'Rede'});
        this.increment({addNum: 100});
    }
    

    前の例では、サブウェアハウスの関連stateを呼び出すには、ウェアハウス名を追加する必要があることがわかります:this.userStore.name/this.schoolStore.classroomですが、サブウェアハウスのgetters、mutations、actionsを呼び出すときにサブウェアハウス名を追加する必要がない場合は、情報が統合されます:this.rootCount/this.changeName,これによりサブウェアハウスのgetters,mutations,actionsが再名される場合があります
    //  schoolStore.js     
    export const schoolStore = {
        state: {
            department: '    ',
            classroom: 'C509'
        },
        getters: {
            rootCount (state) {
                return state.classroom;
            }
        },
        mutations: {
            changeName (state, payload) {
                state.department = payload.name;
            }
        },
        actions: {
            changeName ({commit}, payload) {
                  console.log('school store');
                commit('changeName', payload);
            }
        }
    };
    ...
    //  userStore.js     
    actions: {
        changeName ({commit}, payload) {
            console.log('user store');
            commit('changeName', payload);
        }
    }
    

    このとき、rootCountは重複するgettersキーワードであることがページに提示されますが、重複するmutationsとactionsに対して検出できません.このとき、ページのボタンをクリックすると、indexにいます.vueが設定するchangeNameを実行する方法は、userStoreを実行したい場合があります.jsでactionsですが、何気なくschoolStoreにいるからです.jsにも同名のactionsが宣言されており、2つのactionsが同時にトリガーされます(コンソールで検証できます).このような明確なエラーはなく、人為的な原因による無意識のエラーであり、開発過程ではメンテナンスが極めて難しく、特に複数の人が協力しているプロジェクトでは、したがって、mutationsとactionsに対して定義名詞(特にmutationsは、一般的にコンポーネント内で直接actionsをトリガするため、actionsが再名する可能性はmutationsよりも小さい)は、mutations-typeのような独立した定数リストに格納ことが推奨.js.
    関連情報名を独立に宣言するほか、デフォルトgetters、mutations、actionsの値がグローバル宣言にバインドされているため、ネーミングスペースを使用してこの問題を解決することもできます.これにより、呼び出しが容易になります.
    //  userStore.js   namespced true,        
    export const userStore = {
        namespaced: true,
    ...
    //  index.vue        
    //        this.rootCount   userStore  rootCount
    ...mapGetters({
        'rootCount': 'userStore/rootCount'
    }),  
    ...
    //                       ,      
    ...mapActions(['increment', 'userStore/changeName']),
    addCount () {
        this['userStore/changeName']({name: 'Rede'});
        this.increment({addNum: 100});
    }  
    

    this['userStore/changeName']は使い勝手が悪いが、このような使い方はこの方法の所属を明確に指摘することができ、このような面倒な書き方を使いたくない場合は、ネーミングスペースを指定することもできる.
    //          userStore      
    ...mapActions('userStore/', ['increment', 'changeName']),
    addCount () {
        this.changeName({name: 'Rede'});
        this.increment({addNum: 100});
    }
    

    これによりchangeNameの呼び出しはuserStoreの下のコンテンツを直接呼び出すことができ、createNamespacedHelpersを使用してグローバルに自動的に追加することもできます.
    import { mapState, mapGetters, mapMutations } from 'vuex';
    import { createNamespacedHelpers } from 'vuex';
    const { mapActions } = createNamespacedHelpers('userStore/');
    ...
    //                     ,          
    ...mapActions(['increment', 'changeName']),
    addCount () {
        this.changeName({name: 'Rede'});
        this.increment({addNum: 100});
    }
    

    ただし、この追加はすべてのバインド情報に対して行われるため、incrementはuserStoreというサブウェアハウスにも指定され、このサブウェアハウスがこの方法がないとエラーが報告されるため、この内容をどのように使用するかはよく考慮する必要がありますが、一緒に必要とする場合は、オブジェクトの形式で拡張情報を指定することもできます.
    //            
    import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
    ...
    //          
    //                 ,               ,         
    ...mapActions({
        increment: 'increment',
        changeName: 'userStore/changeName'
    }),
    addCount () {
        this.changeName({name: 'Rede'});
        this.increment({addNum: 100});
    }
    

    また、この同名のchangeNameについても、ファイル名によってそれぞれ
    USER_CHANGENAME,
    SCHOOL_CHANGENAME
    

    どのように使うかは、あなたの考え次第です.
    サブウェアハウスで、メインウェアハウスのstateを取得する場合は、gettersはrootState、rootGettersというパラメータを送信できます.
    // getters ,rootState      ,rootGetters      
    rootCount (state, getters, rootState, rootGetters) 
    // actions ,             ,     
    changeName ({commit, rootState, rootGetters}, payload) 
    

    サブウェアハウスの動的作成
    使用しやすいサブウェアハウスを直接宣言するだけでなく、動的に生成することもできます.
    //  index.vue  registerModule       
    mounted () {
        this.$store.registerModule('myModule', {
            state: {
                info: 'from new module'
            }
        });
    },
    ...
    //             
    addCount () {
        this.changeName({name: 'Rede'});
        this.increment({addNum: 100});
        this.info = this.$store.state.myModule.info;
        this.$store.unregisterModule('myModule');
        console.log(this.$store.state.myModule);
    }
    

    このような一時的なサブウェアハウスは、サブコンポーネント間で一時的な通信に使用するのに非常に役立ち、完了を使用するとunregisterModuleを呼び出して直接削除することもできます.一時的に作成されたサブウェアハウスが静的ウェアハウスに影響を及ぼさないように、指定したサブウェアハウスで次のレベルのウェアハウスを作成することもできます.
    mounted () {
        this.$store.registerModule('nested', {
            state: {
                info: 'nested'
            }
        });
        this.$store.registerModule(['nested', 'myModule'], {
            state: {
                info: 'from new module'
            }
        });
    },
    ...
    //     
    console.log(this.$store.state.nested.info);
    console.log(this.$store.state.nested.myModule.info);
    

    ダイナミックウェアハウスは一時的に作成され、同名のものに遭遇すると直接上書きされます.
    //    myModule      ,myModule  state     name 
    mounted () {
        this.$store.registerModule('nested', {
            state: {
                info: 'nested'
            }
        });
        this.$store.registerModule(['nested', 'myModule'], {
            state: {
                info: 'from new module'
            }
        });
        this.$store.registerModule(['nested', 'myModule'], {
            state: {
                name: 'myModule'
            }
        });
    },
    

    これらの動的に作成するサブウェアハウスを重ね合わせる場合は、preserveStateパラメータを追加できます.
    //      myModule state   info name   
    this.$store.registerModule(['nested', 'myModule'], {
        state: {
            info: 'from new module'
        }
    });
    this.$store.registerModule(['nested', 'myModule'], {
        state: {
            name: 'myModule'
        }
    }, { preserveState: true });
    

    倉庫の多重化
    1つのコンポーネントを多重化すると
    プラグイン
    Vuexはカスタムプラグインをサポートし、プラグインによってステータスの変化をリスニングできます.
    //  index.js       
    // store Vuex       
    // subscribe  mutations       
    // subscribeAction action       
    const myPlugin = store => {
        //   store       
        store.subscribe((mutation, state) => {
            console.log('from myPlugin');
            console.log(mutation);
        });
        store.subscribeAction((action, state) => {
            console.log('from myPlugin');
            console.log(action);
        })
    };
    ...
    //          plugins    
    const store = new Vuex.Store({
        ...
        plugins: [myPlugin]
    });
    

    Inputの最適化
    Vuexではmutationsのみでstateの値を変更するよう強制されます.inputのvalue値をstateの計算プロパティにバインドすると、stateに対応する値をどのように変更しますか?
    //  index.vue          
    
    ...
    userStoreInfo () {
        return `${this.userStore.name}`;
    },
    

    このときinputの内容を変更しようとすると、コンソールがsetterを設定していないことを直接エラーで通知します.解決策はsetメソッドを設定し、actionsで対応するstateを変更することです.
    userStoreInfo: {
        get () {
            return `${this.userStore.name}`;
        },
        set (value) {
            this.changeName({name: value});
        }
    },
    

    公式サイトではvalueをバインドしてinputを傍受する方法についても言及しており、更新する案は、直接的ではないような気がします.