Vuex 快速入門上手教程

一. 什么是Vuex?

vuex-explained-visually.png

Vuex是一個專門為Vue.js應用程序開發的狀態管理模式, 它采用集中式存儲管理所有組件的公共狀態, 并以相應的規則保證狀態以一種可預測的方式發生變化.

Vuex核心

上圖中綠色虛線包裹起來的部分就是Vuex的核心, state中保存的就是公共狀態, 改變state的唯一方式就是通過mutations進行更改. action 的作用跟mutation的作用是一致的,它提交mutation,從而改變state,是改變state的一個增強版.Action 可以包含任意異步操作??赡苣悻F在看這張圖有點不明白, 等經過本文的解釋和案例演示, 再回來看這張圖, 相信你會有更好的理解.

二. 為什么要使用Vuex?

試想這樣的場景, 比如一個Vue的根實例下面有一個父組件名為App.vue, 它下面有兩個子組件A.vueB.vue, App.vue想要與A.vue或者B.vue通訊可以通過props傳值的方式。但是如果A或者B組件有子組件,并且要實現父組件與孫子組件的通信。或者與嵌套的更深的組件之間的通信就很麻煩。如下圖:

716127-20180119171257271-1822439203.png

另外如果A.vueB.vue之間的通訊他們需要共有的父組件通過自定義事件進行實現,或者通過中央事件總線
實現。也很麻煩。如下圖:

716127-20180119171307849-26505562.png

Vuex就是為了解決這一問題出現的

三.如何引入Vuex?

  1. 安裝vuex:
npm install vuex --save
  1. main.js添加:
import Vuex from 'vuex'

Vue.use( Vuex );

const store = new Vuex.Store({
    //待添加
})

new Vue({
    el: '#app',
    store,
    render: h => h(App)
})

四. Vuex的核心概念?

為了方便理解概念,我做了一個vuex的demo。git地址:(vuex demo)[https://github.com/zhou111222/vue-.git]
vuex的文件在store文件夾下。store中的modules文件下包含兩個文件。文件名和頁面的名稱是一致的,代表每個頁面的vuex。index文件負責組合modules文件夾中各個頁面的vuex。并導出。mutations-type文件里面定義一些操作mutation的字符串常量變量名,這里是定義一些動作名,所以名字一般是set 或者是update,然后再結合state.js中定義的變量對象,export多個常量。

image.png

核心概念1: State

state就是Vuex中的公共的狀態, 我是將state看作是所有組件的data, 用于保存所有組件的公共數據.

  • 此時我們就可以把項目中的公共狀態抽離出來, 放到mudules對應頁面的state中,代碼如下:
//detail.js
import * as types from '../mutation-type'

const state = { // 要設置的全局訪問的state對象
  gold: 'BALLY/巴利',
  num: 1,
  groupData: {}
}

export default {
  namespaced: true, // 用于在全局引用此文件里的方法時標識這一個的文件名
  state
}

// home.js
import * as types from '../mutation-type'

const state = { // 要設置的全局訪問的state對象
  carNumber: 0
}
export default {
  namespaced: true, // 用于在全局引用此文件里的方法時標識這一個的文件名
  state
}

  • 此刻store文件夾下的index代碼。兩個頁面的store進行合并:
//ProductListOne.vue
import Vue from 'vue'
import Vuex from 'vuex'
import home from './modules/home'
import detail from './modules/details'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    home,
    detail
  }
})

在main.js中講store注入到vue中。代碼如下:

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import VueBus from './assets/js/bus'
// 中央事件總線封裝
Vue.use(VueBus)
Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store, // 使用store
  components: { App },
  template: '<App/>'
})

home.vue和detai.vue中使用state的方式如下:

{{this.$store.state.屬性名}}

核心概念2: Getters

可以將getter理解為store的computed屬性, getters的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變才會被重新計算。

  • 此時,我們可以在store/modules/home.js中添加一個getters對象, 其中的carNum將依賴carNumber的值。
import * as types from '../mutation-type'

const state = { // 要設置的全局訪問的state對象
  carNumber: 0
}

const getters = {
  carNum (state) {
    return state.carNumber
  }
}
export default {
  namespaced: true, // 用于在全局引用此文件里的方法時標識這一個的文件名
  state,
  getters
}

  • 另外,我們可以在store/modules/detail.js中添加一個getters對象, 其中的getProductdNum將依賴state.num的值。getGroupData 將依賴state.groupData的值。
//home.js
import * as types from '../mutation-type'

const state = { // 要設置的全局訪問的state對象
  gold: 'BALLY/巴利',
  num: 1,
  groupData: {}
}

const getters = {
  getProductdNum (state) {
    return state.num
  },
  getGroupData (state) {
    return state.groupData
  }
}

export default {
  namespaced: true, // 用于在全局引用此文件里的方法時標識這一個的文件名
  state,
  getters
}

核心概念3: Mutations

我將mutaions理解為store中的methods, mutations對象中保存著更改數據的回調函數,函數第一個參數是state, 第二參數是payload, 也就是自定義的參數.該函數名的名稱官方規定叫type,在模塊化的思想中。type的名稱被單獨抽離出來放到了mutation-type文件。mutation-types.js:在這里面定義一些操作mutation的字符串常量變量名,這里是定義一些動作名,所以名字一般是set 或者是update,然后再結合state.js中定義的變量對象,export多個常量 :


//存儲mutation的一些相關的名字,也就是一些字符串常量
//即使用常量替代mutation類型
//mutations里面定義一些方法,這里也就是定義mutations里面方法的名字
// 一些動作,所以用set,update

/* home頁面 */
export const INIT_CAR_NUM = 'INIT_CAR_NUM'
export const ADD_CAR_NUM = 'ADD_CAR_NUM'
export const REDUCE_CAR_NUM = 'REDUCE_CAR_NUM'

/* detail頁面 */
export const ADD_NUMBER = 'ADD_NUMBER'
export const GET_GROUP_DATA = 'GET_GROUP_DATA'
  • 下面,我們分別在store/modules/detail.js中添加mutations屬性, mutations中的方法名(type)和在mutation-type.js中定義的名稱一致。代碼如下:
//home.js
import * as types from '../mutation-type'

const state = { // 要設置的全局訪問的state對象
  carNumber: 0
}

const getters = {
  carNum (state) {
    return state.carNumber
  }
}

const mutations = {
  [types.INIT_CAR_NUM] (state, num) {
    state.carNumber = num
  },
  [types.ADD_CAR_NUM] (state) {
    state.carNumber = state.carNumber + 1
  },
  [types.REDUCE_CAR_NUM] (state) {
    if (state.carNumber > 0) {
      state.carNumber = state.carNumber - 1
    }
  }
}


export default {
  namespaced: true, // 用于在全局引用此文件里的方法時標識這一個的文件名
  state,
  getters,
  mutations
}

//detail.js
import * as types from '../mutation-type'

const state = { // 要設置的全局訪問的state對象
  gold: 'BALLY/巴利',
  num: 1,
  groupData: {}
}

const getters = {
  getProductdNum (state) {
    return state.num
  },
  getGroupData (state) {
    return state.groupData
  }
}

const mutations = {
  [types.ADD_NUMBER] (state, sum) {
    state.num = state.num + sum
  },
  [types.GET_GROUP_DATA] (state, data) {
    state.groupData = Object.assign({}, state.groupData, data)
  }
}

export default {
  namespaced: true, // 用于在全局引用此文件里的方法時標識這一個的文件名
  state,
  getters,
  mutations
}
  • home.vue中添加2個按鈕,為其綁定事件。一個按鈕負責carNumber的加1,另一個減1。在methods中添加相應的方法。
//home.vue
<template>
  <div class="home">
    <div class="hello">
      <h1>{{ msg }}</h1>
      <ul>
        <li class="groupid">
          list頁(vuex異步){{groupId? groupId : ''}}
        </li>
      </ul>
      <div class="car">購物車(vuex同步){{carNumber}}</div>
      <div class="control-car">
        <div class="reduce-car"
             @click="reduceCar()">-</div>
        <div class="add-car"
             @click="addCar()">+</div>
      </div>
      <button @click="goPage()">go list page</button>
      <con3></con3>
      <con4></con4>
    </div>
  </div>
</template>

<script>
import con3 from '@/components/con3'
import con4 from '@/components/con4'
import { mapState, mapMutations } from 'vuex'
import * as types from '@/store/mutation-type'

export default {
  name: 'Home',
  data () {
    return {
      msg: 'hello world'
    }
  },
  components: {
    con3,
    con4
  },
  beforeCreate: function () { },
  created: function () { },
  beforeMount: function () { },
  mounted: function () { },
  beforeUpdate: function () { },
  updated: function () { },
  beforeDestroy: function () { },
  destroyed: function () { },
  computed: {
    ...mapState({
      groupId: state => state.detail.groupData.groupId
    }),
    ...mapState({
      carNumber: state => state.home.carNumber
    })
  },
  methods: {
    goPage: function () {
      // this.$router.push({ path: '/detail' })
      this.$router.push({
        name: 'Detail',
        params: {
          page: '1', sku: '8989'
        }
      })
    },
    ...mapMutations({
      [types.ADD_CAR_NUM]: `home/${types.ADD_CAR_NUM}`,
      [types.REDUCE_CAR_NUM]: `home/${types.REDUCE_CAR_NUM}`
    }),
    addCar: function () {
      this[types.ADD_CAR_NUM]()
    },
    reduceCar: function () {
      this[types.REDUCE_CAR_NUM]()
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss">
@import '../assets/style/reset.scss';
html,
body,
#app {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #fff;
}
#app {
  position: relative;
}
* {
  padding: 0;
  margin: 0;
}
.a {
  font-size: 40px;
}
.b {
  font-family: 'AlternateGothicEF-NoTwo';
  font-size: 40px;
}
h1,
h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
.groupid {
  width: 100%;
  height: 50px;
  background-color: #df8613;
  line-height: 50px;
  text-align: center;
  font-size: 26px;
  margin: 20px 0;
}
.car {
  width: 100%;
  height: 50px;
  background-color: #f5380a;
  line-height: 50px;
  text-align: center;
  font-size: 26px;
  margin: 20px 0;
}
.control-car {
  width: 100%;
  height: 70px;
  display: flex;
  justify-content: center;
  div {
    width: 60px;
    height: 60px;
    background-color: #42b983;
    float: left;
    margin-right: 20px;
    text-align: center;
    line-height: 60px;
    font-size: 26px;
    color: #fff;
    cursor: pointer;
  }
}
button {
  width: 100%;
  height: 50px;
  line-height: 50px;
  text-align: center;
  font-size: 26px;
  margin: 20px 0;
}
</style>

點擊按鈕,值會進行相應的增減。

  • 文件中有mapStates,mapMutations等函數,稍后講。

核心概念4: Actions

actions 類似于 mutations,不同在于:

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

  • actions中可以包含異步操作, mutations中絕對不允許出現異步

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

  • 此時,我們在detail.js中添加actions屬性, 其中groupData采用接口異步操作獲取數據,改變state中的groupData。

//details.js
import * as types from '../mutation-type'

const state = { // 要設置的全局訪問的state對象
  gold: 'BALLY/巴利',
  num: 1,
  groupData: {}
}

const getters = {
  getProductdNum (state) {
    return state.num
  },
  getGroupData (state) {
    return state.groupData
  }
}

const mutations = {
  [types.ADD_NUMBER] (state, sum) {
    state.num = state.num + sum
  },
  [types.GET_GROUP_DATA] (state, data) {
    state.groupData = Object.assign({}, state.groupData, data)
  }
}

const actions = {
  getNewNum (context, num) {
    context.commit(types.ADD_NUMBER, num)
  },
  groupData (context, data) {
    context.commit(types.GET_GROUP_DATA, data)
  }
}

export default {
  namespaced: true, // 用于在全局引用此文件里的方法時標識這一個的文件名
  state,
  getters,
  mutations,
  actions
}

  • details.vue中的con1組件中添加一個按鈕,為其添加一個點擊事件, 給點擊事件觸發groupData方法
<template>
  <div class="con1">
    {{name}}
    <div class="groupid"
         @click="onLoad">
      list頁獲取異步接口數據{{user.groupId}}
    </div>
    <con2 v-bind="$attrs"
          v-on="$listeners"></con2>
    <div class="list1"></div>
  </div>
</template>

<script>
import con2 from './con2'
import { getGroupId } from '@/api/service'
import { mapActions } from 'vuex'

export default {
  data () {
    return {
      msg: 'con1',
      user: {}
    }
  },
  inheritAttrs: false,
  props: ['name'],
  components: {
    con2
  },
  mounted () { },
  methods: {
    ...mapActions({
      groupData: 'detail/groupData'
    }),
    onLoad () {
      getGroupId({
        appSource: 'secoo',
        sku: 52606943,
        source: 1,
        upk: '18210001657400782F74E3AB7FD929AED59F0415135D5665089292C437542835ED117B6D94EBB9CC36B4A5D49D3504B36E3795727824DF877ED0633F6DCDF88DA0D3355E9ACD2A0B2F892115DF6D2755F3297F5E93659937491D26B687A507A85A74C272F7C69FB28536BD6B31EDC482F8B979377EA3A749C8BC'
      }).then(res => {
        this.user = Object.assign({}, this.user, res)
        this.groupData(res)
      })
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss" scoped>
@import '../assets/style/reset.scss';
html,
body,
#app {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #fff;
}
#app {
  position: relative;
}
* {
  padding: 0;
  margin: 0;
}
.a {
  font-size: 40px;
}
.b {
  font-family: 'AlternateGothicEF-NoTwo';
  font-size: 40px;
}
h1,
h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
button {
  width: 220px;
  height: 50px;
  line-height: 50px;
  text-align: center;
  font-size: 26px;
}
.groupid {
  width: 100%;
  height: 50px;
  background-color: #df8613;
  line-height: 50px;
  text-align: center;
  font-size: 26px;
  margin: 20px 0;
}
.list1 {
  width: 746px;
  height: 250px;
  background-color: red;
  border: 2px solid #000;
}
</style>


  • 點擊按鈕即可發現加載異步數據

核心概念5: Modules

由于使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常復雜時,store 對象就有可能變得相當臃腫。為了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行同樣方式的分割

const home = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const detail = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    home,
    detail
  }
})

store.state.home// -> home頁面 的狀態
store.state.detail // -> detail頁面 的狀態

改變state的時候使用mutation或者action中的方法也需要做一些修改:

//原來的
this.$store.commit('getGroupData');
this.$store.dispatch('etNewNum',5);
//修改后的
this.$store.commit('detail/getGroupData');
this.$store.dispatch('detail/getNewNum',5);

講完這些基本概念之后再將一下這些屬性的輔助函數:mapState, mapGetters, mapActions, mapMutations。

map映射函數

map映射函數 映射結果
mapState 將state映射到computed
mapActions 將actions映射到methods
mapGetters 將getters映射到computed

個人感覺,這些函數其實就是對state,gettters,mutations,actions的重命名而已??梢宰屇阍趯懘a的時候少敲幾個字母。在沒有這些函數的時候。頁面或者組件里使用state和getter需要this.store.state.num還有this.store.getter.num.如果頁面有很多個地方使用了state和getter會很麻煩。使用這些輔助函數可以解決這些問題。

使用時首先引入:

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

他是對state的對象進行二次計算,他可以簡化我們代碼,這樣我們獲取a,不用寫this.$store.state.a一大串了,尤其對于我們組件中用到次數挺多的情況下,我們完全可以用mapState來代替。
-需要在computed中使用mapState

computed: {
  ...mapState(['a', 'b']),
}

就可以代替這段代碼:

computed: {
  a() {
    return this.$store.state.a
  },
  b() {
    return this.$store.state.b
  },
}

注意,如果a和b不是同一個文件下的屬性,j假如a屬于detail.b屬于home.可以這么寫:

computed: {
  ...mapState({
    a: state => state.detail.a
  }),
...mapState({
    a: state => state.home.b
  }),
}

使用方法:

this.a

官方的使用方法如下:


img_ccc7f93b277d85a17130b533a4090e8e.png
mapGetters

我們可以理解成他是和mapState一樣,都是對state里面的參數進行計算,并返回相應的值,所以我們平??吹降膍apState.mapGetters都是在computed屬性里面,但是和mapState有點不同的是,mapState是僅對state里面的參數進行計算并返回,而mapGetters是對state參數派生出的屬性進行計算緩存

在computed中添加count的映射:

computed: {
  ...mapGetters(['count'])
}

等價于:

computed: {
  count() {
    return this.$store.getters.count
  }
}

使用方法:

this.count
mapMutations

mapMutations是寫在methods里面,因為他觸發的是方法
在methods中添加映射

methods: {
 ...mapMutations({
      [types.ADD_CAR_NUM]: `home/${types.ADD_CAR_NUM}`,
      [types.REDUCE_CAR_NUM]: `home/${types.REDUCE_CAR_NUM}`
    }),
},

等價于:

methods: {
    this.$store.commit(`home/${types.ADD_CAR_NUM}`, n)
    this.$store.commit(`home/${types.REDUCE_CAR_NUM}`, n)
}

使用方法:

this[types.ADD_CAR_NUM]()
this[types.REDUCE_CAR_NUM]()
mapActions

mapAcions是寫在methods里面,因為他觸發的是方法
在methods中添加addA和addB的映射

methods: {
  ...mapActions({
    groupData: 'detail/groupData'
  }),
},

等價于:

methods: {
  addA(n) {
    this.$store.dispatch('detail/groupData', n)
  }
}

'groupData'是定義的一個函數別名稱,掛載在到this(vue)實例上,

注意:'detail/groupData' 才是details頁面actions里面函數方法名稱 。

調用的方法:

this.groupData(res)

至此,vuex中的常用的一些知識點使用算是簡單的分享完了,當然了,相信這些只是一些皮毛!只能說是給予剛接觸vuex的初學者一個參考與了解吧!有哪里不明白的或不對的,留言下,咱們可以一起討論、共同學習!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 安裝 npm npm install vuex --save 在一個模塊化的打包系統中,您必須顯式地通過Vue.u...
    蕭玄辭閱讀 2,964評論 0 7
  • ### store 1. Vue 組件中獲得 Vuex 狀態 ```js //方式一 全局引入單例類 // 創建一...
    蕓豆_6a86閱讀 740評論 0 3
  • Vuex是什么? Vuex 是一個專為 Vue.js應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件...
    蕭玄辭閱讀 3,153評論 0 6
  • vuex 場景重現:一個用戶在注冊頁面注冊了手機號碼,跳轉到登錄頁面也想拿到這個手機號碼,你可以通過vue的組件化...
    sunny519111閱讀 8,040評論 4 111
  • ### store 1. Vue 組件中獲得 Vuex 狀態 ```js //方式一 全局引入單例類 // 創建一...
    蕓豆_6a86閱讀 397評論 0 0