短短几行代碼手寫一個 vuex

前言

咱們都知道在用 vue 的時候,簡單的父子通訊和 EventBus 已經不能知足咱們的要求了,嵌套層級過多和難以追蹤改變是兩個較爲主要的問題😵,這個時候能夠用 vuex 來解決,想必你們都用過,因此今天跟你們分享的是 vuex 的簡單實現,真的是超簡單,就幾行代碼(文章結尾有連接),帶你領略 vuex 的精髓,而且在最後會有幾個問題答疑(好比時光穿梭、本地持久化等)幫你們鞏固一下 vuex。javascript

前置知識

vuex 是基於 vue 的狀態管理工具,通俗點講講就是變量共享,你要知道 this.$store 本質是個對象,你們能夠看下下面這張圖,看看 this.$store 究竟是個啥(只看有標號的行便可)👇: css

還記得咱們是怎麼使用 vuex 的麼?

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex); // 這是插件固定寫法,沒有什麼爲何,官網寫的很清楚,這樣寫 vue 會自動調用插件的 install 方法
const store = new Vuex.Store({
  state: {...},
  mutations: {...},
  actions: {...},
  getters: {...},
})
new Vue({
  el: '#app',
  store
})
複製代碼

很顯然,Store 裏面大體就傳入四個參數,目前咱們也只須要這些參數就夠了,四個參數又能夠分爲兩類,一類是獲取數據,一類是修改數據,就像下面這張圖畫的同樣👇: html

我感受上面這張圖畫的還挺不錯😂,應該挺明瞭。組件和 store 是分開的,組件獲取數據能夠經過 $store.state$store.getters,組件要修改數據能夠經過 $store.actions$store.mutations(固然最終都是 mutations,這也使得便於追蹤狀態的改變),如此一來,造成了閉環,也符合咱們所說的單向數據流的思想,只有一個地方能改數據,不能遍地開花,否則就亂套了。另外 store 也是個單例模式的例子,全部的組件都共用一個全局的 store,每一個組件的 $store 都是同一個東西,這點也很好理解。
再補充一句,不少人老是記不清 actions 和 mutations 到底哪一個是異步哪一個是同步,不妨試試這樣記:actions 和 async 都是 a 開頭的,因此 actions 是異步,或者 actions 是要調用 api 的,也都是 a 開頭的,因此是異步😳。

簡單實現

有了上面這些概念,接下來咱們就簡單用幾行代碼來簡單實現一下吧。首先先寫個簡單的小框架,就像下面這樣(官網上有說明插件的編寫方式):vue

<script src="https://cdn.bootcss.com/vue/2.6.11/vue.js"></script>
<script> const Vuex = {} Vuex.install = function (Vue) { // 這個 Vue 是 vue 提供給咱們的 console.log('install 方法開始執行') } Vue.use(Vuex) </script>
複製代碼

ok,而後咱們在 install 裏面寫 vuex 的代碼便可,如今咱們只須要聲明一個 store 對象而且賦予 store 兩個屬性(這裏以 state 和 mutations 舉例),這個我用很簡單的代碼演示一下你們就清楚了👇:java

Vuex.install = function (Vue) {
  console.log('install 方法開始執行')
  const store = {} // 聲明一個對象
  store.state = new Vue({ // 賦予 state 屬性,用來獲取值
    data () {
      return {
        msg: '哈哈'
      }
    }
  })
  store.mutations = { // 賦予 mutations 屬性,用來修改值
    SETMSG(value) {
      store.state.msg = value
    }
  }
}
複製代碼

上面的代碼中要注意的是 state 實際上是利用了 vue 的響應式原理,使用了 new Vue(),由此 state 就變成響應式的了,這是 vue 的特性。注意這裏單純的使用 Object.defineProperty 來定義 state 是不行的,是沒法更新視圖的,由於它並無被 vue 進行依賴收集,因此說 vuex 是強依賴於 vue 的。還有就是一開始咱們也要把 state 的數據寫全,否則後面添加的也是無響應更新的,你們若是有用到過 $set 應該能體會到爲何有時候數據變了,視圖沒更新那種感受😬。git

👌,如今咱們已經有了 store,接下來就是把它掛到每一個組件下面了,這裏咱們使用 Vue.mixin 的混入方式,代碼以下:github

Vue.mixin({ // 也是固定寫法:每一個組件都會執行下面這個生命週期
    beforeCreate () {
      this.$store = store // 因而每一個組件都會有 this.$store,而且都指向同一個 store
    }
  })
複製代碼

固然用 Vue.prototype.$store = store 也能達到一樣的效果,事實上,vue-router 也是一樣的方式咱們才能在每一個實例中用 this.$router 來調用。另外若是要說 mixin 和 prototype 這兩種掛載方式的區別,我就想到兩小點😯:vue-router

  • mixin 是在 vue 實例上,prototype 是在原型上
  • 把 store 掛載在實例上,就不用順着原型鏈查找了,提高了一丟丟性能

不知道你們還知道其餘緣由嗎,歡迎在下面留言。vuex

好了,至此,咱們大概就寫完了一個簡單的 demo,如今寫個例子來測試一下:api

let v1 = new Vue({
  el: '#component1',
  computed: {
    data() {
      return this.$store.state.msg
    }
  }
})
let v2 = new Vue({
  el: '#component2',
  methods: {
    change() {
      this.$store.mutations.SETMSG('這是 mutations 觸發的值')
    }
  }
})
console.log(v1, v2)
複製代碼

下面是測試的結果:

固然咱們平時用 mutations 是經過 commit 來寫的,其實 commit 就是個函數,本質上也是調用 mutations,這裏也順手簡單寫下 commit👇:

Vuex.install = function (Vue) {
    ...
    store.commit = function(mutationName, value) {
        store.mutations[mutationName] && store.mutations[mutationName](value)
    }
    ...
}
...
let v2 = new Vue({
  el: '#component2',
  methods: {
    change() { // 改一下調用方式,結果是同樣的
      // this.$store.mutations.SETMSG('這是 mutations 觸發的值')
      this.$store.commit('SETMSG', '這是 mutations 觸發的值')
    }
  }
})
複製代碼

最終效果是同樣的,這裏就不展現了。 深吸一口氣,目前爲止咱們已經實現超簡版的 vuex,接下來是幾個問題答疑🤔。

問題答疑

爲何須要 getters

這個東西其實和咱們平時寫的計算屬性一毛同樣,state 和 getters 的關係比如 data 和 computed,你們細品一下。

如何區分 state 是否是經過 commit 修改的

咱們知道 vuex 中修改 state 就一個通道,就是執行 commit,但其實你不經過 commit 也是能改的,那怎麼知道它是經過 commit 修改的呢?就是在執行 commit 的時候加個標誌位 _committing,執行 commit 的時候將 _committing 設置爲 true,_committing 爲 true 才能修改 state,而其餘方式修改的 state 並不會修改 _committing 標誌位,這樣一來就能判斷是否是經過 commit 修改的。 若是你在 vuex 中打開了嚴格模式,任何非 mutation 更改都會拋出錯誤。

mapState 等輔助函數的實現

這一類輔助函數本質就是語法糖,這裏咱們以 mapState 舉例子,咱們回顧一下用法:

import {mapState} from 'vuex'
export default{
    computed:{
        ...mapState(['msg','user'])
    }
}
複製代碼

而後咱們在頁面中就能用 this.msg 訪問,其實調用的仍是 this.$store.state.msg,只不過寫起來簡單點。下面咱們看下怎麼簡單實現,很顯然,這裏 mapState 是一個函數,接收一個數組:

function mapState (list) {
  let obj = {}
  list.forEach(stateName => {
    obj[stateName] = () => this.$store.state[stateName]
  })
  return obj
}
複製代碼

👏是的,就這麼點代碼,其餘 map 輔助函數也是也同樣的道理。

本地持久化

  • 咱們能夠在每次 commit 的時候把 state 保存到 localStorage 或者 sessionStorage 中,而後頁面初始化的時候,先讀取本地存儲的 state 值,不過要注意頻繁存儲的問題。
  • 咱們能夠利用 beforeunload 這個事件,在頁面卸載以前再把 state 的值存起來,這樣效率也挺高的。

時光穿梭

這是 devtoolPlugin 提供的功能,由於開發的時候全部 state 的改變都有記錄,「時光穿梭」的功能其實就是可以讓咱們回到或去到某一狀態,實際上就是將當前的 state 替換爲記錄中的某個 state,咱們看下 vuex 的 store 中就爲咱們提供了一個這樣的函數 replaceState(真的是直接替換😂),具體代碼以下👇:

replaceState (state) {
    this._withCommit(() => { // 這裏面就是上面說到的 _commiting 標識
      this._vm.state = state
    })
  }
複製代碼

下面是兩個個小截圖,但願可以幫助你理解:

小結

若是硬要說 vuex 中最核心的一個點的話,那就是利用了 vue 的響應式,我想這是最爲主要的。最後但願本篇文章可以對你有所幫助,不知道寫的清不清楚😁,也祝你們百毒不侵,開開心心上班,回見👋。

ps: 最簡版 vuex 代碼地址精裝版 vuex 代碼地址

相關文章
相關標籤/搜索