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


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

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

<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>

接下來就可以使用了。

2. 使用npm安裝
npm install vuex --save
然后在入口文件引入方式如下:

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

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

<!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的方式來實現如上demo。

<!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>

3、在 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上的代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM