第十七節:Vuex4.x 之Module詳解(局部狀態、命名空間、輔助函數等) 和 補充nexttick用法


一. Module說明

1. 什么是Module?

 由於使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象,當應用變得非常復雜時,store 對象就有可能變得相當臃腫;

 為了解決以上問題,Vuex 允許我們將 store 分割成模塊(module);

 每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊;

2. Module的命名空間

(1). 默認情況下,模塊內部的action和mutation仍然是注冊在全局的命名空間中的:

  這樣使得多個模塊能夠對同一個 action 或 mutation 作出響應

  getters 同樣也默認注冊在全局命名空間;   

注:上述默認情況下,顯然是不合理的,我們想達到的目的是各個模塊單獨調用自己的模塊類的對象。

(2). 如果我們希望模塊具有更高的封裝度和復用性,可以添加 namespaced: true (注意是 namespaced的方式使其成為帶命名空間的模塊:當模塊被注冊后,它的所有 getter、action 及 mutation 都會自動根據模塊注冊的路徑調整命名; 

3. 快速入門-基於$store/store用法

(1). 准備三個vuex文件,分別是index.js、user1.js、user2.js,其中user1.js 和 user2.js是子模塊,需要在index.js中進行導入。

user1.js

const user1Module = {
    namespaced: true, //所有 getter、action 及 mutation 都會自動根據模塊注冊的路徑調整命名
    state() {
        return {
            userCounter1: 1000
        }
    },
    getters: {
        // 訪問: $store.getters['user1/getInfo'],這里的user1是指被導入的時候,modules中的命名
        // 四個參數如下
        getInfo(state, getters, rootState, rootGetters) {
            return `userCounter1:${state.userCounter1}`;
        }
    },
    mutations: {
        // 調用:$store.commit('user1/increase')
        // 第一個參數是模塊的局部狀態對象state
        increase(state) {
            state.userCounter1++;
        },
    },
    actions: {
        // 調用$store.dispatch('user1/increaseAction')
        // 6個參數如下
        increaseAction({ commit, dispatch, state, rootState, getters, rootGetters }) {
            setTimeout(() => {
                commit('increase')
            }, 1000);
        },
        // 子module中調用根module中的方法
        fIncrease({ commit, dispatch, state, rootState, getters, rootGetters }) {
            commit('increase', null, { root: true });
            //
            // dispatch('increaseAction', null, { root: true });
        }
    }
}

export default user1Module
View Code

user2.js

const user2Module = {
    namespaced: true,
    state() {
        return {
            userCounter2: 2000
        }
    },
    getters: {
        // 訪問: $store.getters['user2/getInfo'],這里的user2是指被導入的時候,modules中的命名
        // 四個參數如下
        getInfo(state, getters, rootState, rootGetters) {
            return `userCounter2:${state.userCounter2}`;
        }
    },
    mutations: {
        // 調用:$store.commit('user2/increase')
        // 第一個參數是模塊的局部狀態對象state
        increase(state) {
            state.userCounter2++;
        }
    },
    actions: {
        // 調用$store.dispatch('user2/increaseAction')
        // 6個參數如下
        increaseAction({ commit, dispatch, state, rootState, getters, rootGetters }) {
            setTimeout(() => {
                commit('increase')
            }, 1000);
        }
    }

}

export default user2Module
View Code

index.js

import { createStore } from 'vuex';
// 導入子modules
import user1 from './c_moudles/user1'
import user2 from './c_moudles/user2'

export default createStore({
    state() {
        return {
            rootCounter: 100
        }
    },
    getters: {
        getInfo(state) {
            return `rootCounter:${state.rootCounter}`;
        }
    },
    mutations: {
        increase(state) {
            state.rootCounter++;
        }
    },
    actions: {
        increaseAction({ commit, dispatch, state, rootState, getters, rootGetters }) {
            setTimeout(() => {
                commit('increase')
            }, 1000);
        }
    },
    modules: {
        user1,
        user2
    }
});
View Code

剖析補充:

 A. 子模塊中增加namespaced: true,代表所有 getter、action 及 mutation 都會自動根據模塊注冊的路徑調整命名

 B. 子模塊,getters中的參數為:state, getters, rootState, rootGetters;mutations中的參數為:state;actions中的參數為:{ commit, dispatch, state, rootState, getters, rootGetters }

 C. 子模塊中調用父模塊中的 mutations 或 getters,需要增加 {root:true},如下圖,其中null的位置,表示可穿參數位置。

(2). 基於$store對象進行子模塊的state、getters、mutations、actions的調用。

剖析補充:

 A.  子模塊state的調用:$store.state.user1.userCounter1

 B.  子模塊getters的調用:$store.getters['user1/getInfo']

 C.  子模塊mutations的調用:this.$store.commit('user1/increase');

 D.  子模塊actions的調用:this.$store.dispatch('user1/increaseAction');

特別注意:這里的user1代表的是父模塊導入子模塊時,modules里注冊的名稱。

上述寫法同樣是適用於CompositionApi,只不過這里的$store對象要換成store 【  const store = useStore()  】即可。

代碼分享: 

<template>
    <div>
        <p>1.根模塊</p>
        <h4>rootCounter:{{$store.state.rootCounter}}</h4>
        
        <p>2.子模塊</p>
        <h4>userCounter1:{{$store.state.user1.userCounter1}}</h4>
        <h4>userCounter2:{{$store.state.user2.userCounter2}}</h4>
        
        <p>3.調用子模塊的getters</p>
        <h4>{{$store.getters['user1/getInfo']}}</h4>
        <h4>{{$store.getters['user2/getInfo']}}</h4>
        
        <p>4.調用父模塊的方法</p>
        <h4><button @click="fAdd()">加1</button></h4>
        <h4><button @click="fAddAction()">加1(延遲1s)</button></h4>
        
        <p>5.調用子模塊的方法</p>
        <h4><button  @click="user1Add()">userCounter1加1</button></h4>
        <h4><button  @click="user1AddAction()">userCounter1加1(延遲1s)</button></h4>
        <h4><button  @click="user2Add()">userCounter2加1</button></h4>
        <h4><button  @click="user2AddAction()">userCounter2加1(延遲1s)</button></h4>
        
        <p>6.子module中調用父module的方法</p>
        <h4><button @click="test()">Test</button></h4>
        
    </div>
</template>

<script>
    import {mapState} from 'vuex';
    
    export default {
        methods:{
            fAdd(){
                this.$store.commit('increase');
            },
            fAddAction(){
                this.$store.dispatch('increaseAction');
            },
            user1Add(){
                this.$store.commit('user1/increase');
            },
            user1AddAction(){
                this.$store.dispatch('user1/increaseAction');
            },
            user2Add(){
                this.$store.commit('user2/increase');
            },
            user2AddAction(){
                this.$store.dispatch('user2/increaseAction');
            },
            test(){
                this.$store.dispatch('user1/fIncrease');
            }    
        }
    }
</script>

<style scoped>
    p{
        color: #2E8B57;
        font-weight: bold;
    }
</style>
View Code

 

二. OptionsApi_輔助函數用法

1. 說明

 通過輔助函數進行子模塊的調用,主要有三種方案:

寫法1:通過完整的模塊空間名稱來查找;(寫法繁瑣,不推薦)

寫法2:第一個參數傳入模塊空間名稱,后面寫上要使用的屬性;(推薦)

寫法3:通過 createNamespacedHelpers 生成一個模塊的輔助函數;

2. 實操 

 公用的template代碼

<template>
    <div>
        <h4>userCounter1:{{userCounter1}}</h4>
        <h4>{{getInfo}}</h4>
        <h4><button @click="increase">user1加1</button></h4>
        <h4><button @click="increaseAction">user1加1(延遲1s)</button></h4>
    </div>
</template>

寫法1:

    import { mapState, mapGetters, mapMutations, mapActions } from "vuex";
    export default {
        computed: {        
            ...mapState({
                userCounter1: state => state.user1.userCounter1
            }),
            ...mapGetters({
                getInfo: "user1/getInfo"
            })
        },
        methods: {
            ...mapMutations({
                increase: "user1/increase"
            }),
            ...mapActions({
                increaseAction: "user1/increaseAction"
            }),
        }
    }

寫法2:

        import { mapState, mapGetters, mapMutations, mapActions } from "vuex";
        export default {
            computed: {
                ...mapState('user1', ['userCounter1']),
                ...mapGetters('user1', ['getInfo'])
            },
            methods: {
                ...mapMutations('user1',['increase']),
                ...mapActions('user1',['increaseAction'])
            }
        }

寫法3: 

    import { createNamespacedHelpers } from "vuex";
    const { mapState, mapGetters, mapMutations, mapActions } = createNamespacedHelpers("user1")
    export default {
        computed: {
            ...mapState(['userCounter1']),
            ...mapGetters(['getInfo'])
        },
        methods: {
            ...mapMutations(['increase']),
            ...mapActions(['increaseAction'])
        }
    }

 

 

三. CompositionApi_輔助函數用法

1. 說明

 這里統一采用上述的寫法2(寫法2:第一個參數傳入模塊空間名稱,后面寫上要使用的屬性),其中useState、useGetters需要自己封裝(即:傳入模塊名稱,則調用的是子模塊中的對象,如果不傳,則調用的是父模塊),useMutations 和 useActions直接調用返回即可。

useMapper.js

import { useStore } from 'vuex'
import {computed} from 'vue'

/* 
   抽離useState和useGetters中的通用邏輯
   ①:params: 需要獲取的參數名, 可以是數組,也可以是對象
   分別對應兩種調用方式
   ②:fn:可以是mapGetters 或 mapState
 */
export function useMapper(params,fn) {
    const store = useStore();
    const storeStateFns = fn(params);
    const storeState={};
    Object.keys(storeStateFns).forEach(fnKey=>{
        const fn = storeStateFns[fnKey].bind({$store:store});
        storeState[fnKey]=computed(fn);
    })    
    return storeState;
}
View Code

useState.js

import { mapState, createNamespacedHelpers } from 'vuex'
import { useMapper } from './useMapper'

// 下面的封裝兼容了模塊的寫法哦,如果不兼容,直接一句調用即可
export function useState(moduleName, mapper) {
  let mapperFn = mapState
  if (typeof moduleName === 'string' && moduleName.length > 0) {
    mapperFn = createNamespacedHelpers(moduleName).mapState
  } else {
    mapper = moduleName
  }
  return useMapper(mapper, mapperFn)
}
View Code

useGetters.js

import { mapGetters, createNamespacedHelpers } from 'vuex'
import { useMapper } from './useMapper'


export function useGetters(moduleName, mapper) {
  let mapperFn = mapGetters
  if (typeof moduleName === 'string' && moduleName.length > 0) {
    mapperFn = createNamespacedHelpers(moduleName).mapGetters
  } else {
    mapper = moduleName
  }
  return useMapper(mapper, mapperFn)
}
View Code

index.js(統一出口)

// 統一出口文件
import { useGetters } from './useGetters';
import { useState } from './useState';

export {
  useGetters,
  useState
}

2. 實操

代碼分享 

<template>
    <div>
        <h4>rootCounter:{{rootCounter}}</h4>
        <h4>userCounter1:{{userCounter1}}</h4>
        <h4>{{getInfo}}</h4>
        <h4><button @click="increase">user1加1</button></h4>
        <h4><button @click="increaseAction">user1加1(延遲1s)</button></h4>
        <h4><button @click="myIncrement">根module加1</button></h4>
    </div>
</template>

<script>
    import { mapMutations, mapActions } from "vuex";
    import { useState, useGetters } from '../../hooks/version3'

    export default {
        setup() {
            //state 和 getters 需要調用自己的封裝--子modules
            const state1 = useState('user1', ['userCounter1']);
            const getters1 = useGetters('user1', ['getInfo']);
            // state 調用自己的封裝--根modules
            const fState=useState(['rootCounter']);

            // mutations 和 actions 調用直接返回即可
            const mutations1 = mapMutations('user1', ['increase']);
            const actions1 = mapActions('user1', ['increaseAction']);
            
            // mutations --根modules
            const result2 = mapMutations({
                myIncrement: 'increase',
            });

            return {
                ...state1,
                ...getters1,
                ...fState,
                ...mutations1,
                ...actions1,
                ...result2
            }

        }
    }
</script>

 

四. 補充nextTick用法

(建議看最新總結:https://www.cnblogs.com/yaopengfei/p/16267007.html)

1. 說明

官方解釋:將回調推遲到下一個 DOM 更新周期之后執行。在更改了一些數據以等待 DOM 更新后立即使用它。

(2). 比如我們有下面的需求:

 A. 點擊一個按鈕,我們會修改在h3中顯示的msg;

 B. msg被修改后,獲取h3的高度;

(3). 實現上面的案例我們有三種方式:

 方式一:在點擊按鈕后立即獲取到h3的高度(錯誤的做法,因為點擊的時候,h3可能還沒渲染完)

 方式二:在updated生命周期函數中獲取h3的高度(但是其他數據更新,也會執行該updated回調)

 方式三:使用nextTick函數;

2. 實操 

代碼分享: 

<template>
    <div>
        <h2>counter: {{counter}}</h2>
        <h4><button @click="Add">增加1</button></h4>

        <h3 ref="msgRef">msg:{{msg}}</h3>
        <h4><button @click="addMsgContent">增加Msg</button></h4>
    </div>
</template>

<script>
    import { ref, onUpdated, nextTick } from 'vue';

    export default {
        setup() {
            const counter = ref(100);
            const Add = () => counter.value++;

            const msg = ref("hello nextTick");

            const msgRef = ref(null);
            const addMsgContent = () => {
                msg.value += "  123456  ";
                // 更新DOM
 nextTick(() => {
                    console.log(msgRef.value.offsetHeight)
                })
            }
            
            return {
                counter,
                msg,
                msgRef,
                addMsgContent
            }
        }
    }
</script>

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM