(生產)vuex - 狀態管理

參考:https://vuex.vuejs.org/zh-cn/

安裝

  • 直接下載 / CDN 引用

 

https://unpkg.com/vuex
在 Vue 以後引入 vuex 會進行自動安裝:
<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>html

 

  • NPM

 

npm install vuex --savevue

 

 

State

單一狀態樹

Vuex 使用 單一狀態樹 —— 是的,用一個對象就包含了所有的應用層級狀態。至此它便做爲一個『惟一數據源(SSOT)』而存在。這也意味着,每一個應用將僅僅包含一個 store 實例。單一狀態樹讓咱們可以直接地定位任一特定的狀態片斷,在調試的過程當中也能輕易地取得整個當前應用狀態的快照web

使用

    store.jsvuex

import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import state from './state'
import mutations from './mutations'
Vue.use(Vuex)
export default new Vuex.Store({
  actions,
  getters,
  state,
  mutations
})npm

 

main.jsapi

   import store from './store'數組

new Vue({
  el: '#app',
  store,
  ...............
})websocket

    // store 實例會注入到根組件下的全部子組件中,且子組件能經過 this.$store訪問到app

 

mapState 輔助函數

須要獲取多個狀態時候,將這些狀態都聲明爲計算屬性會有些重複和冗餘異步

mapState 輔助函數幫助咱們生成計算屬性

// 引入
import { mapState } from 'vuex'

computed: mapState({
  // 箭頭函數可以使代碼更簡練
  count: state => state.count,

  // 等同於 `state => state.count`
  countAlias: 'count',

  // 等同於 `name => state.name`
  name,

  // 爲了可以使用 `this` 獲取局部狀態,必須使用常規函數
  countPlusLocalState (state) {
    return state.count + this.localCount
  }
})

 

//自己又有單獨的屬性的狀況
computed: {
  //內部的計算屬性
  selfattr() {

  },
  ...mapState({
    count,
    name
  })
}

 

Getters

有時候咱們須要從 store 中的 state 中派生出一些狀態,例如對列表進行過濾並計數

return this.$store.state.todos.filter(todo => todo.done).length

多個組件須要用到此屬性,咱們要麼複製這個函數,或者抽取到一個共享函數而後在多處導入它 —— 不管哪一種方式都不是很理想

Vuex 容許咱們在 store 中定義『getters』(能夠認爲是 store 的計算屬性)。Getters 接受 state 做爲其第一個參數

 

例子:

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

  Getters 會暴露爲 store.getters 對象

  store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

 

  Getters 也能夠接受其餘 getters 做爲第二個參數

getters: {
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}
store.getters.doneTodosCount // -> 1

 

  咱們能夠很容易地在任何組件中使用它:

this.$store.getters.doneTodosCount

 

mapGetters 輔助函數

mapGetters 輔助函數僅僅是將 store 中的 getters 映射到局部計算屬性:

語法參照state

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

 

 

Mutations

更改 Vuex 的 store 中的狀態的惟一方法是提交 mutation。Vuex 中的 mutations 很是相似於事件:每一個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是咱們實際進行狀態更改的地方,而且它會接受 state 做爲第一個參數

例子: 

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 變動狀態
      state.count++
    }
  }
})

//不能直接調用一個 mutation handler , 須要調用 store.commit 方法

store.commit('increment')

 

store.commit 傳入額外的參數

mutations: {
  increment (state, n) {
    state.count += n
  }
}
store.commit('increment', 10)

 

對象風格的提交方式

  store.commit({ type: 'increment', amount: 10 })

  mutations: { increment (state, payload) { state.count += payload.amount } }

 

Mutations 需遵照 Vue 的響應規則

  1. 最好提早在你的 store 中初始化好全部所需屬性。
  2. 當須要在對象上添加新屬性時,你應該 使用 Vue.set(obj, 'newProp', 123), 或者 -
  3. 以新對象替換老對象。例如,利用 stage-3 的對象展開運算符咱們能夠這樣寫:state.obj = { ...state.obj, newProp: 123 }

 

使用常量替代 Mutation 事件類型(規範)

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'

// store.js
import { SOME_MUTATION } from './mutation-types'

mutations: {
  // 咱們可使用 ES2015 風格的計算屬性命名功能來使用一個常量做爲函數名
  [SOME_MUTATION] (state) {

    .........
  }
}

  

mutation 必須是同步函數

  

提交 Mutations

普通提交:

this.$store.commit('xxx') 提交 mutation

使用 mapMutations 提交:

import { mapMutations } from 'vuex'
export default {
  methods: {
    ...mapMutations([
      'increment' // 映射 this.increment() 爲 this.$store.commit('increment')
    ]),
    ...mapMutations({
      add: 'increment' // 映射 this.add() 爲 this.$store.commit('increment')
    })
  }
}

 

 

Actions

Action 相似於 mutation,不一樣在於:
Action 提交的是 mutation,而不是直接變動狀態。
Action 能夠包含任意異步操做

例子:

mutations: {
  increment (state) {
    state.count++
  }
},
actions: {
  increment (context) {
    context.commit('increment')
  }
}

Action 函數接受一個與 store 實例具備相同方法和屬性的 context 對象,所以你能夠調用 context.commit 提交一個 mutation,或者經過 context.state 和 context.getters 來獲取 state 和 getters。當咱們在以後介紹到 Modules時,你就知道 context 對象爲何不是 store 實例自己了

 

 參數解構 來簡化代碼

    actions: {

      increment ({ commit }) {

         commit('increment')

      }

    }

  

分發 Action

Action 經過 store.dispatch 方法觸發:store.dispatch('increment') ; 咱們能夠在 action 內部執行異步操做

Actions 支持一樣的載荷方式和對象方式進行分發:

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

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

 

 mapActions 輔助函數

import { mapActions } from 'vuex'
methods: {
  ...mapActions([
    'increment' // 映射 this.increment() 爲 this.$store.dispatch('increment')
  ]),
  ...mapActions({
    add: 'increment' // 映射 this.add() 爲 this.$store.dispatch('increment')
  })
}

 

組合 Actions

Action 一般是異步的,那麼如何知道 action 何時結束呢?更重要的是,咱們如何才能組合多個 action,以處理更加複雜的異步流程?

store.dispatch 能夠處理被觸發的action的回調函數返回的Promise,而且store.dispatch仍舊返回Promise

例子:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

如今你能夠:
store.dispatch('actionA').then(() => {
  ........
})

在另一個 action 中也能夠:
actions: {
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}

 

 async / await 組合

// 假設 getData() 和 getOtherData() 返回的是 Promise
actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}

 

Modules

應用的全部狀態會集中到一個比較大的對象。當應用變得很是複雜時,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
    }
  }
}

 

命名空間

默認狀況下,模塊內部的 action、mutation 和 getter 是註冊在全局命名空間的——這樣使得多個模塊可以對同一 mutation 或 action 做出響應。若是但願你的模塊更加自包含或提升可重用性,你能夠經過添加 namespaced: true的方式使其成爲命名空間模塊。當模塊被註冊後,它的全部 getter、action 及 mutation 都會自動根據模塊註冊的路徑調整命名 , 例如:

const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true,
      state: { ... }, // 模塊內的狀態已是嵌套的了,使用 `namespaced` 屬性不會對其產生影響
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      },

      
      modules: {     // 嵌套模塊
        myPage: {   // 繼承父模塊的命名空間
          getters: {
            profile () { ... } // -> getters['account/profile']
          }
        },
        posts: {  // 進一步嵌套命名空間
          namespaced: true,
          getters: {
            popular () { ... } // -> getters['account/posts/popular']
          }
        }
      }
    }
  }
})

 

在命名空間模塊內訪問全局內容(Global Assets)

若是你但願使用全局 state 和 getter,rootState 和 rootGetter 會做爲第三和第四參數傳入 getter,也會經過context 對象的屬性傳入 action。

若須要在全局命名空間內分發 action 或提交 mutation,將 { root: true } 做爲第三參數傳給 dispatch 或 commit便可。

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) { ... }
    }
  }
}

 

帶命名空間的綁定函數

當使用 mapStatemapGettersmapActions 和 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'
  ])
}

 

 

插件

Vuex 的 store 接受 plugins 選項,這個選項暴露出每次 mutation 的鉤子。Vuex 插件就是一個函數,它接收 store 做爲惟一參數:

const myPlugin = store => {
  // 當 store 初始化後調用
  store.subscribe((mutation, state) => {
    // 每次 mutation 以後調用
    // mutation 的格式爲 { type, payload }
  })
}

而後像這樣使用:

const store = new Vuex.Store({
  plugins: [myPlugin]
})

 

下面是個大概例子

export default function createWebSocketPlugin (socket) {
  return store => {
    socket.on('data', data => {
      store.commit('receiveData', data)
    })
    store.subscribe(mutation => {
      if (mutation.type === 'UPDATE_DATA') {
        socket.emit('update', mutation.payload)
      }
    })
  }
}
const plugin = createWebSocketPlugin(socket)

const store = new Vuex.Store({
  state,
  mutations,
  plugins: [plugin]
})

 

嚴格模式

開啓嚴格模式,僅需在建立 store 的時候傳入 strict: true

在嚴格模式下,不管什麼時候發生了狀態變動且不是由 mutation 函數引發的,將會拋出錯誤。這能保證全部的狀態變動都能被調試工具跟蹤到

不要在發佈環境下啓用嚴格模式!

構建工具來處理這種狀況

const store = new Vuex.Store({

  strict: process.env.NODE_ENV !== 'production'

})

 

表單處理

當在嚴格模式中使用 Vuex 時,在屬於 Vuex 的 state 上使用 v-model 會比較棘手:

<input v-model="obj.message">

假設這裏的 obj 是在計算屬性中返回的一個屬於 Vuex store 的對象,在用戶輸入時,v-model 會試圖直接修改obj.message。在嚴格模式中,因爲這個修改不是在 mutation 函數中執行的, 這裏會拋出一個錯誤

 

方法一:

用『Vuex 的思惟』去解決這個問題的方法是:給 <input> 中綁定 value,而後偵聽 input 或者 change 事件,在事件回調中調用 action:

<input :value="message" @input="updateMessage">

computed: {

  ...mapState({ message: state => state.obj.message })

},

methods: { updateMessage (e) {

  this.$store.commit('updateMessage', e.target.value)

} }

 

下面是 mutation 函數:

mutations: {
  updateMessage (state, message) {
    state.obj.message = message
  }
}

 

方法二:

使用帶有 setter 的雙向綁定計算屬性

<input v-model="message">

computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.commit('updateMessage', value)
    }
  }
}

 

 

API 參考

Vuex.Store 構造器選項

  • state

類型: Object

Vuex store 實例的根 state 對象

 

  • mutations

類型: { [type: string]: Function }

函數老是接受 state 做爲第一個參數(若是定義在模塊中,則爲模塊的局部狀態),payload 做爲第二個參數(可選)

 

  • actions

類型: { [type: string]: Function }

在 store 上註冊 action。處理函數接受一個 context 對象,包含如下屬性:

{
  state, // 等同於 store.state, 若在模塊中則爲局部狀態
  rootState, // 等同於 store.state, 只存在於模塊中
  commit, // 等同於 store.commit
  dispatch, // 等同於 store.dispatch
  getters // 等同於 store.getters
}

 

  • getters

類型: { [key: string]: Function }
在 store 上註冊 getter,getter 方法接受如下參數:
  state, // 若是在模塊中定義則爲模塊的局部狀態
  getters, // 等同於 store.getters
  rootState // 等同於 store.state
註冊的 getter 暴露爲 store.getters。

 

  • modules

類型: Object
包含了子模塊的對象,會被合併到 store,大概長這樣:
{
  key: {
    state,
    mutations,
    actions?,
    getters?,
    modules?
  },
  ...
}
與根模塊的選項同樣,每一個模塊也包含 state 和 mutations 選項。模塊的狀態使用 key 關聯到 store 的根狀態。模塊的 mutation 和 getter 只會接收 module 的局部狀態做爲第一個參數,而不是根狀態,而且模塊 action 的 context.state 一樣指向局部狀態。

 

  • plugins

類型: Array<Function>
一個數組,包含應用在 store 上的插件方法。這些插件直接接收 store 做爲惟一參數,能夠監聽 mutation(用於外部地數據持久化、記錄或調試)或者提交 mutation (用於內部數據,例如 websocket 或 某些觀察者)

 

 

Vuex.Store 實例屬性

  • state

類型: Object
根狀態,只讀。

  • getters

類型: Object
暴露出註冊的 getter,只讀。

 

Vuex.Store 實例方法

  • commit(type: string, payload?: any) | commit(mutation: Object)

提交 mutation。 

 

  • dispatch(type: string, payload?: any) | dispatch(action: Object)

分發 action。返回 action 方法的返回值,若是多個處理函數被觸發,那麼返回一個 Pormise。 

 

  • replaceState(state: Object)

替換 store 的根狀態,僅用狀態合併或 time-travel 調試。

 

  • watch(getter: Function, cb: Function, options?: Object)

響應式地監測一個 getter 方法的返回值,當值改變時調用回調函數。getter 接收 store 的狀態做爲惟一參數。接收一個可選的對象參數表示 Vue 的 vm.$watch 方法的參數。

 

  • subscribe(handler: Function)

註冊監聽 store 的 mutation。handler 會在每一個 mutation 完成後調用,接收 mutation 和通過 mutation 後的狀態做爲參數:
store.subscribe((mutation, state) => {
  console.log(mutation.type)
  console.log(mutation.payload)
})
一般用於插件

 

  • registerModule(path: string | Array<string>, module: Module)

註冊一個動態模塊。 詳細介紹

 

  • unregisterModule(path: string | Array<string>)

卸載一個動態模塊。 詳細介紹

相關文章
相關標籤/搜索