一個簡單的vuex學(xué)習(xí)項目

包含如何新建項目和上傳github,包含vuex的State、Getters、Mutations、Actions、Module、表單處理。

SimpleVuex源碼點擊下載

  • 注意事項,本人也是菜鳥一枚,下載后安裝時把package-lock.json文件干掉再npm install
    image.png

第一步,自己去注冊一個GitHub的賬號

第二步,新建一個空項目

第三步,本地找個盤兒,git clone 下來空項目

第四步,下載vue-cli

第五步,進入當(dāng)前空項目路徑,vue init webpack

第六步,一系列yes or no ,配置完畢run起來

第七步,把特么的HelloWorld組件改成你想要的樣子

第八步,退出來,安裝vuex

  • npm install vuex --save
    PS 是--save 不是 --save-dev 具體區(qū)別請百度,我解釋了你也不懂

第九步,建立一個store文件夾和main.js同級

  • store具體結(jié)構(gòu)如下
    └── store
    ├── index.js # 我們組裝模塊并導(dǎo)出 store 的地方
    ├── mutation-types.js #使用常量替代 mutation 事件類型,作用用的時候再告訴你
    └── modules
    ├── cart.js # 購物車模塊
    └── products.js # 產(chǎn)品模塊

什么是Vuex?
Vuex 是一個專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式
什么是狀態(tài)管理模式?
文檔上絮絮叨叨,老子粗獷的總結(jié)一下,vuex就是建立一個全局?jǐn)?shù)據(jù)庫,每個數(shù)據(jù)都可修改和監(jiān)控。
項目為啥要用Vuex?
傳參的方法對于多層嵌套的組件將會非常繁瑣,最難受的是對于非父子組件間的狀態(tài)傳遞無能為力。Vuex只要是在這個項目里任何組件都能使用數(shù)據(jù),操控數(shù)據(jù)狀態(tài)。
父子組件里經(jīng)常特么的拷貝多份數(shù)據(jù),且到處都是事件來修改這些數(shù)據(jù),一旦功能繁瑣,代碼就是稀爛,閱讀難交接難維護難上加難。Vuex不用你拷貝,任何組件都能修改數(shù)據(jù)狀態(tài),且是同一個數(shù)據(jù)狀態(tài),何樂不為?
Vuex有什么缺點?
一個難搞的概念和框架,會增加技術(shù)成本和項目周期,刷新數(shù)據(jù)狀態(tài)變?yōu)槌跏蓟?/p>

第十步,我們先做一個最簡單的例子

  • cart.js內(nèi)容如下
// 最簡單的例子
export default {
  // 定義狀態(tài)
  state: {
    count: 0, // 貨物數(shù)量
    str: null,
    arr: [],
    form: {}
  },
  getters: {

  },
  actions: {

  },
  mutations: {
    increment (state) { // 增一
      state.count++
    },
    reduce (state) { // 減一
      state.count--
    }
    
  }
} 
  • index.js內(nèi)容如下
import Vue from 'vue'
import Vuex from 'vuex'
import cart from './modules/cart'

Vue.use(Vuex) // 顯式地通過 Vue.use() 來安裝 Vuex

export default new Vuex.Store({
  modules: {
    cart
  }
})
  • main.js 把store注入全局,也就是vuex注入全局
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store' // 引入

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

第十一步,開始使用,新建相關(guān)組件

  • 和HelloWorld.vue組件同級新建cart.vue和product.vue
  • cart.vue
<template>
  <div class="hello">
    <h1>我是購物車組件</h1>
    <button @click="increment">點擊加1</button> <br>
    仔細(xì)觀察產(chǎn)品數(shù)量:{{this.$store.state.cart.count}}
  </div>
</template>

<script>
export default {
  data() {
    return {
    }
  },
  created(){
    console.log(this.$store)
  },
  methods: {
    increment() {
      this.$store.commit('increment')
    }
  }
}
</script>
  • product.vue
<template>
  <div class="hello">
    <h1>我是產(chǎn)品組件</h1>
    <button @click="reduce">點擊減1</button> <br>
    仔細(xì)觀察產(chǎn)品數(shù)量:{{this.$store.state.cart.count}}
  </div>
</template>

<script>
export default {
  data() {
    return {
    }
  },
  methods: {
    reduce() {
      this.$store.commit('reduce')
    }
  }
}
</script>
  • HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <h2>下面是兩個組件</h2>
    <ul>
      <li>
        <cart></cart>
      </li>
      <li>
        <product></product>
      </li>
    </ul>
  </div>
</template>

<script>
import cart from '../components/cart.vue'
import product from '../components/product.vue'
export default {
  name: 'HelloWorld',
  data() {
    return {
      msg: '歡迎來到我的vuex學(xué)習(xí)項目'
    }
  },
  components: {
    cart,
    product
  }
}
</script>

<style scoped>
h1,
h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

** 效果截圖如下**


image.png

對于以上簡單Vuex做個總結(jié),this.$store.commit('reduce')觸發(fā)store中cart.js的mutations中的increment (state) {state.count++},這樣做的目的就是為了通過提交 mutation 的方式記錄每次狀態(tài)改變。其實你也可以直接this.$store.state.cart.count++,如果你經(jīng)歷過非父子組件間相互傳值的痛苦,此時你應(yīng)該是充滿欣喜興奮的,我們的兩個組件就是毫無關(guān)系的組件但是卻做到了數(shù)據(jù)同步。

第十二步我們先把這個項目傳到github去

git add . // 這個點...看好了,有個點兒的哈。不會git的傻蛋老是說沒得反應(yīng)!!
git commit -m "xxx" // 這是備注信息xxx
git push origin master

第十三步理解,State,Getters,Mutations,Actions

一句話來概括他們的關(guān)系,state定義狀態(tài),getters監(jiān)聽狀態(tài),mutations更改狀態(tài)但是不支持異步,actions觸發(fā)mutations,但允許異步。

State

定義狀態(tài),我看了半天覺得我也沒啥好講的,就是定義和保存所有數(shù)據(jù),了解下他的輔助函數(shù)mapState

  • store/index.js 添加根級state
export default new Vuex.Store({
  state: {
    count: 0
  }
})

之所以是根級,是因為模塊的state有一個聲明空間,極其晦澀和難以理解,我們先易后難。

  • studyState.vue 和HelloWorld.vue同級存放
<template>
  <div class="hello">
    <h1>State以及它的輔助函數(shù)mapState</h1>
    state.count:{{count}} <br>
    countAlias:{{countAlias}} <br>
    countPlusLocalState:{{countPlusLocalState}} <br>
  </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  computed: {
    ...mapState({
      count: state => state.count,
      // 傳字符串參數(shù) 'count' 等同于 `state => state.count`
      countAlias: 'count',
      // 為了能夠使用 `this` 獲取局部狀態(tài),必須使用常規(guī)函數(shù)
      countPlusLocalState(state) {
        return state.study.count + 1
      }
    })
  }
}
</script>
  • HelloWorld.vue中做以下修改
    引入并注冊studyState.vue和放到你覺得合適的地方使用。
 <ul>
    <li>
      <study-state></study-state>
    </li>
 </ul>

  import studyState from '../components/studyState.vue'

  components: {
    studyState
  }

截圖效果

image.png

mapState的第二種使用方式:
當(dāng)映射的計算屬性的名稱與 state 的子節(jié)點名稱相同時,我們也可以給 mapState 傳一個字符串?dāng)?shù)組。

 computed: mapState([
    'count', 'name'
  ])
Getter
  • 可以認(rèn)為是 store 的計算屬性。getter 的返回值會根據(jù)它的依賴被緩存起來,且只有當(dāng)它的依賴值發(fā)生了改變才會被重新計算。其實和state差不多!
    studyGetter.vue
<template>
  <div>
    通過屬性訪問: <br>
    顯示元素:{{this.$store.getters.showElement}} <br><br>
    可以接受其他 getter 作為第二個參數(shù) <br>
    長度:{{showLength}} <br><br>
    通過id獲取數(shù)組中符合的 <br>
    同id數(shù)組元素: {{this.$store.getters.getElementById(2)}} <br><br>
    mapGetters 輔助函數(shù)僅僅是將 store 中的 getter 映射到局部計算屬性<br>
    元素名數(shù)組: {{elementNames}}<br><br>
    <!-- 如果你想將一個 getter 屬性另取一個名字,使用對象形式 <br> -->
    <!-- {{names}} -->

  </div>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
  computed: {
    showLength() {
      return this.$store.getters.showLength
    },
    ...mapGetters([
      'elementNames'
    ])
  },
  //  如果你想將一個 getter 屬性另取一個名字,使用對象形式
  // computed: mapGetters({
  //   names: 'elementNames'
  // })
}
</script>
  • 在store/index.js中做以下修改
export default new Vuex.Store({
  state: {
    count: 0,
    name: '默認(rèn)名字',
    array: [{name: '元素A', id: 1, display: true}, {name: '元素B', id: 2, display: false}]
  },
  getters: {
    showElement: state => {
      return state.array.filter(ele => ele.display)
    },
    // getters可以作為第二參數(shù)
    showLength: (state, getters) => {
      return getters.showElement.length
    },
    // 通過方法獲取,可以傳參哦
    getElementById: (state) => (id) => {
      return state.array.find(ele => ele.id === id)
    },
    elementNames: state => {
      return state.array.map(ele => ele.name)
    }
  }
  • 和上面的步驟一樣,你得在HelloWorld.vue中引入組件
// 展示
<ul>
  <li>
    <h1>Getters以及它的輔助函數(shù)mapGetters </h1>
  </li>
</ul>
<div style="padding-left: 300px;text-align: left;">
  <study-getters></study-getters>
</div>
// 引入
import studyGetters from '../components/studyGetters.vue'
// 注冊
components: {
   studyGetters
}

效果圖

image.png

Mutations
  • 更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似于事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調(diào)函數(shù) (handler)。這個回調(diào)函數(shù)就是我們實際進行狀態(tài)更改的地方,并且它會接受 state 作為第一個參數(shù):
mutations: {
    increment (state) {
      // 變更狀態(tài)
      state.count++
    }
}
  • store/index.js 修改如下
export default new Vuex.Store({
  namespaced: true,
  state: {
    count: 0,
    name: '默認(rèn)名字',
    array: [{
      name: '元素A',
      id: 1,
      display: true
    }, {
      name: '元素B',
      id: 2,
      display: false
    }],
    form: {
      name: null,
      age: null,
    }
  },
  getters: {
    showElement: state => {
      return state.array.filter(ele => ele.display)
    },
    // getters可以作為第二參數(shù)
    showLength: (state, getters) => {
      return getters.showElement.length
    },
    // 通過方法獲取,可以傳參哦
    getElementById: (state) => (id) => {
      return state.array.find(ele => ele.id === id)
    },
    elementNames: state => {
      return state.array.map(ele => ele.name)
    }
  },
  mutations: {
    increment(state) {
      // 變更狀態(tài)
      state.count++
    },
    add(state, num) {
      state.count += num
    },
    addObj(state, obj) {
      state.count += Number(obj.num)
    },
    // 當(dāng)使用對象風(fēng)格的提交方式,整個對象都作為載荷傳給 mutation 函數(shù)
    addByType(state, payload) {
      state.count += payload.amount
    },
    // 使用常量
    [SET_NAME](state, form) {
      // state.form.name = form.name // 我們不建議使用這種方式
      Vue.set(state.form, 'name', form.name)
    },
    // 嘗試異步
    [SET_AGE](state, form) {
      setTimeout(() => {
        Vue.set(state.form, 'age', form.age)
      }, 1000)
      setTimeout(() => {
        Vue.set(state.form, 'age', 12)
      }, 1100)
      setTimeout(() => {
        Vue.set(state.form, 'age', 34)
      }, 1200)
    }
  },
  modules: {
    cart,
    study,
    moduleA
  }
})

新建studyMutations.vue,內(nèi)容如下

<template>
  <div>
    通過Mutations觸發(fā)狀態(tài)更改: <br>
    state.count++ <button @click="increment()">加一</button> <br>
    state.count = {{this.$store.state.count}} <br>
    <p>
      我們不能直接調(diào)用this.$store.mutations('increment') <br>
      它更像是vue的methods,是注冊了一個事件而已,調(diào)用它必須使用store.commit
    </p>
    Payload(提交載荷), 大白話就是可以傳參 <br>
    <button @click="add(10)">點擊加10</button> <br> <br>
    當(dāng)你有多個參數(shù)時:傳對象,實戰(zhàn)中我們基本上都是傳對象 <br>
    輸入數(shù)字:<input type="number" v-model="form.num"><button @click="addObj(form)">增加</button> <br> <br>
    <br>
    提交 mutation 的另一種方式是直接使用包含 type 屬性的對象: <br>
    <button @click="addByType(100)">增加100</button> <br> <br>
    調(diào)用常量方式定義的mutations修改姓名: <br>
    輸入姓名:<input type="text" v-model="form.name"><button @click="setName(form)">提交</button> <br> <br>
    <br>
    state.form.name = {{this.$store.state.form.name}} <br>
    輸入年齡:<input type="text" v-model="form.age"><button @click="setAge(form)">提交</button> <br> <br>
    嘗試異步修改狀態(tài)發(fā)現(xiàn)依舊是可以的,但是為啥官方文檔說不支持異步呢?:<br>
    state.form.age = {{this.$store.state.form.age}} <br>
    <p>并不是不支持,而是無法追蹤狀態(tài)!這個演示我們需要用到devtool,異步vuex不搭理他!詳情請看簡書上我的截圖演示!</p>
    <b>所以又了Actions</b>
  </div>
</template>

<script>
export default {
  data() {
    return {
      form: {
        num: 8,
        name: null
      }
    }
  },
  created() {
    console.log(this.$store)
  },
  computed: {
  },
  methods: {
    // 調(diào)用它必須使用store.commit
    increment() {
      this.$store.commit('increment')
    },
    // Payload(提交載荷), 大白話就是可以傳參
    add(num) {
      // this.$store.commit('add', num)
    },
    // 傳一個對象參數(shù)解決多參數(shù)問題
    addObj(obj) {
      this.$store.commit('addObj', obj)
    },
    addByType(num) {
      // 提交 mutation 的另一種方式是直接使用包含 type 屬性的對象:
      this.$store.commit({
        type: 'addByType',
        amount: num
      })
    },
    // 調(diào)用常量方式定義的mutations修改姓名
    setName(form) {
      this.$store.commit('SET_NAME', form)
    },
    // 嘗試異步
    setAge(form) {
      this.$store.commit('SET_AGE', form)
    }
  }
}
</script>
  • 然后注冊和引用到HelloWorld組件里,截圖如下


    image.png

    **mutations一樣有輔助函數(shù)

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
  /* 輔助函數(shù)
    ...mapMutations([
      'increment', // 將 `this.increment()` 映射為 `this.$store.commit('increment')`

      // `mapMutations` 也支持載荷:
      'addObj' // 將 `this.addObj(obj)` 映射為 `this.$store.commit('addObj', obj)`
    ]),
    
    ...mapMutations({
      add: 'increment' // 將 `this.add()` 映射為 `this.$store.commit('increment')`
    })
    */
}
解釋下為啥mutations不支持異步
  • 安裝vue-devtools 自行百度安裝使用

根據(jù)上圖我輸入不是三十四來分析

mutations里的代碼是

  mutations: {
    // 嘗試異步
    [SET_AGE](state, form) {
      setTimeout(() => {
        Vue.set(state.form, 'age', form.age)
      }, 1000)
      setTimeout(() => {
        Vue.set(state.form, 'age', 12)
      }, 1100)
      setTimeout(() => {
        Vue.set(state.form, 'age', 34)
      }, 1200)
    }
  },

** 我們打開devtool工具


image.png
Actions
  • actions的存在就是為了觸發(fā)mutations,只不過他支持了異步,所以我們雖然有時候覺得沒啥子意義,但是約定俗成的會使用actions,這就是為了防止出現(xiàn)意外的bug。
  • 新建一個studyActions.vue 內(nèi)容如下
    PS 如果有人和我一起在做的話,很多地方我考慮的不全面,估計會出問題,你可以查看我的github項目完整代碼對比看看我是不是遺漏了某個步驟。
<template>
  <div>
    輸入身高:<input type="text" v-model="form.height"><button @click="setHeight(form)">提交</button> <br> <br><br>
    state.form.height = {{this.$store.state.form.height}} <br>
  </div>
</template>

<script>
export default {
  data() {
    return {
      form: {
        num: 8,
        name: null
      }
    }
  },
  methods: {
    // 嘗試異步
    setHeight(form) {
      this.$store.dispatch('setHeight', form)
    }
  }
}
</script>
  • store目錄下的index.js做以下修改
  state: {
    form: {
      height: null
    }
  }
  mutations: {
    /*** 這里是原來的代碼我就不復(fù)制了**/
    // 嘗試Actions異步
    [SET_HEIGHT](state, height) {
      Vue.set(state.form, 'height', height)
    },
  },
  actions: {
    setHeight (context, form) {
      // context Action 函數(shù)接受一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以調(diào)用 context.commit 提交一個 mutation,或者通過 context.state 和 context.getters 來獲取 state 和 getters。
      context.commit('SET_HEIGHT', form.height)
      setTimeout(() => {
        context.commit('SET_HEIGHT', 180)
      }, 500)
      setTimeout(() => {
        context.commit('SET_HEIGHT', 185)
      }, 1000)
    }
  },

mutations_types.js

export const SET_HEIGHT = 'SET_HEIGHT' // 修改身高

效果如下

image.png

PS 只有這樣的結(jié)果才有意義!!使用vuex才有價值!!

我們繼續(xù)
dispatch是一個分發(fā)actions的函數(shù)

    setHeight(form) {
      // 這兒和mutations的commit一樣,actions需要用dispatch去觸發(fā)
      this.$store.dispatch('setHeight', form)
    }

最終版studyActions.vue

<template>
  <div>
    輸入身高:<input type="text" v-model="form.height"><button @click="setHeight(form)">提交</button> <br> <br>
    state.form.height = {{this.$store.state.form.height}} <br><br>
    <p>mapActions映射</p>
    <button @click="increment">提交increment, count++</button> <br>
    this.$store.state.count = {{this.$store.state.count}}<br> <br>
    <p>`mapActions` 也支持載荷:</p>
    輸入姓名:<input type="text" v-model="form.name"><button @click="setName(form)">提交</button> <br>
    state.form.name = {{this.$store.state.form.name}} <br><br>
    <p>mapActions映射increment為add</p>
    <button @click="add">提交increment, count++</button> <br>
    <p>高級玩法</p>
    <button @click="actionA">加一后,再加一</button> <br>
    <p>進階玩法</p>
    <button @click="actionB">加一后,再加十一</button> <br>
  </div>
</template>

<script>
import { mapActions } from 'vuex'
export default {
  data() {
    return {
      form: {
        num: 8,
        name: null
      }
    }
  },
  methods: {
    // 嘗試異步
    setHeight(form) {
      // 這兒和mutations的commit一樣,actions需要用dispatch去觸發(fā)
      this.$store.dispatch('setHeight', form)
    },
    // mapActions 輔助函數(shù)將組件的 methods 映射為 store.dispatch 調(diào)用(需要先在根節(jié)點注入 store)
    ...mapActions([
      'increment', // 將 `this.increment()` 映射為 `this.$store.dispatch('increment')`

      // `mapActions` 也支持載荷:
      'setName' // 將 `this.incrementBy(amount)` 映射為 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 將 `this.add()` 映射為 `this.$store.dispatch('increment')`
    }),
    // 高級操作
    actionA() {
      this.$store.dispatch('actionA').then(() => {
        // 十秒以后再加一
        setTimeout(() => {
          this.add()
        }, 1000)
      })
    },
    // 進階級
    actionB() {
      this.$store.dispatch('actionB')
    }
  }

}
</script>

store下的index.js


  actions: {
    setHeight (context, form) {
      // context 你就理解成actions可以傳入一個參數(shù)就是store本身(雖然并不是本身)。包含里面的state,getter,mutation,action
      context.commit('SET_HEIGHT', form.height)
      setTimeout(() => {
        context.commit('SET_HEIGHT', 180)
      }, 500)
      setTimeout(() => {
        context.commit('SET_HEIGHT', 185)
      }, 1000)
    },
    // actions觸發(fā)mutations的increment
    increment({commit, state}) {
      commit('increment', state)
    },
    // mutation中的SET_NAME
    setName({commit, state}, form) {
      commit('SET_NAME', form) 
    },
    // 組合 Action
    actionA ({ commit }) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          commit('increment')
          resolve()
        }, 1000)
      })
    },
    // 你甚至還能這么玩
    actionB ({ dispatch, commit }) {
      return dispatch('actionA').then(() => {
        setTimeout(() => {
          commit('add', 10)
        }, 1000)
      })
    }
  },

這里面講解下幾個難點

Action 函數(shù)接受一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以調(diào)用 context.commit 提交一個 mutation,或者通過 context.state 和 context.getters 來獲取 state 和 getters。

高階玩法你需要明白 store.dispatch 可以處理被觸發(fā)的 action 的處理函數(shù)返回的 Promise,并且 store.dispatch 仍舊返回 Promise。

整篇文章寫得很碎,項目會不停的更新,所以有可能和簡書上的不一致,請自行理解,這算是初稿!后面還有模塊的講解和關(guān)于表單處理,下一篇吧。

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

推薦閱讀更多精彩內(nèi)容