[vue][plugin][vuex][自總結] - vuex-總結

原文出自本人博客:
vuex的詳細總結
博主博客--兼乎javascript

說在前面

最近在使用vuex作項目,因此有了總結vuex的念頭。因而在本天中午到晚上9點,我一直沒有停過,爲了能儘快將vuex的重點寫出來。雖然本vuex文檔還不夠完整但暫時夠用,最大缺點是沒有寫實戰,只是總結了每一個知識的的要點。回想一下,時間過得不少,new Boy() 以後時間更加緊迫,有時候分心乏術。到如今,使用vue也有一段時間了。總是想總結點有關vue的卻總是在寫一些vue相關的demo,從而沒有了總結的時間。今天總結下定決定寫點vue相關的,一方面鞏固基數,一方面分享總結,同時本編偏理論和一些細節,後面一部分由於官方文檔也挺實用的就直接摘抄Vuex官方文檔。html

另外貼上一段戳心的雞湯,請喝:
咱們的敵人不是咱們身外的黑暗,而是本身心裏的黑暗,那就是咱們的容易失望,咱們的沮喪,咱們的缺少信心,耐心和細心,咱們缺少堅韌,輕言放棄,乃至自暴自棄。前端

不懂的要上官網看文檔,不懂的看一次兩次直到弄懂爲止。

開講

1.vue

vue是一個前端javascript寫的漸進式框架,在組件化模塊的時候主要無非是渲染數據、加強交互效果、拓展業務邏輯組件、組件分離要高內聚低耦合、分配管理路由,在這以後就是一個掛在在瀏覽器端的全局js模塊。固然這是個人片面之詞,詳情請移步vue。vue

2.Vuex

能夠這麼通俗理解:vuex是一個掛載到vue的全局變量對象(store),並且store的 屬性(state) 的 改變 只 能經過提交mutation來改變,使用Getter來映射store對象狀態。另外 提交 同步事務 使用 mutation 的 commit, 分發 異步事務 使用 action 的 dispatch。同時使用 module 來方便管理 vuex模塊 和 狀態java

Vuex官方文檔:https://vuex.vuejs.org/zh-cn/intro.html

前方高能vue-router

是什麼?

概念:

狀態管理模式,核心是一個store(倉庫),包含 共享的 單一狀態(state)樹vuex

爲何?

特色:

一、一個 全局單例 模式管理,方便集中管理全部組件狀態
二、狀態管理 是 響應式 的,且高效
三、改變狀態(state)的 惟一途徑 是 顯示提交commit(mutation)
四、mutation->動做api

怎麼樣?

狀態相應機制:

vuex響應機制

使用技巧:

一、由於狀態儲存是響應式,因此 讀取狀態的方法 最簡單的方法是使用 計算屬性(computed),但建議使用輔助函數獲取狀態數組

二、Action 相似於 mutation,不一樣在於:瀏覽器

Action 提交的是 mutation,而不是直接變動狀態。
(
    同步狀況:Action -> 提交 mutation  ;  Mutation -> 提交 commit
    異步狀況:Action -> 使用dispatch出發異步
)
Action 能夠包含任意異步操做,而mutation 是同步事務。
(Action -> 異步  ;  Mutation -> 同步)

三、使用action分發異步事務時:

一個 store.dispatch 在不一樣模塊中能夠觸發多個 action 函數。
在這種狀況下,只有當全部觸發函數完成後,返回的 Promise 纔會執行。

核心概念

一、state:單一狀態樹,能夠認爲是 store 的 狀態變量(data)

使用輔助函數:
獲取狀態:mapState

當映射的計算屬性的名稱與 state 的子節點名稱相同時,使用數組傳入

如:mapState([
        'count'
    ])
否則 傳 對象
如:mapState({
        count: state => state.count,
        countAlias: 'count'   //這裏 'count' 等價於上述 state => state.count
    })

對象展開運算符(…):

使用對象展開運算符將此對象 混入 到 外部對象 中
…mapState({   })

二、Getter:能夠認爲是 store 的計算屬性(computed)

接受參數

參數能夠有state和getter自己

如:const store = new Vuex.store({
        state: {
            todos: [
                {id: 1, text: 'id1', done: true},
                {id: 2, text: 'id2', done: false}
            ]
        },
        getters: {
            doneTodos: state => {
                //這裏過來state中todos的done爲true的對象,並暴露爲store.getters對象
                //store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
                return state.todos.filter( todo => todo.done)
            },
            todoCount: (state, getter) => {
                return getters.doneTodos.length  // -> 1
            }
        }
    })

使用輔助函數:

僅僅是將 store 中的 getter  映射 到 局部 計算屬性:mapGetters
例子:看上文

對象展開運算符(…):

使用對象展開運算符將 getter 混入 computed 對象中

如:computed: {
        ...mapGetters([
          'doneTodosCount',
          'anotherGetter',
          // ...
        ])
      }

三、Mutation ( 同步 事務 ):改變store狀態的 惟一方式。

相似於事件(Event):每一個 mutation 都有一個 字符串 的 事件類型 (type) 
                        和 一個 回調函數 (handler,改變狀態的地方)

接受參數

參數能夠有多個。第一參數爲state,
                及其餘對象或屬性(支持提交負荷Payload)

如:const store = new Vuex.Store({
      state: {
        count: 1
      },
      mutations: {
        increment (state, n) {
          state.count += n
        }
      }
    })
觸發事件:提交一次commit
store.commit('increment', 10)

在組件中提交 Mutation

兩種方法:
    1.使用 this.$store.commit('xxx') (支持載荷PayLoad)
    2.使用 mapMutations 輔助函數 (支持載荷PayLoad)
        將組件中的 methods 映射爲 store.commit 調用(須要在根節點注入 store)

        如:
        // xxx.vue組件
        import { mapMutations } from 'vuex'
        export default {
          // ...
          methods: {
            ...mapMutations([
              'increment', 
            // 將 `this.increment()` 映射爲 `this.$store.commit('increment')`
              'incrementBy' 
            // 將 `this.incrementBy(amount)` 映射爲 `this.$store.commit('incrementBy', amount)`
            ]),
            ...mapMutations({
              add: 'increment' 
            // 將 `this.add()` 映射爲 `this.$store.commit('increment')`
            })
          }
        }

Mutation 需遵照 Vue 的響應規則

使用 常量 替代 Mutation 事件類型( 推薦 )

這是一種規範,並且這樣既能使用 eslint 的檢測工具,也能讓開發者一目瞭然

如:
//mutation-types.js
export default {
    const SOME_MUTATION = ' SOME_MUTATION '
}

//store.js
import Vuex from 'vuex'
import * as types from 'mutation-types'

const store = Vuex.store({
    state: { … },
    mutations: {
        [ SOME_MUTATION ]( state ) => {
            …
        }
    }
})

Mutation 必須是同步函數( 重點 )

爲了實現state實時跟蹤,使用同步函數,也爲了調試方便

四、Action ( 異步 事務 ):用法相似於mutation,不一樣在於能夠提交 異步事務(使用dispatch 時 提交異步),

並且 是 提交 mutation 上的事件

接受參數

參數能夠有多個。第一參數爲接受一個與 store 實例具備相同方法和屬性的 context 對象(所以你能夠調用 context.commit 提交一個 mutation,或者經過 context.state 和 context.getters 來獲取 state 和 getter),
                及其餘對象或屬性(支持提交負荷Payload)

如:
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state, n) {
      state.count += n
    }
  },
  actions: {
    incrementAsyn(context, n) {
        context.commit( 'increment', n )
    }
  }
//或者使用參數結構
//actions: {
//  increment( { commit } ) {
//     commit( 'increment', n )
//   }
  //  }

})

分發Action

由於Action提交的commit實際是提交mutation,而mutation的提交必須是同步的,
要向提交異步的action必須使用dispatch

如:
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state, amount) {
      state.count += amount
    }
  },
  actions: {
    incrementAsyn( { commit } ) {
        setTimeout( () => {
            commit( 'increment' )
        }, 1000)
    }
   }
});

// 以載荷形式分發
store.dispatch( 'incrementAsyn', {
    amount: 10
} )

// 以對象形式分發
store.dispatch( {
    type: 'incrementAsyn', 
    amount: 10
} )

例子2:
來看一個更加實際的購物車示例,涉及到調用異步 API 和分發多重 mutation:

actions: {
  checkout ({ commit, state }, products) {
    // 把當前購物車的物品備份起來
    const savedCartItems = [...state.cart.added]
    // 發出結帳請求,而後樂觀地清空購物車
    commit(types.CHECKOUT_REQUEST)
    // 購物 API 接受一個成功回調和一個失敗回調
    shop.buyProducts(
      products,
      // 成功操做
      () => commit(types.CHECKOUT_SUCCESS),
      // 失敗操做
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

在組件中 分發 Action
兩種方法:
1.使用 this.$store.dispatch('xxx') (支持載荷PayLoad)
2.使用 mapActions 輔助函數 (支持載荷PayLoad)

將組件中的 methods 映射爲 store.commit 調用(須要在根節點注入 store)

如:
// xxx.vue組件
import { mapActions } from 'vuex'
export default {
  // ...
  methods: {
   ...mapActions([
      'increment', 
    // 將 `this.increment()` 映射爲 `this.$store.dispatch('increment')`
      'incrementBy' 
    // 將 `this.incrementBy(amount)` 映射爲 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' 
    // 將 `this.add()` 映射爲 `this.$store.dispatch('increment')`
    })
  }
}

組合Action

那麼既然使用actions來 異步 分發改變狀態,
所以也要使用到 Promise ,
和 asyn/await 的新知識的

使用 Promise
actions: {
    actionA( {commit} ){
        return new Promise( (resolve, reject) => {
            setTimeout( () => {
                commit('someMutation')
                resolve()
            }, 1000)
        })
    },
    actionB( {dispatch, commit} ){
        return dispatch('actionA).then( () => {
            commit('someOtherMutation')
        })
    }
},
// xxx.vue組件
methods: {
this.$store.dispatch('actionA').then(() => {
    …
})
}


使用  asyn/await 
//假設 getData() 和 getOtherData() 返回的是 Promise

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

注意:
一個 store.dispatch 在不一樣模塊中能夠觸發多個 action 函數。
在這種狀況下,只有當全部觸發函數完成後,返回的 Promise 纔會執行。

5.Module

因爲使用單一狀態樹,應用的全部狀態會集中到一個比較大的對象。當應用變得很是複雜時,store 對象就有可能變得至關臃腫。

爲了解決以上問題,Vuex 容許咱們將 store 分割成模塊(module)。每一個模塊擁有本身的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行一樣方式的分割:

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態

模塊的局部狀態
    對於模塊內部的 mutation 和 getter,接收的第一個參數是模塊的局部狀態對象。

    如:
    const moduleA = {
      state: { count: 0 },
      mutations: {
        increment (state) {
          // 這裏的 `state` 對象是模塊的局部狀態
          state.count++
        }
      },
      getters: {
        doubleCount (state) {
          return state.count * 2
        }
      }
    }
    
    一樣,對於模塊內部的 action,局部狀態經過 context.state 暴露出來,
    根節點狀態則爲 context.rootState:
    如:
    const moduleA = {
      // ...
      actions: {
        incrementIfOddOnRootSum ({ state, commit, rootState }) {
          if ((state.count + rootState.count) % 2 === 1) {
            commit('increment')
          }
        }
      }
    }
    對於模塊內部的 getter,根節點狀態會做爲第三個參數暴露出來:
    如:
    const moduleA = {
      // ...
      getters: {
        sumWithRootCount (state, getters, rootState) {
          return state.count + rootState.count
        }
      }
    }
    
命名空間
    默認狀況下,module中的{ state, actions, getters } 註冊 在 全局變量上, 
    使得多個模塊可以對同一 mutation 或 action 做出響應。
    
    若是但願 模塊具備更高的封裝度和複用性,能夠經過 添加  namespaced: true  的方式使其成爲命名空間模塊。
    當模塊被註冊後,它的全部 getter、action 及 mutation 都會自動根據模塊註冊的路徑調整命名。例如:

    如:
    const store = new Vuex.Store({
      modules: {
        account: {
          namespaced: true,
    
          // 模塊內容(module assets)
          state: { ... }, // 模塊內的狀態已是嵌套的了,使用 `namespaced` 屬性不會對其產生影響
          getters: {
            isAdmin () { ... } // -> getters['account/isAdmin']
          },
          actions: {
            login () { ... } // -> dispatch('account/login')
          },
          mutations: {
            login () { ... } // -> commit('account/login')
          },
          // 嵌套模塊
          modules: {
            // 繼承父模塊的命名空間
            myPage: {
              state: { ... },
              getters: {
                profile () { ... } // -> getters['account/profile']
              }
            },
            // 進一步嵌套命名空間
            posts: {
              namespaced: true,
              state: { ... },
              getters: {
                popular () { ... } // -> getters['account/posts/popular']
              }
            }
          }
        }
      }
    })
    啓用了命名空間的 getter 和 action 會收到局部化的 getter,dispatch 和 commit。換言之,你在使用模塊內容(module assets)時不須要在同一模塊內額外添加空間名前綴。更改 namespaced 屬性後不須要修改模塊內的代碼。
    
在命名空間模塊內訪問全局內容(Global Assets)
    若是但願 使用全局  state  和 getter,rootState 和 rootGetter 會做爲第三和第四參數傳入 getter,也會經過 context 對象的屬性傳入 action。

    {
        命名模塊(module)內 使用 全局 state 和 getter
        在命名模塊(module)的getterr 傳入 rootState 和 rootGetter
    }
    若須要在全局命名空間內分發 action 或提交 mutation,將 { root: true } 做爲第三參數傳給 dispatch 或 commit 便可。
    {
        全局命名空間 內 分發 action 或 提交 mutation
        則在 action 或 mutation 內
          dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
          dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
    }

    如:
    modules: {
      foo: {
        namespaced: true,
    
        getters: {
          // 在這個模塊的 getter 中,`getters` 被局部化了
          // 你可使用 getter 的第四個參數來調用 `rootGetters`
          someGetter (state, getters, rootState, rootGetters) {
            getters.someOtherGetter // -> 'foo/someOtherGetter'
            rootGetters.someOtherGetter // -> 'someOtherGetter'
          },
          someOtherGetter: state => { ... }
        },
    
        actions: {
          // 在這個模塊中, dispatch 和 commit 也被局部化了
          // 他們能夠接受 `root` 屬性以訪問根 dispatch 或 commit
          someAction ({ dispatch, commit, getters, rootGetters }) {
            getters.someGetter // -> 'foo/someGetter'
            rootGetters.someGetter // -> 'someGetter'
    
            dispatch('someOtherAction') // -> 'foo/someOtherAction'
            dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
    
            commit('someMutation') // -> 'foo/someMutation'
            commit('someMutation', null, { root: true }) // -> 'someMutation'
          },
          someOtherAction (ctx, payload) { ... }
        }
      }
    }

帶命名空間的綁定函數

當使用 mapState, mapGetters, mapActions 和 mapMutations 這些函數來綁定命名空間模塊時,寫起來可能比較繁瑣:
如:
computed: {
  ...mapState({
    a: state => state.some.nested.module.a,
    b: state => state.some.nested.module.b
  })
},
methods: {
  ...mapActions([
    'some/nested/module/foo',
    'some/nested/module/bar'
  ])
}
解決方法:
一、對於這種狀況,能夠將模塊的空間名稱字符串做爲第一個參數傳遞給上述函數,這樣全部綁定都會自動將該模塊做爲上下文。因而上面的例子能夠簡化爲:

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo',
    'bar'
  ])
}
二、經過使用 createNamespacedHelpers 建立基於某個命名空間輔助函數。它返回一個對象,對象裏有新的綁定在給定命名空間值上的組件綁定輔助函數:

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // 在 `some/nested/module` 中查找
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // 在 `some/nested/module` 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

給插件開發者的注意事項

若是開發的插件(Plugin)提供了模塊並容許用戶將其添加到 Vuex store,可能須要考慮模塊的空間名稱問題。對於這種狀況,你能夠經過插件的參數對象來容許用戶指定空間名稱:

如:
// 經過插件的參數對象獲得空間名稱
// 而後返回 Vuex 插件函數
export function createPlugin (options = {}) {
  return function (store) {
    // 把空間名字添加到插件模塊的類型(type)中去
    const namespace = options.namespace || ''
    store.dispatch(namespace + 'pluginAction')
  }
}

模塊動態註冊

在 store 建立以後,你可使用 store.registerModule 方法註冊模塊:

如:
// 註冊模塊 `myModule`
store.registerModule('myModule', {
  // ...
})
// 註冊嵌套模塊 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
  // ...
})
以後就能夠經過 store.state.myModule 和 store.state.nested.myModule 訪問模塊的狀態。

模塊動態註冊功能使得其餘 Vue 插件能夠經過在 store 中附加新模塊的方式來使用 Vuex 管理狀態。例如,vuex-router-sync 插件就是經過動態註冊模塊將 vue-router 和 vuex 結合在一塊兒,實現應用的路由狀態管理。

你也可使用 store.unregisterModule(moduleName) 來動態卸載模塊。注意,你不能使用此方法卸載靜態模塊(即建立 store 時聲明的模塊)。

在註冊一個新 module 時,你頗有可能想保留過去的 state,例如從一個服務端渲染的應用保留 state。你能夠經過 preserveState 選項將其歸檔:store.registerModule('a', module, { preserveState: true })。

模塊重用
有時咱們可能須要建立一個模塊的多個實例,例如:

建立多個 store,他們公用同一個模塊 (例如當 runInNewContext 選項是 false 或 'once' 時,爲了在服務端渲染中避免有狀態的單例)
在一個 store 中屢次註冊同一個模塊
若是咱們使用一個純對象來聲明模塊的狀態,那麼這個狀態對象會經過引用被共享,致使狀態對象被修改時 store 或模塊間數據互相污染的問題。

實際上這和 Vue 組件內的 data 是一樣的問題。所以解決辦法也是相同的——使用一個函數來聲明模塊狀態(僅 2.3.0+ 支持):

如:
const MyReusableModule = {
  state () {
    return {
      foo: 'bar'
    }
  },
  // mutation, action 和 getter 等等...
}

項目結構

Vuex 並不限制你的代碼結構。可是,它規定了一些須要遵照的規則:

1. 應用層級的狀態應該集中到單個 store 對象中。
2. 提交 mutation 是更改狀態的惟一方法,而且這個過程是同步的。
3. 異步邏輯都應該封裝到 action 裏面。

只要你遵照以上規則,如何組織代碼隨你便。若是你的 store 文件太大,只需將 action、mutation 和 getter 分割到單獨的文件。
對於大型應用,咱們會但願把 Vuex 相關代碼分割到模塊中。下面是項目結構示例:

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API請求
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # 咱們組裝模塊並導出 store 的地方
    ├── actions.js        # 根級別的 action
    ├── mutations.js      # 根級別的 mutation
    └── modules
        ├── cart.js       # 購物車模塊
        └── products.js   # 產品模塊
相關文章
相關標籤/搜索