前端MVC Vue2學習總結(九)——Vuex狀態管理插件

1、概要

1.一、Vuex定義與注意事項

Vuex是爲vue.js框架更好的管理狀態而設計一個插件。Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。Vuex 也集成到 Vue 的官方調試工具 devtools extension,提供了諸如零配置的 time-travel 調試、狀態快照導入導出等高級調試功能。javascript

使用Vue開發中須要將應用拆分紅多個組件,可是組件與組件之間數據共享成了一個問題,父子組件實現起來相對簡單,有不少兄弟組件和跨多級組件,實現起來過程繁瑣,在多人協同開發上,不利於統一管理,Vuex能夠解決這些問題。html

1.1.一、狀態管理模式

沒有使用Vuex時,讓咱們看一個簡單的 Vue 計數應用:前端

new Vue({
  // state
  data () {
    return {
      count: 0
    }
  },
  // view
  template:'<div>{{ count }}</div>',
  // actions
  methods: {
    increment () {
      this.count++
    }
  }
})

這個狀態自管理應用包含如下幾個部分:vue

  • state,驅動應用的數據源;
  • view,以聲明方式將 state 映射到視圖;
  • actions,響應在 view 上的用戶輸入致使的狀態變化。

如下是一個表示「單向數據流」理念的極簡示意:java

可是,當咱們的應用遇到多個組件共享狀態時,單向數據流的簡潔性很容易被破壞:node

  • 多個視圖依賴於同一狀態。
  • 來自不一樣視圖的行爲須要變動同一狀態。

對於問題一,傳參的方法對於多層嵌套的組件將會很是繁瑣,而且對於兄弟組件間的狀態傳遞無能爲力。對於問題二,咱們常常會採用父子組件直接引用或者經過事件來變動和同步狀態的多份拷貝。以上的這些模式很是脆弱,一般會致使沒法維護的代碼。git

所以使用Vuex,咱們爲何不把組件的共享狀態抽取出來,以一個全局單例模式管理呢?在這種模式下,咱們的組件樹構成了一個巨大的「視圖」,無論在樹的哪一個位置,任何組件都能獲取狀態或者觸發行爲!es6

另外,經過定義和隔離狀態管理中的各類概念並強制遵照必定的規則,咱們的代碼將會變得更結構化且易維護。github

這就是 Vuex 背後的基本思想,借鑑了  FluxRedux、和  The Elm Architecture。與其餘模式不一樣的是,Vuex 是專門爲 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。spring

 

1.1.二、使用 Vuex

雖然 Vuex 能夠幫助咱們管理共享狀態,但也附帶了更多的概念和框架。這須要對短時間和長期效益進行權衡。

若是您不打算開發大型單頁應用,使用 Vuex 多是繁瑣冗餘的。確實是如此——若是您的應用夠簡單,您最好不要使用 Vuex。一個簡單的  store 模式就足夠您所需了。可是,若是您須要構建一箇中大型單頁應用,您極可能會考慮如何更好地在組件外部管理狀態,Vuex 將會成爲天然而然的選擇。引用 Redux 的做者 Dan Abramov 的話說就是:"Flux 架構就像眼鏡:您自會知道何時須要它。"

1.1.三、注意事項

Vuex會有必定的門檻和複雜性,它的主要使用場景是大型單頁面應用,若是你的項目不是很複雜,用一個bus也能夠實現數據的共享(在前面講組件的內容中已經講到過bus做爲總線進行通訊的示例),可是它在數據管理,維護,還只是一個簡單的組件,而Vuex能夠更優雅高效地完成狀態管理,因此,是否使用Vuex取決於你的團隊和技術儲備。

使用bus做爲總線通訊的示例:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Vue2 Demo</title>
</head>
<body>
<div id="app01">
    <my-comp1></my-comp1>
    <my-comp2></my-comp2>
</div>
<script src="../../js/vue/vue.js"></script>
<script>
    //事件總線
    var bus = new Vue();

    Vue.component("my-comp1", {
        template: "<button @click='incrN'>{{n}}</button>",
        data() {
            return {n: 0}
        },
        methods: {
            incrN: function () {
                this.n++;
                //發佈事件
                bus.$emit("inc",this.n);
            }
        }
    });


    Vue.component("my-comp2", {
        template: "<button @click='incrN'>{{n}}</button>",
        data() {
            return {n: 999}
        },
        methods: {
            incrN: function () {
                this.n--;
            }
        },
        //勾子,掛載完成時執行事件
        mounted:function () {
            var _this=this;
            //監聽事件,訂閱事件
            bus.$on("inc",function (val) {
                _this.n+=val;
            })
        }
    });

    var vm = new Vue({
        el: "#app01",
        data: {}
    });
</script>
</body>
</html>
View Code

1.二、概念

每個 Vuex 應用的核心就是store(倉庫),store基本上就是一個容器,它包含着你的應用中大部分的狀態 (state)。 Vuex和單純的全局對象有如下兩點不一樣:

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

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

  • store:表示對Vuex對象的全局引用。組件經過Store來訪問Vuex對象中的State。
  • state:保存數據的狀態、對象的狀態,即其所擁有的數據。
  • getter:至關於Store的計算屬性。由於就像計算屬性同樣,Getter的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被從新計算。下面會說到具體的使用場景。
  • mutations:定義了對state中數據的修改操做,更改store中的狀態的惟一方法是提交mutation。mutation相似於事件:每一個mutation都有一個字符串的事 件類型(type),和一個回調函數(handler).利用store.commit('方法名')來調用這個函數。
  • mutations-type:能夠認爲是store中的計算屬性,mapGetters是輔助函數,僅僅將store中的getter映射到局部計算屬性。
  • action :mutation中定義的操做只能執行同步操做,Vuex中的異步操做在Action中進行,Action最終經過調用Mutation的操做來更新數據;相似於mutation,不一樣在於action提交的是mutation,而不是直接變動狀態,action能夠包含任意 異步操做。action用store.dispatch方法觸發函數。mapActions是輔助函數,將組件的 methods 映射爲store.dispatch。
  • module:Store和State之間的一層,便於大型項目管理,Store包含多個Module,Module包含State、Mutation和Action。

1.三、資源

github: https://github.com/vuejs/vuex

中文幫助: https://vuex.vuejs.org/zh/(本文大量引用)

英文幫助: https://vuex.vuejs.org/

視頻教程: https://www.bilibili.com/video/av17503637/

2、安裝

2.一、直接下載或CDN 引用

引用地址: https://unpkg.com/vuex

Unpkg.com 提供了基於 NPM 的 CDN 連接。以上的連接會一直指向 NPM 上發佈的最新版本。您也能夠經過 https://unpkg.com/vuex@3.0.1/dist/vuex.js這樣的方式指定特定的版本。

CDN引用:

<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>

也可使用下載後本地引用,vuex的github地址是: https://github.com/vuejs/vuex

2.二、NPM包管理器

npm i vuex --save

2.三、Yarn

yarn add vuex

在一個模塊化的打包系統中,您必須顯式地經過 Vue.use() 來安裝 Vuex:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

//當使用全局 script 標籤引用 Vuex 時,不須要以上安裝過程。

2.四、Promise

Vuex 依賴 Promise。若是你支持的瀏覽器並無實現 Promise (好比 IE),那麼你可使用一個polyfill的庫,例如 es6-promise。

你能夠經過 CDN 將其引入:

<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js"></script>

而後 window.Promise 會自動可用。

若是你喜歡使用諸如 npm 或 Yarn 等包管理器,能夠按照下列方式執行安裝:

npm install es6-promise --save # npm
yarn add es6-promise # Yarn

或者更進一步,將下列代碼添加到你使用 Vuex 以前的一個地方:

import 'es6-promise/auto'

2.五、本身構建

若是須要使用 dev 分支下的最新版本,您能夠直接從 GitHub 上克隆代碼並本身構建。

git clone https://github.com/vuejs/vuex.git node_modules/vuex
cd node_modules/vuex
npm install
npm run build

3、應用

3.一、頁面使用Vuex快速起步

建立一個 store,建立過程直截了當——僅須要提供一個初始 state 對象和一些 mutation:

// 若是在模塊化構建系統中,請確保在開頭調用了 Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

如今,你能夠經過 store.state 來獲取狀態對象,以及經過 store.commit 方法觸發狀態變動:

store.commit('increment')

console.log(store.state.count) // -> 1

再次強調,咱們經過提交 mutation 的方式,而非直接改變 store.state.count,是由於咱們想要更明確地追蹤到狀態的變化。這個簡單的約定可以讓你的意圖更加明顯,這樣你在閱讀代碼的時候能更容易地解讀應用內部的狀態改變。此外,這樣也讓咱們有機會去實現一些能記錄每次狀態改變,保存狀態快照的調試工具。有了它,咱們甚至能夠實現如時間穿梭般的調試體驗。

因爲 store 中的狀態是響應式的,在組件中調用 store 中的狀態簡單到僅須要在計算屬性中返回便可。觸發變化也僅僅是在組件的 methods 中提交 mutation。

完整示例代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <p>{{ count }}</p>
    <p>
        <button @click="increment">+</button>
        <button @click="decrement">-</button>
    </p>
    <div>
        <comp1></comp1>
        <comp1></comp1>
    </div>
</div>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script>

    //定義倉庫對象
    const store = new Vuex.Store({
        state: {
            count: 0
        },
        mutations: {
            increment: state => state.count++,
            decrement: state => state.count--
        }
    })

    //定義組件
    Vue.component("comp1",{
        template:"<h2>{{count}}</h2>",
        computed: {
            count () {
                return store.state.count
            }
        }
    });

    new Vue({
        el: '#app',
        computed: {
            count () {
                return store.state.count
            }
        },
        methods: {
            increment () {
                store.commit('increment')
            },
            decrement () {
                store.commit('decrement')
            }
        }
    })
</script>
</body>
</html>

運行結果:

 

在線示例:這是一個 最基本的 Vuex 記數應用示例

3.二、Vue-cli中使用Vuex快速起步

安裝vuex:

插件引用:

//導入插件
import Vuex from 'vuex'

//使用插件
Vue.use( Vuex );

//定義倉庫對象
const store = new Vuex.Store({
    //屬性
})

//定義vue實例並關聯存儲倉庫
new Vue({
    el: '#app',
    store,
    render: h => h(App)
});

定義狀態對象main.js:

import Vue from 'vue'
import Vuex from 'vuex';
import App from './App'
import router from './router/hello'

Vue.config.productionTip = false

Vue.use(Vuex);

//定義倉庫對象
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment: state => state.count++,
    decrement: state => state.count--
  }
})

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  render:r=>r(App)
})

直接使用狀態對象中的數據App.vue:

<template>
  <div>
    <img src="./assets/logo.png">
    <h2>
      {{$store.state.count}}
    </h2>
    <Counter/>
    <header>
      <!-- router-link 定義點擊後導航到哪一個路徑下 -->
      <router-link to="/" exact>index</router-link>
      <router-link to="/foo">Go to Foo</router-link>
      <router-link to="/bar">Go to Bar</router-link>
    </header>
    <!-- 對應的組件內容渲染到router-view中 -->
    <router-view></router-view>
  </div>
</template>
<script>
  import Counter from './components/Counter';

  export default {
    components:{Counter}
  }
</script>
<style scoped>
  a {
    color: #777;
  }

  a:hover {
    color: orangered;
  }
</style>
View Code

在組件中使用狀態對象:

//Counter.vue
<template>
  <div id="app">
    <h2>{{ count }}</h2>
    <p>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </p>
  </div>
</template>

<script>
  export default {
    name: "Counter",
    computed: {
      count() {
        return this.$store.state.count
      }
    },
    methods: {
      increment() {
        this.$store.commit('increment')
      },
      decrement() {
        this.$store.commit('decrement')
      }
    }
  }
</script>

<style scoped>
  h2 {
    color: darkblue;
  }
</style>

引用組件:

//bar.vue

<template>
  <div>
    <h2>Bar</h2>
    <p>{{msg}}</p>
    <Counter/>
  </div>
</template>
<script>
  import Counter from './Counter';

  export default {
    data() {
      return {
        msg: "我是Bar組件"
      }
    },
    components: {Counter}
  }
</script>
<style scoped>
  h2 {
    color: springgreen;
  }
</style>

App.Vue內容:

<template>
  <div>
    <img src="./assets/logo.png">
    <h2>
      {{$store.state.count}}
    </h2>
    <Counter/>
    <header>
      <!-- router-link 定義點擊後導航到哪一個路徑下 -->
      <router-link to="/" exact>index</router-link>
      <router-link to="/foo">Go to Foo</router-link>
      <router-link to="/bar">Go to Bar</router-link>
    </header>
    <!-- 對應的組件內容渲染到router-view中 -->
    <router-view></router-view>
  </div>
</template>
<script>
  import Counter from './components/Counter';

  export default {
    components:{Counter}
  }
</script>
<style scoped>
  a {
    color: #777;
  }

  a:hover {
    color: orangered;
  }
</style>

運行結果:

切換到一個單頁Bar

3.三、沒有使用vuex的汽車列表(示例)

爲了方便後面的內容講解這裏從新作一個簡單的vuex汽車列表示例,這個示例分別有兩個組件CarListOne.vue和CarListTwo.vue, 在App.vue的datat中保存着共有的汽車列表, 代碼和初始化的效果以下圖所示:

 

App.Vue:
<template>
  <div id="app">
    <h2>汽車商城</h2>
    <hr/>
    <car-list-one v-bind:cars="cars"></car-list-one>
    <car-list-two v-bind:cars="cars"></car-list-two>
  </div>
</template>

<script>
  import CarListOne from './components/CarListOne.vue'
  import CarListTwo from './components/CarListTwo.vue'

  export default {
    name: 'app',
    components: {
      'car-list-one': CarListOne,
      'car-list-two': CarListTwo
    },
    data() {
      return {
        cars: [
          {name: '奇瑞', price: 18.3},
          {name: '吉利', price: 19.6},
          {name: '長安', price: 17.5},
          {name: '紅旗', price: 21.9}
        ]
      }
    }
  }
</script>

<style>
  h2 {
    color: orangered;
  }
</style>

CarListOne.vue

<template>
  <div id="car-list-one">
    <h2>Car List One</h2>
    <ul>
      <li v-for="car in cars">
        <span>{{ car.name }}</span>
        <span>¥{{ car.price }}萬元</span>
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    props: ['cars'],
    data() {
      return {}
    }
  }
</script>

<style scoped>
  h2 {
    color: dodgerblue;
  }
</style>

CarListTwo.vue

<template>
  <div id="car-list-two">
    <h2>Car List Two</h2>
    <ul>
      <li v-for="car in cars">
        <button>{{ car.name }}</button>
        <button>¥{{ car.price }}萬元</button>
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    props: ['cars'],
    data() {
      return {}
    }
  }
</script>

<style scoped>
  h2 {
    color: limegreen;
  }
</style>

3.四、State

state就是Vuex中的公共的狀態, 我是將state看做是全部組件的data, 用於保存全部組件的公共數據。

此時咱們就能夠把App.vue中的兩個組件共同使用的data抽離出來, 放到state中,代碼以下:

//main.js
import Vue from 'vue'
import App from './App.vue'
import Vuex from 'vuex'

Vue.use( Vuex )

const store = new Vuex.Store({
  state:{ 
    cars: [
      {name: '奇瑞', price: 20},
      {name: '吉利', price: 40},
      {name: '長安', price: 60},
      {name: '比亞迪', price: 80}
    ]
  }
})

new Vue({
  el: '#app',
  store,
  render: h => h(App)
})
此時,CarListOne.vue和CarListTwo.vue也須要作相應的更改
//CarListOne.vue
export default {
    data () {
        return {
            cars : this.$store.state.cars //獲取store中state的數據
        }
    }
}
//CarListTwo.vue
export default {
    data () {
        return {
            cars: this.$store.state.cars //獲取store中state的數據
        }
    }
}

此時的頁面以下圖所示, 能夠看到, 將公共數據抽離出來後, 頁面沒有發生變化。

3.五、Getters

我將getters屬性理解爲全部組件的computed屬性, 也就是計算屬性。vuex的官方文檔也是說到能夠將getter理解爲store的計算屬性,getters的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被從新計算。

此時,咱們能夠在main.js中添加一個getters屬性, 其中的saleCars對象將state中的價格減小一半(除以2)

//main.js
const store = new Vuex.Store({
  state:{
    cars: [
      {name: '奇瑞', price: 20},
      {name: '吉利', price: 40},
      {name: '長安', price: 60},
      {name: '比亞迪', price: 80}
    ]
  },
  getters:{ //添加getters
    saleCars: (state) => {
      let saleCars = state.cars.map( car => {
        return {
          name: car.name,
          price: car.price / 2
        }
      })
      return saleCars;
    }
  } 
})
將carListOne.vue中的cars的值更換爲this.$store.getters.saleCars
export default {
    data () {
        return {
            cars : this.$store.getters.saleCars 
        }
    }
}

如今的頁面中,Car List One中的每項汽車的價格都減小了一半

 

getters 和 vue 中的 computed 相似 , 都是用來計算 state 而後生成新的數據 ( 狀態 ) 的。

仍是前面的例子 , 假如咱們須要一個與狀態 show 恰好相反的狀態 , 使用 vue 中的 computed 能夠這樣算出來 :

computed(){
    not_show(){
        return !this.$store.state.dialog.show;
    }
}
那麼 , 若是不少不少個組件中都須要用到這個與 show 恰好相反的狀態 , 那麼咱們須要寫不少不少個 not_show , 使用 getters 就能夠解決這種問題 :

export default {
    state:{//state
        show:false
    },
    getters:{
        not_show(state){//這裏的state對應着上面這個state
            return !state.show;
        }
    },
    mutations:{
        switch_dialog(state){//這裏的state對應着上面這個state
            state.show = state.show?false:true;
            //你還能夠在這裏執行其餘的操做改變state
        }
    },
    actions:{
        switch_dialog(context){//這裏的context和咱們使用的$store擁有相同的對象和方法
            context.commit('switch_dialog');
            //你還能夠在這裏觸發其餘的mutations方法
        },
    }
}
咱們在組件中使用 $store.state.dialog.show 來得到狀態 show , 相似的 , 咱們可使用 $store.getters.not_show 來得到狀態 not_show 。

注意 : $store.getters.not_show 的值是不能直接修改的 , 須要對應的 state 發生變化才能修改。

 

 

mapState、mapGetters、mapActions
不少時候 , $store.state.dialog.show 、$store.dispatch('switch_dialog') 這種寫法又長又臭 , 很不方便 , 咱們沒使用 vuex 的時候 , 獲取一個狀態只須要 this.show , 執行一個方法只須要 this.switch_dialog 就好了 , 使用 vuex 使寫法變複雜了 ?

使用 mapState、mapGetters、mapActions 就不會這麼複雜了。

以 mapState 爲例 :

<template>
  <el-dialog :visible.sync="show"></el-dialog>
</template>

<script>
import {mapState} from 'vuex';
export default {
  computed:{

    //這裏的三點叫作 : 擴展運算符
    ...mapState({
      show:state=>state.dialog.show
    }),
  }
}
</script>
至關於 :

<template>
  <el-dialog :visible.sync="show"></el-dialog>
</template>

<script>
import {mapState} from 'vuex';
export default {
  computed:{
    show(){
        return this.$store.state.dialog.show;
    }
  }
}
</script>
mapGetters、mapActions 和 mapState 相似 , mapGetters 通常也寫在 computed 中 , mapActions 通常寫在 methods 中。

 

 

3.六、Mutations

我將mutaions理解爲store中的methods, mutations對象中保存着更改數據的回調函數,該函數名官方規定叫type, 第一個參數是state, 第二參數是payload, 也就是自定義的參數.

下面,咱們在main.js中添加mutations屬性,其中minusPrice這個回調函數用於將汽車的價格減小payload這麼多, 代碼以下:

//main.js
const store = new Vuex.Store({
  state:{
    cars: [
      {name: '奇瑞', price: 20},
      {name: '吉利', price: 40},
      {name: '長安', price: 60},
      {name: '比亞迪', price: 80}
    ]
  },
  getters:{
    saleCars: (state) => {
      let saleCars = state.cars.map( car => {
        return {
          name: car.name,
          price: car.price / 2
        }
      })
      return saleCars;
    }
  },
  mutations:{ //添加mutations
    minusPrice (state, payload ) {
      let newPrice = state.cars.forEach( car => {
        car.price -= payload
      })
    }
  }
})
在CarListTwo.vue中添加一個按鈕,爲其添加一個點擊事件, 給點擊事件觸發minusPrice方法
//CarListTwo.vue
<template>
    <div id="car-list-two">
        <h2>Car List Two</h2>
        <ul>
            <li v-for="car in cars">
                <span class="name">{{ car.name }}</span>
                <span class="price">${{ car.price }}</span>
            </li>
            <button @click="minusPrice">減小价格</button> //添加按鈕
        </ul>
    </div>
</template>
在CarListTwo.vue中註冊minusPrice方法, 在該方法中commitmutations中的minusPrice這個回調函數
注意:調用mutaions中回調函數, 只能使用store.commit(type, payload)
//CarListTwo.vue
export default {
    data () {
        return {
            cars: this.$store.state.cars
        }
    },
    methods: {
        minusPrice() {
            this.$store.commit('minusPrice', 2); //提交`minusPrice,payload爲2
        }
    }
}

添加按鈕, 能夠發現, Car List Two中的價格減小了2, 固然你能夠自定義payload,以此自定義減小對應的價格.

mutations效果


(Car List One中的價格沒有發生變化, 是由於getters將價格進行了緩存)

 

前面咱們提到的對話框例子 , 咱們對vuex 的依賴僅僅只有一個 $store.state.dialog.show 一個狀態 , 可是若是咱們要進行一個操做 , 須要依賴不少不少個狀態 , 那管理起來又麻煩了 !

mutations 登場 , 問題迎刃而解 :

export default {
    state:{//state
        show:false
    },
    mutations:{
        switch_dialog(state){//這裏的state對應着上面這個state
            state.show = state.show?false:true;
            //你還能夠在這裏執行其餘的操做改變state
        }
    }
}
使用 mutations 後 , 原先咱們的父組件能夠改成 :

<template>
  <div id="app">
    <a href="javascript:;" @click="$store.commit('switch_dialog')">點擊</a>
    <t-dialog></t-dialog>
  </div>
</template>

<script>
import dialog from './components/dialog.vue'
export default {
  components:{
    "t-dialog":dialog
  }
}
</script>
使用 $store.commit('switch_dialog') 來觸發 mutations 中的 switch_dialog 方法。

這裏須要注意的是:

mutations 中的方法是不分組件的 , 假如你在 dialog_stroe.js 文件中的定義了
switch_dialog 方法 , 在其餘文件中的一個 switch_dialog 方法 , 那麼
$store.commit('switch_dialog') 會執行全部的 switch_dialog 方法。
mutations裏的操做必須是同步的。
你必定好奇 , 若是在 mutations 裏執行異步操做會發生什麼事情 , 實際上並不會發生什麼奇怪的事情 , 只是官方推薦 , 不要在 mutationss 裏執行異步操做而已。

 

 

3.七、Actions

actions 相似於 mutations,不一樣在於:

  • actions提交的是mutations而不是直接變動狀態

  • actions中能夠包含異步操做, mutations中絕對不容許出現異步

  • actions中的回調函數的第一個參數是context, 是一個與store實例具備相同屬性和方法的對象

  • 此時,咱們在store中添加actions屬性, 其中minusPriceAsync採用setTimeout來模擬異步操做,延遲2s執行 該方法用於異步改變咱們剛纔在mutaions中定義的minusPrice

//main.js
const store = new Vuex.Store({
  state:{
    cars: [
      {name: '奇瑞', price: 20},
      {name: '吉利', price: 40},
      {name: '長安', price: 60},
      {name: '比亞迪', price: 80}
    ]
  },
  getters:{
    saleCars: (state) => {
      let saleCars = state.cars.map( car => {
        return {
          name: car.name,
          price: car.price / 2
        }
      })
      return saleCars;
    }
  },
  mutations:{
    minusPrice (state, payload ) {
      let newPrice = state.cars.forEach( car => {
        car.price -= payload
      })
    }
  },
  actions:{ //添加actions
    minusPriceAsync( context, payload ) {
      setTimeout( () => {
        context.commit( 'minusPrice', payload ); //context提交
      }, 2000)
    }
  }
})
在CarListTwo.vue中添加一個按鈕,爲其添加一個點擊事件, 給點擊事件觸發minusPriceAsync方法
<template>
    <div id="car-list-two">
        <h2>Car List Two</h2>
        <ul>
            <li v-for="car in cars">
                <span class="name">{{ car.name }}</span>
                <span class="price">${{ car.price }}</span>
            </li>
            <button @click="minusPrice">減小价格</button>
            <button @click="minusPriceAsync">異步減小价格</button> //添加按鈕
        </ul>
    </div>
</template>
在CarListTwo.vue中註冊minusPriceAsync方法, 在該方法中dispatchactions中的minusPriceAsync這個回調函數
export default {
    data () {
        return {
            cars: this.$store.state.cars
        }
    },
    methods: {
        minusPrice() {
            this.$store.commit('minusPrice', 2);
        },
        minusPriceAsync() {
            this.$store.dispatch('minusPriceAsync', 5); //分發actions中的minusPriceAsync這個異步函數
        }
    }
}

 

  • 添加按鈕, 能夠發現, Car List Two中的價格延遲2s後減小了5


    actions效果
    actions效果
     
     
     
    多個 state 的操做 , 使用 mutations 會來觸發會比較好維護 , 那麼須要執行多個 mutations 就須要用 action 了:
    
    export default {
        state:{//state
            show:false
        },
        mutations:{
            switch_dialog(state){//這裏的state對應着上面這個state
                state.show = state.show?false:true;
                //你還能夠在這裏執行其餘的操做改變state
            }
        },
        actions:{
            switch_dialog(context){//這裏的context和咱們使用的$store擁有相同的對象和方法
                context.commit('switch_dialog');
                //你還能夠在這裏觸發其餘的mutations方法
            },
        }
    }
    那麼 , 在以前的父組件中 , 咱們須要作修改 , 來觸發 action 裏的 switch_dialog 方法:
    
    <template>
      <div id="app">
        <a href="javascript:;" @click="$store.dispatch('switch_dialog')">點擊</a>
        <t-dialog></t-dialog>
      </div>
    </template>
    
    <script>
    import dialog from './components/dialog.vue'
    export default {
      components:{
        "t-dialog":dialog
      }
    }
    </script>
    使用 $store.dispatch('switch_dialog') 來觸發 action 中的 switch_dialog 方法。
    
    官方推薦 , 將異步操做放在 action 中。

     

     

 

3.八、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 的狀態

 

 

前面爲了方便 , 咱們把 store 對象寫在了 main.js 裏面 , 但實際上爲了便於往後的維護 , 咱們分開寫更好 , 咱們在 src 目錄下 , 新建一個 store 文件夾 , 而後在裏面新建一個 index.js :

import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);

export default new vuex.Store({
    state:{
        show:false
    }
})
那麼相應的 , 在 main.js 裏的代碼應該改爲 :

//vuex
import store from './store'

new Vue({
  el: '#app',
  router,
  store,//使用store
  template: '<App/>',
  components: { App }
})
這樣就把 store 分離出去了 , 那麼還有一個問題是 : 這裏 $store.state.show 不管哪一個組件均可以使用 , 那組件多了以後 , 狀態也多了 , 這麼多狀態都堆在 store 文件夾下的 index.js 很差維護怎麼辦 ?

咱們可使用 vuex 的 modules , 把 store 文件夾下的 index.js 改爲 :

import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);

import dialog_store from '../components/dialog_store.js';//引入某個store對象

export default new vuex.Store({
    modules: {
        dialog: dialog_store
    }
})
這裏咱們引用了一個 dialog_store.js , 在這個 js 文件裏咱們就能夠單獨寫 dialog 組件的狀態了 :

export default {
    state:{
        show:false
    }
}
作出這樣的修改以後 , 咱們將以前咱們使用的 $store.state.show 通通改成 $store.state.dialog.show 便可。

若是還有其餘的組件須要使用 vuex , 就新建一個對應的狀態文件 , 而後將他們加入 store 文件夾下的 index.js 文件中的 modules 中。

modules: {
    dialog: dialog_store,
    other: other,//其餘組件
}

 

 

 

改進的計算器

eg:store.js

import Vue from 'vue';
import Vuex from 'vuex'; //引入 vuex
import store from './store' //註冊store

Vue.use(Vuex); //使用 vuex

export default new Vuex.Store({
    state: {
        // 初始化狀態
        count: 0,
        someLists:[]
    },
    mutations: {
        // 處理狀態
        increment(state, payload) {
            state.count += payload.step || 1;
        }
    },
    actions: {
        // 提交改變後的狀態
        increment(context, param) {
            context.state.count += param.step;
            context.commit('increment', context.state.count)//提交改變後的state.count值
        },
        incrementStep({state, commit, rootState}) {
            if (rootState.count < 100) {
                store.dispatch('increment', {//調用increment()方法
                    step: 10
                })
            }
        }
    },
    getters: {
        //處理列表項
        someLists: state =>param=> {
            return state.someLists.filter(() => param.done)
        }
    }
})
使用時,eg:

main.js:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store' //引入狀態管理 store

Vue.config.productionTip = false

new Vue({
  router,
  store,//註冊store(這能夠把 store 的實例注入全部的子組件)
  render: h => h(App)
}).$mount('#app')
views/home.vue:

<template>
  <div class="home">
    <!--在前端HTML頁面中使用 count-->
    <HelloWorld :msg="count"/>
    <!--表單處理 雙向綁定 count-->
    <input :value="count" @input="incrementStep">
  </div>
</template>

<script>
    import HelloWorld from '@/components/HelloWorld.vue'
    import {mapActions, mapState,mapGetters} from 'vuex' //註冊 action 和 state

    export default {
        name: 'home',
        computed: {
            //在這裏映射 store.state.count,使用方法和 computed 裏的其餘屬性同樣
            ...mapState([
                'count'
            ]),
            count () {
                return store.state.count
            }
        },
        created() {
            this.incrementStep();
        },
        methods: {
            //在這裏引入 action 裏的方法,使用方法和 methods 裏的其餘方法同樣
            ...mapActions([
                'incrementStep'
            ]),
            // 使用對象展開運算符將 getter 混入 computed 對象中
            ...mapGetters([
                'someLists'
                // ...
            ])
        },
        components: {
            HelloWorld
        }
    }
</script>
相關文章
相關標籤/搜索