Vue 入門之 Vuex 實戰

Vue 入門之 Vuex 實戰

引言

Vue 組件化作的確實很是完全,它獨有的 vue 單文件組件也是作的很是有特點。組件化的同時帶來的是:組件之間的數據共享和通訊的難題。 尤爲 Vue 組件設計的就是,父組件經過子組件的 prop 進行傳遞數據,並且數據傳遞是單向的。也就是說:父組件能夠把數據傳遞給子組件,可是 反之則不一樣。以下圖所示:css

vue父子傳遞

單向數據流動

單方向的數據流動帶來了很是簡潔和清晰的數據流,純展現性或者獨立性較強的模塊的開發確實很是方便和省事。 可是複雜的頁面邏輯,組件之間的數據共享處理就會須要經過事件總線的方式解決或者使用 Vue 的 Vuex 框架了。html

子組件通知父組件數據更新:事件方式的實現

子組件能夠在子組件內觸發事件,而後在父容器中添加子組件時綁定父容器的方法爲事件響應方法的方式.以下圖所示:vue

vue父子傳遞

  • 使用 v-on 綁定自定義事件
每一個 Vue 實例都實現了事件接口(Events interface),即:
使用 $on(eventName) 監聽事件
使用 $emit(eventName) 觸發事件

參考代碼案例:jquery

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Vue入門之event message</title>
  <!-- 新 Bootstrap 核心 CSS 文件 -->
  <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css">

  <!-- 可選的Bootstrap主題文件(通常不用引入) -->
  <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap-theme.min.css">

  <!-- jQuery文件。務必在bootstrap.min.js 以前引入 -->
  <script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>

  <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
  <script src="http://cdn.bootcss.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>

  <script src="https://unpkg.com/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>

<body>
  <div id="app">
    <p>推薦次數:{{ voteCount }}</p>
    <hr>
    <!--綁定兩個自定義事件,當組件內部觸發了事件後,會自定調用父容器綁定的methods的方法,達到了子容器向父容器數據進行通訊同步的方法-->
    <vote-btn v-on:vote="voteAction" v-on:sendmsg="sendMsgAction"></vote-btn>
    <hr>
    <ul class="list-group">
      <li v-for="o in msg" class="list-group-item">{{o}}</li>
    </ul>
  </div>
  <script>
    Vue.component('vote-btn', {
      template: `
        <div>
          <button class="btn btn-success" v-on:click="voteArticle">推薦</button>
          <hr/>
          <input type="text" v-model="txtMsg" />
          <button v-on:click="sendMsg" class="btn btn-success">發送消息</button>
        </div>
      `,
      data: function () {
        return {
          txtMsg: ""
        }
      },
      methods: {
        voteArticle: function () {
          // 觸發事件,vote
          this.$emit('vote')
        },
        sendMsg: function () {
          // 觸發事件,sendmsg,並
          this.$emit('sendmsg', this.txtMsg)
        }
      }
    })

    var app = new Vue({
      el: '#app',
      data: {
        voteCount: 0,
        msg: []
      },
      methods: {
        voteAction: function() {  // 事件觸發後,會直接執行此方法
          this.voteCount += 1
        },
        sendMsgAction: function (item) {
          this.msg.push(item)
        }
      }
    });
  </script>
</body>

</html>

 

事件總線方式解決非父子組件數據同步

若是非父子組件怎麼經過事件進行同步數據,或者同步消息呢?Vue 中的事件觸發和監聽都是跟一個具體的 Vue 實例掛鉤。 因此在不一樣的 Vue 實例中想進行事件的統一跟蹤和觸發,那就須要一個公共的 Vue 實例,這個實例就是公共的事件對象。git

參考下面作的一個購物車的案例的代碼:github

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Vue入門之event message</title>
  <!-- 新 Bootstrap 核心 CSS 文件 -->
  <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css">

  <!-- 可選的Bootstrap主題文件(通常不用引入) -->
  <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap-theme.min.css">

  <!-- jQuery文件。務必在bootstrap.min.js 以前引入 -->
  <script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>

  <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
  <script src="http://cdn.bootcss.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>

  <script src="https://unpkg.com/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>

<body>
  <div id="app">
    <product-list :products="products" v-on:addpro="addToCarts"> </product-list>
    <hr>
    <cart :cart-products="carts"> </cart>
  </div>
  <script>
    var eventBus = new Vue();
    Vue.component('cart', {
      template: `
      <table class="table table-borderd table-striped table-hover">
      <thead>
        <tr>
          <th>商品編號</th>
          <th>商品名</th>
          <th>數量</th>
          <th>操做</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in cartProducts">
          <td>{{ item.id }}</td>
          <td>{{ item.name }}</td>
          <td>
            {{ item.count }}
          </td>
          <td>
            <button type="button" @click="removeCarts(item)" class="btn btn-success"><i class="glyphicon glyphicon-remove"></i></button>
          </td>
        </tr>
      </tbody>
    </table>
      `,
      data: function () {
        return {
        }
      },
      methods: {
        removeCarts: function (item) {
          eventBus.$emit('remo', item)
        }
      },
      props: ['cartProducts']
    })

    Vue.component('product-list', {
      template: `
      <table class="table table-borderd table-striped table-hover">
      <thead>
        <tr>
          <th>商品編號</th>
          <th>商品名</th>
          <th>操做</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in products">
          <td>{{ item.id }}</td>
          <td>{{ item.name }}</td>
          <td>
            <button type="button" v-on:click="addToCarts(item)" class="btn btn-success"><i class="glyphicon glyphicon-shopping-cart"></i></button>
          </td>
        </tr>
      </tbody>
    </table>
      `,
      data: function () {
        return {
        }
      },
      methods: {
        addToCarts: function (item) {
          this.$emit('addpro', item)
        }
      },
      props: ['products'],
    })

    var app = new Vue({
      el: '#app',
      data: {
        products: [
          { id: '1', name: '鱷魚' },
          { id: '2', name: '' },
          { id: '3', name: '兔子' },
          { id: '4', name: '' },
          { id: '5', name: '孔雀' }
        ],
        carts: []
      },
      methods: {
        addToCarts: function (item) {
          var isExist = false
          for(var i=0; i<this.carts.length; i++) {
            if( item.id === this.carts[i].id ) {
              item.count = this.carts[i].count + 1
              Vue.set(this.carts, i, item)
              isExist = true
            }
          }
          !isExist && (item.count = 1, this.carts.push(item))
        },
        removeCarts: function (item) {
          for(var i =0; i<this.carts.length; i++) {
            if( item.id === this.carts[i].id) {
              this.carts.splice(i,1)
            }
          }
        }
      },
      mounted: function () {
        self = this;
        eventBus.$on('remo', function (item) {
          self.removeCarts(item)
        })
      }
    });
  </script>
</body>
</html>

 

Vuex 解決複雜單頁面應用

上面的方式只能解決一些簡單的頁面中的組件的通訊問題,可是若是是複雜的單頁面應用就須要使用更強大的 Vuex 來幫咱們進行狀態的統一管理和同步。vue-router

當第一次接觸 Vuex 的時候,眼前一亮,以前通過 Redux 以後,被它繁瑣的使用令我痛苦不已,雖然思路很清晰,其實徹底能夠設計的更簡單和高效。 當我接觸到 Vuex 以後,發現這就是我想要的。的確簡潔就是一種藝術。vuex

其實本質上,Vuex 就是一個大的 EventBus 對象的升級版本,至關於一個特定的倉庫,全部數據都在統一的倉庫中,進行統一的管理。bootstrap

幾個核心的概念:api

  • StateVuex 倉庫中的數據。
  • Getter: 相似於 Vue 實例中的計算屬性,Getter 就是普通的獲取 state 包裝函數。
  • Mutations: Vuex 的 store 中的狀態的惟一方法是提交 mutation。Vuex 中的 mutations 很是相似於事件:每一個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。
  • Action: action 能夠觸發 Mutations,不能直接改變 state。

看下面一張圖瞭解一下 Vuex 總體的數據流動:

再看一張具體點的:

vuex

建立Vuex實例 Vuex.Store

咱們能夠經過Vuex提供的Vuex.Store構造器來構造一個Vuexstore實例。

import Vuex from 'vuex'

const store = new Vuex.Store({ ...options })

 

如下是關於Vuex.Store 構造器選項的說明。

state

  • 類型: Object | Function

    Vuex store 實例的根 state 對象。

    若是你傳入返回一個對象的函數,其返回的對象會被用做根 state。這在你想要重用 state 對象,尤爲是對於重用 module 來講很是有用。

單一狀態樹

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

單狀態樹和模塊化並不衝突——在後面的章節裏咱們會討論如何將狀態和狀態變動事件分佈到各個子模塊中。

在 Vue 組件中得到 Vuex 狀態

那麼咱們如何在 Vue 組件中展現狀態呢?因爲 Vuex 的狀態存儲是響應式的,從 store 實例中讀取狀態最簡單的方法就是在計算屬性中返回某個狀態:

// 建立一個 Counter 組件
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return store.state.count
    }
  }
}

 

每當 store.state.count 變化的時候, 都會從新求取計算屬性,而且觸發更新相關聯的 DOM。

然而,這種模式致使組件依賴全局狀態單例。在模塊化的構建系統中,在每一個須要使用 state 的組件中須要頻繁地導入,而且在測試組件時須要模擬狀態。

Vuex 經過 store 選項,提供了一種機制將狀態從根組件「注入」到每個子組件中(需調用 Vue.use(Vuex)):

const app = new Vue({
  el: '#app',
  // 把 store 對象提供給 「store」 選項,這能夠把 store 的實例注入全部的子組件
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})

 

經過在根實例中註冊 store 選項,該 store 實例會注入到根組件下的全部子組件中,且子組件能經過 this.$store 訪問到。讓咱們更新下 Counter 的實現:

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

 

mapState 輔助函數

當一個組件須要獲取多個狀態時候,將這些狀態都聲明爲計算屬性會有些重複和冗餘。爲了解決這個問題,咱們可使用 mapState 輔助函數幫助咱們生成計算屬性,讓你少按幾回鍵:

// 在單獨構建的版本中輔助函數爲 Vuex.mapState
import { mapState } from 'vuex'

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

    // 傳字符串參數 'count' 等同於 `state => state.count`
    countAlias: 'count',

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

 

當映射的計算屬性的名稱與 state 的子節點名稱相同時,咱們也能夠給 mapState 傳一個字符串數組。

computed: mapState([
  // 映射 this.count 爲 store.state.count
  'count'
])

 

對象展開運算符

mapState 函數返回的是一個對象。咱們如何將它與局部計算屬性混合使用呢?一般,咱們須要使用一個工具函數將多個對象合併爲一個,以使咱們能夠將最終對象傳給 computed 屬性。可是自從有了對象展開運算符(現處於 ECMASCript 提案 stage-4 階段),咱們能夠極大地簡化寫法:

computed: {
  localComputed () { /* ... */ },
  // 使用對象展開運算符將此對象混入到外部對象中
  ...mapState({
    // ...
  })
}

 

組件仍然保有局部狀態

使用 Vuex 並不意味着你須要將全部的狀態放入 Vuex。雖然將全部的狀態放到 Vuex 會使狀態變化更顯式和易調試,但也會使代碼變得冗長和不直觀。若是有些狀態嚴格屬於單個組件,最好仍是做爲組件的局部狀態。你應該根據你的應用開發須要進行權衡和肯定。

mutations

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

    在 store 上註冊 mutation,處理函數老是接受 state 做爲第一個參數(若是定義在模塊中,則爲模塊的局部狀態),payload 做爲第二個參數(可選)。 更改 Vuex 的 store 中的狀態的惟一方法是提交 mutation。Vuex 中的 mutation 很是相似於事件:每一個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是咱們實際進行狀態更改的地方,而且它會接受 state 做爲第一個參數:

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

 

你不能直接調用一個 mutation handler。這個選項更像是事件註冊:「當觸發一個類型爲 increment 的 mutation 時,調用此函數。」要喚醒一個 mutation handler,你須要以相應的 type 調用 store.commit 方法:

store.commit('increment')

 

提交載荷(Payload)

你能夠向 store.commit 傳入額外的參數,即 mutation 的 載荷(payload):

// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}

 

store.commit('increment', 10)

 

在大多數狀況下,載荷應該是一個對象,這樣能夠包含多個字段而且記錄的 mutation 會更易讀:

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

 

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

 

對象風格的提交方式

提交 mutation 的另外一種方式是直接使用包含 type 屬性的對象:

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

 

當使用對象風格的提交方式,整個對象都做爲載荷傳給 mutation 函數,所以 handler 保持不變:

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

 

Mutation 需遵照 Vue 的響應規則

既然 Vuex 的 store 中的狀態是響應式的,那麼當咱們變動狀態時,監視狀態的 Vue 組件也會自動更新。這也意味着 Vuex 中的 mutation 也須要與使用 Vue 同樣遵照一些注意事項:

  1. 最好提早在你的 store 中初始化好全部所需屬性。

  2. 當須要在對象上添加新屬性時,你應該

    • 使用 Vue.set(obj, 'newProp', 123), 或者

    • 以新對象替換老對象。例如,利用 stage-3 的對象展開運算符咱們能夠這樣寫:

      state.obj = { ...state.obj, newProp: 123 }

       

使用常量替代 Mutation 事件類型

使用常量替代 mutation 事件類型在各類 Flux 實現中是很常見的模式。這樣可使 linter 之類的工具發揮做用,同時把這些常量放在單獨的文件中可讓你的代碼合做者對整個 app 包含的 mutation 一目瞭然:

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

 

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

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // 咱們可使用 ES2015 風格的計算屬性命名功能來使用一個常量做爲函數名
    [SOME_MUTATION] (state) {
      // mutate state
    }
  }
})

 

用不用常量取決於你——在須要多人協做的大型項目中,這會頗有幫助。但若是你不喜歡,你徹底能夠不這樣作。

Mutation 必須是同步函數

一條重要的原則就是要記住 mutation 必須是同步函數。爲何?請參考下面的例子:

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

 

如今想象,咱們正在 debug 一個 app 而且觀察 devtool 中的 mutation 日誌。每一條 mutation 被記錄,devtools 都須要捕捉到前一狀態和後一狀態的快照。然而,在上面的例子中 mutation 中的異步函數中的回調讓這不可能完成:由於當 mutation 觸發的時候,回調函數尚未被調用,devtools 不知道何時回調函數實際上被調用——實質上任何在回調函數中進行的狀態的改變都是不可追蹤的。

在組件中提交 Mutation

你能夠在組件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 輔助函數將組件中的 methods 映射爲 store.commit 調用(須要在根節點注入 store)。

 
import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 將 `this.increment()` 映射爲 `this.$store.commit('increment')`

      // `mapMutations` 也支持載荷:
      'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 將 `this.add()` 映射爲 `this.$store.commit('increment')`
    })
  }
}
下一步
 

 

:Action

在 mutation 中混合異步調用會致使你的程序很難調試。例如,當你調用了兩個包含異步回調的 mutation 來改變狀態,你怎麼知道何時回調和哪一個先回調呢?這就是爲何咱們要區分這兩個概念。在 Vuex 中,mutation 都是同步事務:

store.commit('increment')
// 任何由 "increment" 致使的狀態變動都應該在此刻完成。

 

actions

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

    在 store 上註冊 action。處理函數老是接受 context 做爲第一個參數,payload 做爲第二個參數(可選)。

    context 對象包含如下屬性:

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

    同時若是有第二個參數 payload 的話也可以接收。

    詳細介紹

getters

  • 類型: { [key: string]: Function }

在 store 上註冊 getter,getter 方法接受如下參數:

state,     // 若是在模塊中定義則爲模塊的局部狀態
  getters,   // 等同於 store.getters

當定義在一個模塊裏時會特別一些:

state,       // 若是在模塊中定義則爲模塊的局部狀態
  getters,     // 等同於 store.getters
  rootState    // 等同於 store.state
  rootGetters  // 全部 getters

註冊的 getter 暴露爲 store.getters

Getter 接受 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)
    }
  }
})

 

mapGetters 輔助函數

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

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用對象展開運算符將 getter 混入 computed 對象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

 

若是你想將一個 getter 屬性另取一個名字,使用對象形式:

mapGetters({
  // 把 `this.doneCount` 映射爲 `this.$store.getters.doneTodosCount`
  doneCount: 'doneTodosCount'
})

 

modules

  • 類型: Object

    包含了子模塊的對象,會被合併到 store,大概長這樣:

    { key: { state, namespaced?, mutations, actions?, getters?, modules? }, ... }

    與根模塊的選項同樣,每一個模塊也包含 state 和 mutations 選項。模塊的狀態使用 key 關聯到 store 的根狀態。模塊的 mutation 和 getter 只會接收 module 的局部狀態做爲第一個參數,而不是根狀態,而且模塊 action 的 context.state 一樣指向局部狀態。

strict

  • 類型: Boolean
  • 默認值: false

    使 Vuex store 進入嚴格模式,在嚴格模式下,任何 mutation 處理函數之外修改 Vuex state 都會拋出錯誤。

    詳細介紹

Vuex.Store 實例屬性

state

  • 類型: Object

    根狀態,只讀。

getters

  • 類型: Object

    暴露出註冊的 getter,只讀。

Vuex.Store 實例方法

commit

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

    提交 mutation。options 裏能夠有 root: true,它容許在命名空間模塊裏提交根的 mutation。詳細介紹

dispatch

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

    分發 action。options 裏能夠有 root: true,它容許在命名空間模塊裏分發根的 action。返回一個解析全部被觸發的 action 處理器的 Promise。詳細介紹

replaceState

  • replaceState(state: Object)

    替換 store 的根狀態,僅用狀態合併或時光旅行調試。

watch

  • watch(fn: Function, callback: Function, options?: Object): Function

    響應式地偵聽 fn 的返回值,當值改變時調用回調函數。fn 接收 store 的 state 做爲第一個參數,其 getter 做爲第二個參數。最後接收一個可選的對象參數表示 Vue 的 vm.$watch 方法的參數。

    要中止偵聽,調用此方法返回的函數便可中止偵聽。

subscribe

  • subscribe(handler: Function): Function

    訂閱 store 的 mutation。handler 會在每一個 mutation 完成後調用,接收 mutation 和通過 mutation 後的狀態做爲參數:

    store.subscribe((mutation, state) => {
      console.log(mutation.type)
      console.log(mutation.payload)
    })

     

    要中止訂閱,調用此方法返回的函數便可中止訂閱。

    一般用於插件。詳細介紹

subscribeAction

  • subscribeAction(handler: Function): Function

    2.5.0 新增

    訂閱 store 的 action。handler 會在每一個 action 分發的時候調用並接收 action 描述和當前的 store 的 state 這兩個參數:

    store.subscribeAction((action, state) => {
      console.log(action.type)
      console.log(action.payload)
    })

     

    要中止訂閱,調用此方法返回的函數便可中止訂閱。

    該功能經常使用於插件。詳細介紹

registerModule

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

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

    options 能夠包含 preserveState: true 以容許保留以前的 state。用於服務端渲染。

unregisterModule

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

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

hotUpdate

  • hotUpdate(newOptions: Object)

    熱替換新的 action 和 mutation。詳細介紹

組件綁定的輔助函數

mapState

  • mapState(namespace?: string, map: Array<string> | Object<string | function>): Object

    爲組件建立計算屬性以返回 Vuex store 中的狀態。詳細介紹

    第一個參數是可選的,能夠是一個命名空間字符串。詳細介紹

    對象形式的第二個參數的成員能夠是一個函數。function(state: any)

// 在單獨構建的版本中輔助函數爲 Vuex.mapState
import { mapState } from 'vuex'

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

    // 傳字符串參數 'count' 等同於 `state => state.count`
    countAlias: 'count',

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

 

mapGetters

  • mapGetters(namespace?: string, map: Array<string> | Object<string>): Object

    爲組件建立計算屬性以返回 getter 的返回值。詳細介紹

    第一個參數是可選的,能夠是一個命名空間字符串。詳細介紹

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用對象展開運算符將 getter 混入 computed 對象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

 

mapActions

  • mapActions(namespace?: string, map: Array<string> | Object<string | function>): Object

    建立組件方法分發 action。詳細介紹

    第一個參數是可選的,能夠是一個命名空間字符串。詳細介紹

    對象形式的第二個參數的成員能夠是一個函數。function(dispatch: function, ...args: any[])

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 將 `this.increment()` 映射爲 `this.$store.dispatch('increment')`

      // `mapActions` 也支持載荷:
      'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 將 `this.add()` 映射爲 `this.$store.dispatch('increment')`
    })
  }
}

 

mapMutations

  • mapMutations(namespace?: string, map: Array<string> | Object<string | function>): Object

    建立組件方法提交 mutation。詳細介紹

    第一個參數是可選的,能夠是一個命名空間字符串。詳細介紹

    對象形式的第二個參數的成員能夠是一個函數。function(commit: function, ...args: any[])

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 將 `this.increment()` 映射爲 `this.$store.commit('increment')`

      // `mapMutations` 也支持載荷:
      'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 將 `this.add()` 映射爲 `this.$store.commit('increment')`
    })
  }
}

 

createNamespacedHelpers

  • createNamespacedHelpers(namespace: string): Object

    建立基於命名空間的組件綁定輔助函數。其返回一個包含 mapStatemapGettersmapActions 和 mapMutations 的對象。它們都已經綁定在了給定的命名空間上。詳細介紹

模塊

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

      // 模塊內容(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 會收到局部化的 getterdispatch 和 commit。換言之,你在使用模塊內容(module assets)時不須要在同一模塊內額外添加空間名前綴。更改 namespaced 屬性後不須要修改模塊內的代碼。

在帶命名空間的模塊內訪問全局內容(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) { ... }
    }
  }
}

 

在帶命名空間的模塊註冊全局 action

若須要在帶命名空間的模塊註冊全局 action,你可添加 root: true,並將這個 action 的定義放在函數 handler中。例如:

{
  actions: {
    someOtherAction ({dispatch}) {
      dispatch('someAction')
    }
  },
  modules: {
    foo: {
      namespaced: true,

      actions: {
        someAction: {
          root: true,
          handler (namespacedContext, payload) { ... } // -> 'someAction'
        }
      }
    }
  }
}

 

帶命名空間的綁定函數

當使用 mapStatemapGettersmapActions 和 mapMutations 這些函數來綁定帶命名空間的模塊時,寫起來可能比較繁瑣:

computed: {
  ...mapState({
    a: state => state.some.nested.module.a,
    b: state => state.some.nested.module.b
  })
},
methods: {
  ...mapActions([
    'some/nested/module/foo', // -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()
  ])
}

 

對於這種狀況,你能夠將模塊的空間名稱字符串做爲第一個參數傳遞給上述函數,這樣全部綁定都會自動將該模塊做爲上下文。因而上面的例子能夠簡化爲:

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.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 })

相關文章
相關標籤/搜索