理解vuex的狀態管理模式架構

理解vuex的狀態管理模式架構html

一: 什麼是vuex?
官方解釋以下:
vuex是一個專爲vue.js應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證以一種可預測的方式發生變化。
使用方式有以下2種:
1. 若是直接在瀏覽器下引用包的話;以下:vue

<script src="https://unpkg.com/vue@2.5.13/dist/vue.js"></script>
<script src="https://unpkg.com/vuex@3.0.1/dist/vuex.js"></script>

接下來就可使用了。git

2. 使用npm安裝
npm install vuex --save
而後在入口文件引入方式以下:github

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

首先咱們先來看看一個簡單的demo,再來對比下vuex到底作了什麼事情。具體爲咱們解決了什麼事情?
咱們先來實現一個簡單的demo,有一個標籤顯示數字,兩個按鈕分別作數字的加一和減一的操做;以下使用純vue的demo以下:vue-router

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>vue-demo</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <p>{{count}}
        <button @click="inc">+</button>
        <button @click="dec">-</button>
      </p>
    </div>
    <script>
      new Vue({
        el:'#app',
        data () {
          return {
            count: 0
          }
        },
        methods: {
          inc () {
            this.count++
          },
          dec () {
            this.count--
          }
        }
      })
    </script>
  </body>
</html>

如上的代碼的含義是:button的標籤內綁定兩個函數,當點擊的時候 分別調用 inc 和 dec的對應的函數,接着會調用 vue中的methods的對應的方法
。而後會對data中的count屬性值發生改變,改變後會把最新值渲染到視圖中。vuex

注意:上面的代碼直接複製運行下就能夠看到效果了。vue-cli

如今咱們來看看使用vuex的方式來實現如上demo。npm

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>vue-demo</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex@3.0.1/dist/vuex.js"></script>
  </head>
  <body>
    <div id="app">
      <p>{{count}}
        <button @click="inc">+</button>
        <button @click="dec">-</button>
      </p>
    </div>
    <script>
      const store = new Vuex.Store({
        state: {
          count: 0
        },
        mutations: {
          inc: state => state.count++,
          dec: state => state.count--
        }
      });
      const app = new Vue({
        el: '#app',
        computed: {
          count() {
            return store.state.count;
          }
        },
        methods: {
          inc() {
            store.commit('inc');
          },
          dec() {
            store.commit('dec');
          }
        }
      });
    </script>
  </body>
</html>

注意:上面的代碼直接複製運行下就能夠看到效果了。數組

對比下上面的代碼:
1. 引用vuex源碼;
2. methods的方法不變,可是方法內的邏輯不在函數內進行,而是讓store對象去處理。
3. count數據再也不是一個data函數返回的對象的屬性了。而是經過store方法內的計算字段返回的。
具體的調用以下:
先view上的元素操做點擊事件 -> 調用methods中的對應方法 -> 經過store.commit(type) 觸發store中的mutations對應的方法來改變state的屬性,值發生改變後,視圖就獲得更新。
回到store對象上來,store對象是 Vuex.Store的實列。在store內分爲state對象和mutations對象,其中state存放的是狀態,
好比count屬性就是它的狀態值,而mutations則是一個會引起狀態改變的全部方法。瀏覽器

理解什麼是狀態管理模式?
狀態管理:簡單的理解就是統一管理和維護各個vue組件的可變化狀態。

咱們明白vue是單向數據流的,那麼它的狀態管理通常包含以下幾部分:
1. state; 驅動應用的數據(通常指data中返回的數據)。
2. view; 通常指模板,以聲明的方式將state的數據映射到視圖。
3. actions: 響應在view上的用戶輸入致使的狀態變化
可是當咱們的應用遇到多個組件共享狀態時候,那麼單向數據流可能不太知足咱們的需求:
好比以下幾個方面:
1. 多個視圖依賴於同一狀態。
傳參的方法對於多層嵌套的組件將會很是繁瑣,而且對於兄弟組件間的狀態傳遞無能爲力。

2. 咱們常常會採用父子組件直接引用或者經過事件來變動和同步狀態的多份拷貝。以上的這些模式很是脆弱,一般會致使沒法維護的代碼。
所以咱們能夠把組件的共享狀態提取出來,做爲全局來管理,所以vuex產生了。

vuex的優勢:
最主要解決了組件之間共享同一狀態的問題。能夠把組件的共享狀態提取出來,做爲全局來管理。

什麼狀況下我應該使用 Vuex?

若是不打算開發大型單頁應用,使用 Vuex 多是繁瑣冗餘的。確實是如此——若是您的應用夠簡單,最好不要使用 Vuex。一個簡單的 global event bus 就足夠您所需了。可是,若是您須要構建是一箇中大型單頁應用,極可能會考慮如何更好地在組件外部管理狀態,Vuex 將會成爲天然而然的選擇。

二: Vuex狀態管理的demo學習
每個Vuex應用的核心就是store(倉庫), store是保存應用中大部分的狀態。
Vuex 和通常的全局對象有如下幾點不一樣:
1. Vuex的狀態存儲是響應性的。
   當vue組件從store中讀取狀態的時候,若store中的狀態發生變化,那麼相對應的組件也就會獲得相應的更新。
2. 咱們不能直接修改store中的狀態。
   改變store中的狀態的惟一途徑是顯示地提交(commit)mutations.

2-1 單一狀態樹
Vuex使用的是單一狀態樹,用一個對象就包含了所有的應用層級狀態。這也意味着每一個應用將僅僅包含一個store的實列。
Vuex的狀態存儲是響應性的,所以從store實列中讀取一個狀態的最簡單的方法是在計算屬性返回某個狀態。
好比demo2的代碼:

<div id="app">
  <p>{{count}}
    <button @click="inc">+</button>
    <button @click="dec">-</button>
  </p>
</div>
<script>
  const store = new Vuex.Store({
    state: {
      count: 0
    },
    mutations: {
      inc: state => state.count++,
      dec: state => state.count--
    }
  });
  const app = new Vue({
    el: '#app',
    computed: {
      count() {
        return store.state.count;
      }
    },
    methods: {
      inc() {
        store.commit('inc');
      },
      dec() {
        store.commit('dec');
      }
    }
  });
</script>

如上代碼,從store中讀取一個狀態能夠從 computed中的count方法內就能夠讀取到了。當 store.state.count變化的時候,都會從新求取計算屬性,而且觸發相關聯的DOM更新。
可是這種模式致使組件依賴的全局狀態單列,在模塊構建系統中,在每一個須要使用state的組件中須要頻繁的導入(由於每一個頁面都須要
導入 new Vuex.Store這樣的,可是一個應用系統僅僅包含一個store實列),而且在測試組件的時候須要模擬狀態。
所以vuex經過store選項,提供了一種機制將狀態從根組件注入到每個子組件中。

使用vuex的示列:

new Vuex.Store({
  state: {
    // code 
  },
  mutations: {
    // code ....
  }
});

state是用來存儲初始化的數據的。若是要讀取數據使用 $store.state.數據變量。
修改數據使用mutations,它保存的須要改變數據的全部方法,改變mutations裏的數據須要使用 $store.commit();

仍是須要 vue-cli 中的項目來講明下:
1. 在scr目錄下新建一個vuex文件夾,在該文件夾下 新建 mystore.js文件,代碼以下:

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

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 1
  },
  // 須要修改stats的數據的話,須要使用$store.commit()方法
  mutations: {
    add(state) {
      return state.count++;
    },
    reduce(state) {
      return state.count--;
    }
  }
});

2. 在src/views文件夾下 新建一個 count.vue,代碼以下:

<template>
  <div>
    <p>{{ msg }}
    <!-- 獲取vuex文件的mystore.js中的 state中的count的值 -->
    {{ $store.state.count }}
    </p>
    <p>
      <button @click="$store.commit('add')"> + </button>
      <button @click="$store.commit('reduce')"> - </button>
    </p>
  </div>
</template>

<script>
  import mystore from '@/vuex/mystore';
  export default {
    data () {
      return {
        msg: 'Hello world'
      }
    },
    /*
     引用mystore.js,store爲數據倉庫
     */
    store: mystore
  }
</script>

三、在 src/router/index.js 路由配置文件中配置 count.vue 的路由, 代碼以下:

import Vue from 'vue';
import Router from 'vue-router';
// import HelloWorld from '@/views/HelloWorld';

Vue.use(Router);

const router = new Router({
  mode: 'history', // 訪問路徑不帶井號  須要使用 history模式,才能使用 scrollBehavior
  routes: [
    {
      path: '/count',
      name: 'count',
      component: resolve => require(['@/views/count'], resolve) // 使用懶加載
    }
  ]
});
export default router;

直接在瀏覽器訪問 http://localhost:8080/count 便可了。

三:學習Vuex state訪問狀態對象
上面咱們的代碼是訪問狀態對象的,是單頁應用程序中的共享值,如今咱們再來看看狀態對象如何賦值給內部對象,也就是
把mystore.js中的值,賦值給模板裏data的值,也就是想直接在template中用 {{xxx}}直接調用數據。

咱們知道vuex的狀態存儲是響應性的,從store實列中讀取狀態最簡單的方式是在計算屬性中返回某個狀態。

3-1 經過computed的計算屬性直接賦值
在src/views文件夾下新建 count2.vue, 代碼以下:

<template>
  <div>
    <p>{{ msg }}
    <!-- 獲取vuex文件的mystore.js中的 state中的count的值 -->
    {{ $store.state.count }}
    </p>
    <p>computed計算賦值結果是:{{ count }}</p>
    <p>
      <button @click="$store.commit('add')"> + </button>
      <button @click="$store.commit('reduce')"> - </button>
    </p>
  </div>
</template>

<script>
  import mystore from '@/vuex/mystore';
  export default {
    data () {
      return {
        msg: 'Hello world'
      }
    },
    computed: {
      count () {
        return this.$store.state.count
      }
    },
    /*
     引用mystore.js,store爲數據倉庫
     */
    store: mystore
  }
</script>

在瀏覽中訪問 http://localhost:8080/count2 能夠看到。

3-2 經過mapState的對象來賦值
當一個組件須要獲取多個狀態的時候,將這些狀態都聲明爲計算屬性會有些重複和冗餘,爲了解決這個問題,可使用
mapState輔助函數來幫助咱們生成計算屬性。

在src/views文件夾下新建 count3.vue, 代碼以下:

<template>
  <div>
    <p>{{ msg }}
    <!-- 獲取vuex文件的mystore.js中的 state中的count的值 -->
    {{ $store.state.count }}
    </p>
    <p>computed計算賦值結果是:{{ count }}</p>
    <p>
      <button @click="$store.commit('add')"> + </button>
      <button @click="$store.commit('reduce')"> - </button>
    </p>
  </div>
</template>

<script>
  import mystore from '@/vuex/mystore';
  // 引入mapState
  import { mapState } from 'vuex';

  export default {
    data () {
      return {
        msg: 'Hello world'
      }
    },
    computed: mapState({
      count: function(state) {
        return state.count;
      }
    }),
    /*
     引用mystore.js,store爲數據倉庫
     */
    store: mystore
  }
</script>

在瀏覽中訪問 http://localhost:8080/count3 能夠看到。

3-3 經過mapState的數組來賦值,
在src/views 下 新建 count4.vue, 代碼以下:

<template>
  <div>
    <p>{{ msg }}
    <!-- 獲取vuex文件的mystore.js中的 state中的count的值 -->
    {{ $store.state.count }}
    </p>
    <p>computed計算賦值結果是:{{ count }}</p>
    <p>
      <button @click="$store.commit('add')"> + </button>
      <button @click="$store.commit('reduce')"> - </button>
    </p>
  </div>
</template>

<script>
  import mystore from '@/vuex/mystore';
  // 引入mapState
  import { mapState } from 'vuex';

  export default {
    data () {
      return {
        msg: 'Hello world'
      }
    },
    /*
     * 數組中的count 必須和 mystore.js定義的常量 mystate 中的 count同名,
     由於這是直接訪問mystate的count
    */
    computed: mapState(['count']),
    /*
     引用mystore.js,store爲數據倉庫
     */
    store: mystore
  }
</script>

在瀏覽中訪問 http://localhost:8080/count4 能夠看到。

四: getters計算過濾操做
    有時候咱們須要從store中的state中派生出一些狀態,好比在使用store中的state以前,咱們會對state中的某些字段進行過濾一下,好比對state中的count字段都進行加10這樣的數據;可是若是有多個組件須要用到這個操做的話,那麼咱們就須要複製這個函數,或者抽取到一個共享函數內,
而後多處導入這個函數,可是這上面兩種方式都不是太好,由於咱們如今有更好的方式來解決它。
Vuex中容許咱們在store中定義 getters,getters的返回值會根據它的依賴被緩存起來,且只有當他的依賴值發生改變了
纔會從新計算。
如今咱們須要對mystore.js文件中的count進行一個計算屬性的操做,在它輸出以前,加上10的操做。
以下代碼有兩個按鈕,一個加5,一個減5,那麼在加5或者減5以前,先加20,而後再進行加5或者5操做。代碼以下:
在文件夾 src/vuex/mystore.js代碼以下:

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

Vue.use(Vuex);

// 增長一個常量對象 state
const mystate = {
  count: 0
};
// mutations 保存全部的方法,該方法能夠改變state數據
const mymutations = {
  // 增長
  add(state, num) {
    const count = state.count += num;
    return count;
  },
  // 減小
  reduce(state, num) {
    const count = state.count -= num;
    return count;
  }
};

// 增長一個getters對象
const mygetters = {
  mycount: function(state) {
    const count = state.count += 20;
    return count;
  }
};

// 封裝代碼,讓外部可見
export default new Vuex.Store({
  state: mystate,  // state的固定寫法 保存數據的狀態值
  mutations: mymutations, // mutations的固定寫法 改變數據的全部方法
  getters: mygetters
});

在src/views/下新建 count5.vue 代碼以下:

<template>
  <div>
    <p>{{ msg }}
    <!-- 獲取vuex文件的mystore.js中的 state中的count的值 -->
    {{ $store.state.count }}
    </p>
    <p>computed計算賦值結果是:{{ mycount }}</p>
    <p>
      <!-- 
        $store.commit('add', 5) 第一個參數是方法名,第二個是參數
      -->
      <button @click="$store.commit('add', 5)"> + </button>
      <button @click="$store.commit('reduce', 5)"> - </button>
    </p>
    <div>
      <p>使用mapMutations修改狀態:</p>
      <p>
        <button @click="add(10)">+</button>
        <button @click="reduce(10)">-</button>
      </p>
    </div>
  </div>
</template>

<script>
  import mystore from '@/vuex/mystore';
  // 引入mapState
  import { mapState, mapMutations, mapGetters } from 'vuex';

  export default {
    data () {
      return {
        msg: 'Hello world'
      }
    },
    computed: {
      // mapState(['count']) 此處的count必須和store.js定義的常量 mystate中的count同名,由於這是直接訪問 mystate的count
      ...mapState(['count']),
      // mapGetters 輔助函數,能夠將store中的getter映射到局部計算屬性mycount
      ...mapGetters(['mycount'])
    },
    methods: mapMutations(['add', 'reduce']),
    /*
     引用mystore.js,store爲數據倉庫
     */
    store: mystore
  }
</script>

在瀏覽器下 訪問 http://localhost:8080/count5 便可。

五:Mutations修改狀態
Mutations是修改vuex中的store的惟一方法。每一個mutations都有一個字符串的事件類型(type)和一個回調函數(handler)。這個回調函數就是
咱們進行更改的地方。它也會接受state做爲第一個參數。

打開上面src/vuex/mystore.js 代碼中的 mutations 能夠看到以下:

// mutations 保存全部的方法,該方法能夠改變state數據
const mymutations = {
  // 增長
  add(state, num) {
    const count = state.count += num;
    return count;
  },
  // 減小
  reduce(state, num) {
    const count = state.count -= num;
    return count;
  }
};

咱們以前調用 mutations的方法是這樣的 $store.commit()便可調用方法來改變state數據,如今咱們想使用 @click="add()"來調用。
1. 咱們在src/views/ 下新建一個count6.vue, 先導入咱們的 mapMutations方法

import { mapState, mapMutations } from 'vuex'; 

2. 使用methods屬性,並加入 mapMutations

methods: mapMutations(['add', 'reduce']); 

3. 在template中使用 @click="", 以下代碼:

<button @click="add(5)">+</button>
<button @click="reduce(5)">-</button>

count6.vue 代碼以下:

<template>
  <div>
    <p>{{ msg }}
    <!-- 獲取vuex文件的mystore.js中的 state中的count的值 -->
    {{ $store.state.count }}
    </p>
    <p>computed計算賦值結果是:{{ count }}</p>
    <p>
      <!-- 
        $store.commit('add', 5) 第一個參數是方法名,第二個是參數
      -->
      <button @click="$store.commit('add', 5)"> + </button>
      <button @click="$store.commit('reduce', 5)"> - </button>
    </p>
    <div>
      <p>使用mapMutations修改狀態:</p>
      <p>
        <button @click="add(10)">+</button>
        <button @click="reduce(10)">-</button>
      </p>
    </div>
  </div>
</template>

<script>
  import mystore from '@/vuex/mystore';
  // 引入mapState
  import { mapState, mapMutations } from 'vuex';

  export default {
    data () {
      return {
        msg: 'Hello world'
      }
    },
    computed: {
      // mapState(['count']) 此處的count必須和store.js定義的常量 mystate中的count同名,由於這是直接訪問 mystate的count
      ...mapState(['count'])
    },
    methods: mapMutations(['add', 'reduce']),
    /*
     引用mystore.js,store爲數據倉庫
     */
    store: mystore
  }
</script>

src/vuex/mystore.js代碼修改以下:

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

Vue.use(Vuex);

// 增長一個常量對象 state
const mystate = {
  count: 0
};
// mutations 保存全部的方法,該方法能夠改變state數據
const mymutations = {
  // 增長
  add(state, num) {
    const count = state.count += num;
    return count;
  },
  // 減小
  reduce(state, num) {
    const count = state.count -= num;
    return count;
  }
};

// 封裝代碼,讓外部可見
export default new Vuex.Store({
  state: mystate,  // state的固定寫法 保存數據的狀態值
  mutations: mymutations // mutations的固定寫法 改變數據的全部方法
});

六: actions異步修改狀態
actions是異步修改state的狀態的。可是Mutations是同步改變狀態的。

6-1 在mystore.js中聲明actions
actions是能夠調用Mutations的方法的。以下代碼:

// 增長一個 actions
const myactions = {
  addAction(context) {
    console.log(context);
    context.commit('add', 5); // 調用mymutations 中的 add方法,並傳參數5
  },
  reduceAction(context) {
    context.commit('reduce', 5); // 調用mymutations中的reduce方法,並傳參數5
  }
};

myactions 裏有兩個方法 addAction 和 reduceAction , 在方法體內,咱們都用 commit 調用了 Mutations
裏面的方法。
其中context,是上下文對象,在這邊能夠理解爲store自己。

在Vuex.store()中封裝

// 封裝代碼,讓外部可見
export default new Vuex.Store({
  state: mystate,  // state的固定寫法 保存數據的狀態值
  mutations: mymutations, // mutations的固定寫法 改變數據的全部方法
  getters: mygetters,
  actions: myactions
});

在conut7.vue中調用,代碼以下:

<p>
  actions的異步操做<br/>
  <button @click="addAction"> + </button>
  <button @click="reduceAction"> - </button>
</p>

import引用以下:

import { mapState, mapMutations, mapGetters, mapActions } from 'vuex';

添加methods方法,代碼以下:

methods: {
...mapMutations(['add', 'reduce']),
...mapActions(['addAction', 'reduceAction'])
}

下面是所有代碼:

mystore.js代碼以下:

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

Vue.use(Vuex);

// 增長一個常量對象 state
const mystate = {
  count: 0
};
// mutations 保存全部的方法,該方法能夠改變state數據
const mymutations = {
  // 增長
  add(state, num) {
    const count = state.count += num;
    return count;
  },
  // 減小
  reduce(state, num) {
    const count = state.count -= num;
    return count;
  }
};

// 增長一個getters對象
const mygetters = {
  mycount: function(state) {
    const count = state.count += 20;
    return count;
  }
};
// 增長一個 actions
const myactions = {
  addAction(context) {
    console.log(context);
    context.commit('add', 5); // 調用mymutations 中的 add方法,並傳參數5
  },
  reduceAction(context) {
    context.commit('reduce', 5); // 調用mymutations中的reduce方法,並傳參數5
  }
};
// 封裝代碼,讓外部可見
export default new Vuex.Store({
  state: mystate,  // state的固定寫法 保存數據的狀態值
  mutations: mymutations, // mutations的固定寫法 改變數據的全部方法
  getters: mygetters,
  actions: myactions
});

count7.vue代碼以下:

<template>
  <div>
    <p>{{ msg }}
    <!-- 獲取vuex文件的mystore.js中的 state中的count的值 -->
    {{ $store.state.count }}
    </p>
    <p>computed計算賦值結果是:{{ mycount }}</p>
    <p>
      <!-- 
        $store.commit('add', 5) 第一個參數是方法名,第二個是參數
      -->
      <button @click="$store.commit('add', 5)"> + </button>
      <button @click="$store.commit('reduce', 5)"> - </button>
    </p>
    <div>
      <p>使用mapMutations修改狀態:</p>
      <p>
        <button @click="add(10)">+</button>
        <button @click="reduce(10)">-</button>
      </p>
      <p>
        actions的異步操做<br/>
        <button @click="addAction"> + </button>
        <button @click="reduceAction"> - </button>
      </p>
    </div>
  </div>
</template>

<script>
  import mystore from '@/vuex/mystore';
  // 引入mapState
  import { mapState, mapMutations, mapGetters, mapActions } from 'vuex';

  export default {
    data () {
      return {
        msg: 'Hello world'
      }
    },
    computed: {
      // mapState(['count']) 此處的count必須和store.js定義的常量 mystate中的count同名,由於這是直接訪問 mystate的count
      ...mapState(['count']),
      ...mapGetters(['mycount'])
    },
    methods: {
      ...mapMutations(['add', 'reduce']),
      ...mapActions(['addAction', 'reduceAction'])
    },
    /*
     引用mystore.js,store爲數據倉庫
     */
    store: mystore
  }
</script>

能夠查看github上的代碼

相關文章
相關標籤/搜索