vue--vuex 狀態管理模式

丶Serendipity丶發表於2022-04-08

前言

   vuex作為vue的核心外掛,同時在開發中也是必不可少的基礎模組,本文來總結一下相關知識點。

正文

  1、基於單向資料流問題而產生了Vuex

  單向資料流是vue 中父子元件的核心概念,props 是單向繫結的。當父元件的屬性值發生變化的時候,會傳遞給子元件發生相應的變化,從而形成一個單向下行的繫結,父元件的屬性改變會流向下行子元件中,但是反之,為了防止子元件無意間修改了父元件中的資料而影響到了其他的子元件的狀態,vue 規定了從下往上的資料流是不允許的。當我們的應用遇到多個元件共享狀態時,單向資料流的簡潔性很容易被破壞:

        a、多個元件依賴於同一狀態。元件之間傳參變得特別繁瑣,並且兄弟元件之間狀態傳遞無能為力。

        b、來自不同檢視的行為需要變更同一狀態。 經常會採用父子元件直接引用或者通過事件來變更和同步狀態的多份拷貝。

  我們為什麼不把元件的共享狀態抽取出來,以一個全域性單例模式管理呢?Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式。它採用集中式儲存管理應用的所有元件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。

  使用 Vuex 並不意味著你需要將所有的狀態放入 Vuex。雖然將所有的狀態放到 Vuex 會使狀態變化更顯式和易除錯,但也會使程式碼變得冗長和不直觀。如果有些狀態嚴格屬於單個元件,最好還是作為元件的區域性狀態。應該根據你的應用開發需要進行權衡和確定。

  2、安裝及使用

    CDN 方式: <script src="...vuex.js"></script>
    NPM 方式: npm install vuex --save
    Yarn方式: yarn add vuex
    其他方式:專案初始化是引入依賴
    無論哪種方式都需要  Vue.use(Vuex)來安裝 Vuex

  3、核心及使用方法

  每一個 Vuex 應用的核心就是 store(倉庫)。“store”基本上就是一個容器,它包含著你的應用中大部分的狀態 vuex的Vuex 核心 State 、Getters 、Mutation、  Action、  Module。

  (1)State

  Vuex也使用了單一狀態樹來管理應用層級的全部狀態。唯一資料來源。

  單一狀態樹能夠讓我們最直接的方式找到某個狀態的片段,而且在之後的維護和除錯過程中,也可以非常方便的管理和維護。

  state儲存狀態類似於元件中data,在元件中訪問狀態經常有兩種方法:分別為

    a、通過 this.$store.state.屬性 的方式來訪問狀態,通常寫在computed計算屬性中,當然也可以直接通過插值表示式訪問;

    b、藉助mapState 輔助函式。

  核心程式碼如下:

        <div id="app">
            {{ mycount }}<br>
            直接插值表示式訪問
            {{ this.$store.state.count }}<br>
            {{ myinfoAge }}<br>
        </div>
        <script>
        import Vue from 'vue';
        import Vuex from 'vuex';
        import { mapState } from 'vuex'// 使用輔助函式一定記住引入
        const store = new Vuex.Store({
            // 儲存狀態資料
            state: {
                count: 0,
                info:{
                    name:"xiaoming",
                    age:18
                }
            },
        )
        new Vue({ 
            el: '#app',
            store,
            computed:{
                // a、計算屬性方式
                mycount(){
                    return this.$store.state.count
                },
                // b、利用輔助函式賦值給對應變數,頁面可以直接使用該變數
                 ...mapState({
                    myinfoAge: (state) => state.info.age,
                }),
                // 當對映的計算屬性的名稱與 state 的子節點名稱相同時,我們也可以給 mapState 傳一個字串陣列。下面的簡化寫法相當於 info: (state) => state.info,
                ...mapState(["info"]),
            }
        })
        </script>

  (2) Getters

  store的一個計算屬性,類比元件的計算屬性,getter 的返回值會根據它的依賴被快取起來,且只有當它的依賴值發生了改變才會被重新計算,Getter 接受 state 作為其第一個引數,在state中的資料發生改變,計算屬性重新計算

  Getters 的狀態儲存相當於元件中計算屬性,訪問方式有三種:

    a、通過屬性訪問

    b、通過方法訪問

    c、通過mapGetters 輔助函式訪問

  核心程式碼如下:

        <div id="app">
           {{ myInfoLength }}<br>
           直接插值表示式訪問
           {{ this.$store.getters.infoLength }}<br>
           {{ myName }}<br>
           {{ infoLength }}
        </div>
        <script>
        import Vue from 'vue';
        import Vuex from 'vuex';
        import { mapGetters  } from 'vuex'// 使用輔助函式一定記住引入
        const store = new Vuex.Store({
            state: {
                info:[
                    {name:"name1",age:18},
                    {name:"name2",age:28}
                ]
            },
            // 儲存狀態資料
            getters: {
                infoLength: (state) => {
                    return state.info.length;
                },
                getNameByAge: (state) => (age) => {
                    return state.info.find(item => item.age === age).name
                }
            },
        )
        new Vue({ 
            el: '#app',
            store,
            computed:{
                // a、通過屬性訪問
                myInfoLength(){
                    return this.$store.getters.infoLength
                }
                // b、通過方法訪問
                myName(){
                    return this.$store.getters.getNameByAge(18)
                }
                // c、mapGetters 輔助函式僅僅是將 store 中的 getter 對映到區域性計算屬性
                ...mapGetters(["infoLength"])
            }
        })
        </script>

  (3) Mutation

  前面state和getters兩個核心概念都是為了在store儲存資料和訪問資料的使用,Mutation則提供了對store中資料的修改功能,並且是唯一的更新方式,提交Mutation,Mutation主要包括兩部分:字串的事件型別(type)和一個回撥函式(handler),該回撥函式的第一個引數就是state。

  在檢視元件中不能直接修改store容器中的狀態,需要先在容器中註冊一個事件函式,當需要更新狀態時候需要提交觸發該事件,同時可以向該事件傳遞引數。這裡需要區別與元件內v-model雙向繫結。提交事件方法有一下幾種:

    a、普通提交方式

    b、物件風格提交

    c、藉助 mapMutations 輔助函式

  其核心程式碼如下:

        <div id="app">
            <button @click="handleAdd">點我加一</button>
            <button @click="handleAddForNum">點我加加</button>
            <button @click="handleAddForObj">物件新增</button>
            <button @click="handleAddMap">物件新增</button>
        </div>
        <script>
        import Vue from 'vue';
        import Vuex from 'vuex';
        import { mapMutations  } from 'vuex'// 使用輔助函式一定記住引入
        const store = new Vuex.Store({
            state: {
                count:1
            },
            mutations:{
                // 註冊事件
                addCount(state){
                    state.count ++
                },
                addCountForNum(state,num){
                    state.count += num
                },
                addCountForObj(state,payload){
                    state.count += payload.num
                },
                addMap(state){
                    state.count ++
                }
            }
        )
        new Vue({ 
            el: '#app',
            store,
            methods:{
                // a、普通提交方式
                handleAdd(){
                    this.$store.commit('addCount')
                },
                handleAddForNum(){
                    this.$store.commit('addCountForNum',10)
                },
                // b、物件風格提交
                handleAddForObj() {
                    this.$store.commit({ type: "addCountForObj", num: 100 });
                },
                // c、藉助 mapMutations 輔助函式
                ...mapMutations(["addMap"]),
                handleAddMap(){
                    this.addMap()
                }
            }
        })
        </script> 

  Mutation 需遵守 Vue 的響應規則,Vuex 的 store 中的狀態是響應式的,那麼當我們變更狀態時,監視狀態的 Vue 元件也會自動更新。這也意味著 Vuex 中的 mutation 也需要與使用 Vue 一樣遵守一些注意事項:最好提前在你的 store 中初始化好所有所需屬性。當需要在物件上新增新屬性時,你應該使用 Vue.set(obj, 'newProp', 123), 或者以新物件替換老物件。例如,利用物件展開運算子 state.obj = { ...state.obj, newProp: 123 }

        注意:Mutation 必須是同步函式。在 mutation 中混合非同步呼叫會導致你的程式很難除錯。例如,當你呼叫了兩個包含非同步回撥的 mutation 來改變狀態,你怎麼知道什麼時候回撥和哪個先回撥呢?

    (4) Action

     Action類似於Mutation, 但是是用來代替Mutation進行非同步操作的.action 用於非同步的修改state,它是通過muation間接修改state的。

     context是和store物件具有相同方法和屬性的物件.也就是說, 我們可以通過context去進行commit相關的操作, 也可以獲取context.state等.

     若需要非同步操作來修改state中的狀態,首先需要action來註冊事件,元件檢視在通過dispatch分發方式呼叫該事件,該事件內部提交mutation中的事件完成改變狀態操作,總之,通過action這個中介來提交mutation中的事件函式.分發事件方法如下:

      a、普通提交方式

       b、物件風格提交

       c、藉助 mapActions 輔助函式

     核心程式碼如下:

        <div id="app">
            <button @click="handleAdd">點我加一</button>
            <button @click="handleAddTen">點我加十</button>
            <button @click="handleAddForObj">物件新增</button>
            <button @click="handleAddMap">物件新增</button>
        </div>
        <script>
        import Vue from 'vue';
        import Vuex from 'vuex';
        import { mapMutations,mapActions  } from 'vuex'// 使用輔助函式一定記住引入
        const store = new Vuex.Store({
            state: {
                count:1
            },
            // 註冊事件修改state狀態值
            mutations:{
                addCount(state){
                    state.count ++
                },
                addCountForNum(state,num){
                    state.count += num
                },
                addCountForObj(state,payload){
                    state.count += payload.num
                },
                addMap(state){
                    state.count ++
                }
            },
            // 註冊事件,提交給 mutation
            actions:{
                addAction(context){
                    setTimeout(() => {
                        context.commit('addCount')
                    }, 1000)
                },
                addActionForNum(context,num){
                    setTimeout(() => {
                        context.commit('addCountForNum',num)
                    }, 1000)
                },
                addActionForObj(context,payload){
                    setTimeout(() => {
                        context.commit('addCountForObj',payload)
                    }, 1000)
                },
                addActionMap(context){
                    setTimeout(() => {
                        context.commit('addMap')
                    }, 1000)
                }
            }
        )
        new Vue({ 
            el: '#app',
            store,
            methods:{
                // a、普通提交方式
                handleAdd(){
                    this.$store.dispatch('addAction')
                },
                handleAddTen(){
                    this.$store.dispatch('addActionForNum',10)
                },
                //  b、物件風格提交
                handleAddForObj(){
                    this.$store.dispatch({
                        type: 'addActionForObj',
                        amount: 10
                    })
                }
                // 藉助 mapActions 輔助函式
                ...mapActions(["addActionMap"]),// 相當於...mapActions({addActionMap:"addActionMap"}) 
                handleAddMap(){
                    this.addActionMap()
                }
            }
        })

  組合 Action:組合多個 action,以處理更加複雜的非同步流程.store.dispatch 可以處理被觸發的 action 的處理函式返回的 Promise,並且 store.dispatch 仍舊返回 Promise。一個 store.dispatch 在不同模組中可以觸發多個 action 函式。在這種情況下,只有當所有觸發函式完成後,返回的 Promise 才會執行。假設 getData() 和 getOtherData() 返回的是 Promise。

     actions: {
            async actionA ({ commit }) {
                commit('gotData', await getData())
            },
            async actionB ({ dispatch, commit }) {
                await dispatch('actionA') // 等待 actionA 完成
                commit('gotOtherData', await getOtherData())
            }
        }

  (5)Modules

  Vuex允許我們將store分割成模組(Module), 而每個模組擁有自己的state、mutation、action、getters等

        4、Vuex和全域性物件的不同

  (1)Vuex 的狀態儲存是響應式的。當 Vue 元件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那麼相應的元件也會相應地得到高效更新。

  (2)你不能直接改變 store 中的狀態。改變 store 中的狀態的唯一途徑就是顯式地提交 (commit) mutation。這樣使得我們可以方便地跟蹤每一個狀態的變化,從而讓我們能夠實現一些工具幫助我們更好地瞭解我們的應用。

寫在最後

  以上就是本文的全部內容,希望給讀者帶來些許的幫助和進步,方便的話點個關注,小白的成長之路會持續更新一些工作中常見的問題和技術點。

相關文章