九. Vuex詳解

1. 理解Vuex

1.1 Vuex功能

官方解釋javascript

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

狀態管理究竟是什麼?vue

狀態管理模式、集中式存儲管理這些名詞聽起來就很是高大上,讓人捉摸不透。其實你能夠簡單的將其當作把須要多個組件共享的變量所有存儲在一個對象裏面。而後將這個對象放在頂層的Vue實例中讓其餘組件可使用。那麼多個組件是否是就能夠共享這個對象中的全部變量屬性了呢?是的java

若是是這樣的話爲何官方還要專門出一個插件Vuex呢?難道咱們不能本身封裝一個對象來管理嗎?固然能夠,只是咱們要先想一想VueJS帶給咱們最大的便利是什麼呢?沒錯,就是響應式。若是你本身封裝實現一個對象能不能保證它裏面全部的屬性作到響應式呢?固然也能夠,只是本身封裝可能稍微麻煩一些。不用懷疑,Vuex就是爲了提供這樣一個在多個組件間共享狀態的插件,用它就能夠了。vuex

管理什麼狀態?npm

可是有什麼狀態是須要咱們在多個組件間共享的呢?若是你作過大型開放必定遇到過多個狀態在多個界面間的共享問題。好比用戶的登陸狀態、用戶名稱、頭像、地理位置信息等。好比商品的收藏、購物車中的物品等。這些狀態信息均可以放在統一的地方對它進行保存和管理,並且它們仍是響應式的。promise

OK,從理論上理解了狀態管理以後,讓咱們從實際的代碼再來看看狀態管理。網絡

1.2 單界面的狀態管理

理解app

咱們知道,要在單個組件中進行狀態管理是一件很是簡單的事情。異步

什麼意思呢?咱們來看下面的圖片。這圖片中的三種東西,怎麼理解呢?

  • State:不用多說,就是咱們的狀態。(你姑且能夠當作就是data中的屬性)

  • View:視圖層,能夠針對State的變化,顯示不一樣的信息。(這個好理解)

  • Actions:這裏的Actions主要是用戶的各類操做:點擊、輸入等會致使狀態的改變。

image-20201205210034289

實現

在下面案例中,咱們有木有狀態須要管理呢?沒錯就是counter。counter須要某種方式被記錄下來,也就是咱們的State。counter目前的值須要被顯示在界面中,也就是咱們的View部分。
界面發生某些操做時(咱們這裏是用戶的點擊,也能夠是用戶的input),須要去更新狀態,也就是咱們的Actions,這不就是上面的流程圖了嗎?

<template>
  <div id="app">
      <div>當前計數:{{ counter }}</div>
      <button @click="counter+=1">+1</button>
      <button @click="counter-=1">-1</button>
  </div>
</template>

<script>
export default {
  name: "app",
  data() {
    return {
      counter: 0
    }
  }
}
</script>

<style scoped></style>
1.3 多界面狀態管理

Vue已經幫咱們作好了單個界面的狀態管理,可是若是是多個界面呢?

  • 多個視圖都依賴同一個狀態(一個狀態改了,多個界面須要進行更新)
  • 不一樣界面的Actions都想修改同一個狀態(Home.vue須要修改,Profile.vue也須要修改這個狀態)

也就是說對於某些狀態(狀態1/狀態2/狀態3)來講只屬於咱們某一個試圖,可是也有一些狀態(狀態a/狀態b/狀態c)屬於多個試圖共同想要維護的。

  • 狀態1/狀態2/狀態3你放在本身的房間中,你本身管理本身用沒問題。
  • 可是狀態a/狀態b/狀態c咱們但願交給一個大管家來統一幫助咱們管理!
  • 沒錯,Vuex就是爲咱們提供這個大管家的工具。

全局單例模式(大管家)

  • 咱們如今要作的就是將共享的狀態抽取出來,交給咱們的大管家統一進行管理。
  • 以後每一個試圖按照大管家規定好的規定,進行訪問和修改等操做。這就是Vuex背後的基本思想。
1.4 Vuex狀態管理圖例
image-20201205210951073

2. Vuex基本使用

2.1 安裝Vuex
npm install vuex --save
2.2 簡單的案例

使用Vuex實現一下以前的計數器案例

store/index.js

import VueX from 'vuex'
import Vue from 'vue'

Vue.use(VueX)

const store = new VueX.Store({
    state: {
        count: 0
    },
    mutations: {
        increment(state) {
            state.count++;
        },
        decrement(state) {
            state.count--;
        }
    }
})

export default store

將store掛載到Vue實例中

咱們讓全部的Vue組件均可以使用這個store對象,來到main.js文件導入store對象,而且放在new Vue中。這樣在其餘Vue組件中,咱們就能夠經過this.$store的方式獲取到這個store對象了。

//...
import store from '@/store'
//...
new Vue({
  store,
  render: h => h(App),
}).$mount('#app')

組件中使用Vuex的count

<template>
  <div id="app">
      <div>當前計數:{{ count }}</div>
      <button @click="increment">+1</button>
      <button @click="decrement">-1</button>
  </div>
</template>

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

<style scoped></style>

好的,上面就是使用Vuex最簡單的方式了。

咱們來對使用步驟,作一個簡單的總結:

  • 提取出一個公共的store對象,用於保存在多個組件中共享的狀態。
  • 將store對象放置在new Vue對象中,這樣能夠保證在全部的組件中均可以使用到。
  • 在其餘組件中使用store對象中保存的狀態便可
    • 經過this.$store.state屬性的方式來訪問狀態
    • 經過this.$store.commit('mutation中的方法')來修改狀態

注意:
① 咱們經過提交mutation的方式,而非直接改變store.state.count
② 這是由於Vuex能夠更明確的追蹤狀態的變化,因此不要直接改變store.state.count的值。

2.3 Vux的幾個核心概念

咱們來對這幾個概念一一理解

  • State
  • Getters
  • Mutation
  • Action
  • Module

3. State

State單一狀態樹

Vuex提出使用單一狀態樹, 什麼是單一狀態樹呢?英文名稱是Single Source of Truth,也能夠翻譯成單一數據源。

可是它是什麼呢?咱們來看一個生活中的例子。咱們知道在國內咱們有不少的信息須要被記錄,好比上學時的我的檔案,工做後的社保記錄,公積金記錄,結婚後的婚姻信息,以及其餘相關的戶口、醫療、文憑、房產記錄等等(還有不少信息)。這些信息被分散在不少地方進行管理,有一天你須要辦某個業務時(好比入戶某個城市),你會發現你須要到各個對應的工做地點去打印、蓋章各類資料信息,最後到一個地方提交證實你的信息無誤。這種保存信息的方案不只僅低效並且不方便管理,以及往後的維護也是一個龐大的工做(須要大量的各個部門的人力來維護,固然國家目前已經在完善咱們的這個系統了)。

這個和咱們在應用開發中比較相似:若是你的狀態信息是保存到多個Store對象中的,那麼以後的管理和維護等等都會變得特別困難。因此Vuex也使用了單一狀態樹來管理應用層級的所有狀態。單一狀態樹可以讓咱們最直接的方式找到某個狀態的片斷,並且在以後的維護和調試過程當中也能夠很是方便的管理和維護。

4. Getters

相似於組件中的計算屬性computed

有時候咱們須要從store中獲取一些state變化後的狀態,好比下面的Store中獲取學生年齡大於20的學生個數。

state: {
  students: [
       {id: 110, name: 'polaris',age: 18},
       {id: 111, name: 'rose',age: 22},
       {id: 112, name: 'jack',age: 34},
       {id: 113, name: 'tom',age: 11},
   ]
},

咱們能夠在Store中定義getters

getters: {
   greateAgesCount: state => {
       return state.students.filter(s => s.age >= 20).length;
   }
}
//若是咱們已經有了一個獲取全部年齡大於20歲學生列表的getters, 那麼代碼也能夠這樣來寫
getters: {
   greateAgesStudents: state => {
       return state.students.filter(s => s.age >= 20);
   },
   greateAgesCount: (state, getters) => {
       return getters.greateAgesStudents.length;
   }   
}
//組件中獲取getters計算後的值
computed: {
  greateAgesCount() {
     return this.$store.getters.greateAgesCount
  }
},

getters默認是不能傳遞參數的,若是但願傳遞參數,那麼只能讓getters自己返回另外一個函數。好比上面的案例中咱們但願根據ID獲取用戶的信息。

getters: {
   studentById: state => {
       return id => {
           return state.students.find(s => s.id === id)
       }
   }  
}
computed: {
  studentById() {
    return this.$store.getters.studentById(112)
  }
}

5. Mutation

5.1 狀態更新

Vuex的store狀態的更新惟一方式:提交Mutation

Mutation主要包括兩部分:

  • 字符串的 事件類型type

  • 一個 回調函數 handler,該回調函數的第一個參數就是state。

mutation的定義方式:

//以下:increment就是事件類型,(state) {state.count++;}是回調函數
mutations: {
    increment(state) {
        state.count++;
    },
    decrement(state) {
        state.count--;
    }
}

在某個組件中經過mutation更新state值

increment: function() {
    this.$store.commit('increment');
}
5.2 傳遞參數

在經過mutation更新數據的時候,有可能咱們但願攜帶一些 額外的參數,參數被稱爲是mutation的載荷(Payload)

Mutation中的代碼:

mutations: {
    increment(state,n) {
        state.count += n;
    },
    decrement(state,n) {
        state.count -= n;
    }
}

在某個組件中經過mutation更新state值

methods: {
    increment() {
      this.$store.commit('increment',2)
    },
    decrement() {
      this.$store.commit('decrement',2)
    }
}

可是若是參數不是一個呢?好比咱們有不少參數須要傳遞,這個時候咱們一般會以對象的形式傳遞,也就是payload是一個對象。

changeCount(state,payload) {
    state.count = payload.count
}
changeCount() {
   this.$store.commit('changeCount',{count: 5})
}
5.3 提交風格

上面的經過commit進行提交是一種普通的方式

Vue還提供了另一種風格, 它是一個包含type屬性的對象

changeCount() {
   this.$store.commit({
      type: 'changeCount',
      count: 100
   })
}

Mutation中的處理方式是將整個commit的對象做爲payload使用, 因此代碼沒有改變依然以下:

changeCount(state,payload) {
     state.count = payload.count
}
5.4 響應規則

Vuex的store中的state是響應式的,當state中的數據發生改變時Vue組件會自動更新。

這就要求咱們必須遵照一些Vuex對應的規則

  • 提早在store中初始化好所需的屬性

  • 當給state中的對象添加新屬性時,,使用下面的方式

    • 方式一:使用Vue.set(obj, 'newProp', 123)
  • 方式二:用新對象給舊對象從新賦值

state中的對象添加新屬性的案例

咱們來看一個例子:當咱們點擊更新信息時界面並無發生對應改變,如何才能讓它改變呢?

import VueX from 'vuex'
import Vue from 'vue'

Vue.use(VueX)

const store = new VueX.Store({
    state: {
        info: { name: 'polaris', age: 18 }
    },
    mutations: {
        updateInfo(state,payload) {
            state.info['height'] = payload.height
        }
    }
})

export default store
<template>
  <div id="app">
      <p>個人我的信息:{{info}}</p>
      <button @click="updateInfo">更新信息</button>
  </div>
</template>

<script>
export default {
  name: "app",
  computed: {
    info() {
      return this.$store.state.info
    }
  },
  methods: {
    updateInfo() {
      this.$store.commit('updateInfo',{height: 1.88})
    }
  }
}
</script>

<style scoped></style>

下面代碼的方式一和方式二,均可以讓state中的屬性是響應式的

mutations: {
    // updateInfo(state,payload) {
    //     state.info['height'] = payload.height
    // }
    updateInfo(state, payload) {
        //方式一
//      Vue.set(state.info,'height',payload.height)
        //方式二
         state.info = {...state.info, 'height': payload.height}
    }
}

咱們也能夠響應式的刪除某個對象的屬性如:Vue.delete(state.info,'height')

5.5 常量類型

概念

咱們來考慮一個問題,在mutation中咱們定義了不少事件類型(也就是其中的方法名稱)。當咱們的項目增大時Vuex管理的狀態愈來愈多,須要更新狀態的狀況愈來愈多,那麼意味着Mutation中的方法愈來愈多。方法過多使用者須要花費大量的經歷去記住這些方法甚至是多個文件間來回切換查看方法名稱,甚至若是不是複製可能還會出現寫錯的狀況。

如何避免上述的問題呢?在各類Flux實現中,一種很常見的方案就是 使用常量替代Mutation事件的類型 。咱們能夠將這些常量放在一個單獨的文件中方便管理以及讓整個app全部的事件類型一目瞭然。

具體怎麼作呢?咱們能夠建立一個文件 mutation-types.js, 而且在其中定義咱們的常量。
定義常量時咱們可使用ES2015中的風格,使用一個常量來做爲函數的名稱。

代碼

image-20201205225307575
5.6 同步函數

一般狀況下Vuex要求咱們Mutation中的方法必須是同步方法。

主要的緣由是當咱們使用devtools時,利用devtools幫助咱們捕捉mutation的快照,可是若是是異步操做那麼devtools將不能很好的追蹤這個操做何時會被完成。即若是Vuex中的代碼咱們使用了異步函數,你會發現state中的info數據一直沒有被改變由於它沒法追蹤到。因此一般狀況下不要在mutation中進行異步的操做。

mutations: {
   updateInfo(state) {
       setTimeout(() => {
           state.info.name = "GG";
       },1000)
   }
},
image-20201206194141790

6. Action

6.1 基本定義

前面咱們強調不要再Mutation中進行異步操做,可是某些狀況咱們確實但願在Vuex中進行一些異步操做,好比網絡請求必然是異步的,這個時候怎麼處理呢?

Action相似於Mutation,可是是用來代替Mutation進行異步操做的。

Action的基本使用代碼以下

mutations: {
   updateInfo(state) {
       // setTimeout(() => {
       //     state.info.name = "GG";
       // },1000)
       state.info.name = "GG";
   }
},
actions: {
    actUpdateInfo(context) {
       setTimeout(() => {
           context.commit('updateInfo');
       },1000)
    }
}

context是什麼?context是和store對象具備相同方法和屬性的對象,也就是說咱們能夠經過context去進行commit相關的操做,也能夠獲取context.state等。可是注意這裏它們並非同一個對象,爲何呢? 咱們後面學習Modules的時候再具體說。

這樣的代碼是否畫蛇添足呢?咱們定義了actions,而後又在actions中去進行commit,這不是脫褲放屁嗎?事實上並非這樣,若是在Vuex中有異步操做那麼咱們就能夠在actions中完成了。

6.2 分發

在Vue組件中, 若是咱們調用action中的方法,那麼就須要使用dispatch,一樣的dispatch也是支持傳遞payload

methods: {
  updateInfo() {
     // this.$store.commit('updateInfo');
     this.$store.dispatch('actUpdateInfo');
  }
}
6.3 對象的解構寫法
const obj = {
	name: 'why',
	age: 18,
	height: 1.88
};
//順序可變
const {age, name, height} = obj;
console.log(name);

在Actions中使用對象的解構寫法

getters 和 mutations 固然也可使用對象的解構寫法

actions: {
    actUpdateInfo({commit}) {
       setTimeout(() => {
           commit('updateInfo');
       },1000)
    }
}
6.4 Action返回的Promise

不清楚promise的用法請回看第八章

引入

當咱們的store中異步操做執行結束後,是否可以提醒一下調用者已經成功執行了呢?

咱們能夠這樣實現:

actions: {
   actUpdateInfo(context,success) {
       setTimeout(() => {
           context.commit('updateInfo');
           success();
       },1000)
   }
},
methods: {
  updateInfo() {
    this.$store.dispatch('actUpdateInfo',() => {
      console.log('執行成功!'); 
    });
  }
}

可是這樣就不能傳入其餘參數了,那咱們再換種寫法!

actions: {
   actUpdateInfo(context,payload) {
       setTimeout(() => {
           context.commit('updateInfo',payload.message);
           console.log(payload.message);
           payload.success();
       },1000)
   }
},
methods: {
  updateInfo() {
    this.$store.dispatch('actUpdateInfo', {
        message: '我是攜帶的參數',
        success: () => {
        	console.log('執行成功!');
      	}
    });
  }
}

雖然能夠實現,可是回調的信息和攜帶的參數寫到一塊兒去了,這種作法是不夠優雅的,下面咱們經過Promise實現!

使用Promise

前面咱們學習ES6語法的時候說過Promise常常用於異步操做。在Action中咱們能夠將異步操做放在一個Promise中,而且在成功或者失敗後調用對應的resolve或reject。

actions: {
  actUpdateInfo(context, payload) {
    return new Promise((resolve, reject) => {
       setTimeout(() => {
           context.commit('updateInfo');
           console.log(payload);
           resolve('執行成功!');
       }, 1000)
    })
  }
},
methods: {
  updateInfo() {
    this.$store.dispatch("actUpdateInfo", '我是攜帶的信息').then(res => {
      console.log(res);
    });
  },
},

7. Module

7.1 理解

Module是模塊的意思,爲何在Vuex中咱們要使用模塊呢?

Vue使用單一狀態樹,那麼也意味着不少狀態都會交給Vuex來管理。當應用變得很是複雜時store對象就有可能變得至關臃腫。爲了解決這個問題Vuex容許咱們將store分割成模塊(Module),而每一個模塊擁有本身的state,mutations,actions,getters等。

咱們按照什麼樣的方式來組織模塊呢?看下面代碼

//注意:模塊中mutation和getters接收的第一個參數state,context是局部狀態對象。
const moduleA = {
    state: {
        name: 'polaris'
    },
    mutations: {
        updateName(state) {
            state.name = 'GG';
		}
    },
    actions: {
        actUpdateName(context) {
            setTimeout(() => {
                context.commit('updateName')
            },1000)
        }
    },
    getters: {
        fullName(state) {
            return state.name + "hahaha";
        }
    }
}
              
const moduleB = {
    state: {
       name: 'rose'       
    },
    mutations: {},
    actions: {},
    getters: {}
}
              
const store = new Vuex.store({
     modules: {
        a: moduleA,
        b: moduleB
     }         
})
//state,調用時必須加上模塊名,不一樣模塊間能夠有相同的值
this.$store.state.a.name  //獲取moduleA的狀態中的值
this.$store.state.b.name  //獲取moduleB的狀態中的值
//mutations,不一樣模塊間能夠有相同的值可是不要這麼寫,由於外部會同時調用不一樣模塊的mutations方法
updateName() {
    this.$store.commit('updateName'); //依次去模塊中找
}
//getters,不一樣模塊間不能有相同的值,會報錯
this.$store.getters.fullName //依次去模塊中找
//actions,不一樣模塊有相同的mutations方法時,會同時調用不一樣模塊的mutations方法
actUpdateName() {
   this.$store.dispatch('actUpdateName')
}

//=> 總結:除了state,各個模塊中的其餘內容不要重名!
7.2 store推薦的項目結構
image-20201206220857081
相關文章
相關標籤/搜索