60行代碼實(shí)現(xiàn)一個(gè)Vue狀態(tài)管理庫(kù)

先把成品放出來(lái):vuebx

前言

當(dāng)我們使用Vue開(kāi)發(fā)應(yīng)用時(shí),隨著應(yīng)用的功能增多,不可避免地組件也會(huì)增加,不同組件之間的狀態(tài)重用就會(huì)越來(lái)越困難。這個(gè)時(shí)候我們就需要一個(gè)全局狀態(tài)管理工具幫助我們管理狀態(tài),Vue官方提供了Vuex給我們使用,但是很多時(shí)候我們的項(xiàng)目只是幾個(gè)頁(yè)面,根本不需要用Vuex這種復(fù)雜度比較高的解決方案,所以我們迫切需要一個(gè)輕量、高可用的狀態(tài)管理庫(kù)。

正文

有人可能會(huì)說(shuō)用EventBus不就行了嗎?,事實(shí)上EventBus實(shí)質(zhì)上是個(gè)跨組件的消息傳遞工具,根本算不上狀態(tài)管理,而且寫(xiě)起來(lái)也很麻煩。其實(shí)問(wèn)題不用弄得這么復(fù)雜,我們可以利用Vue自帶的響應(yīng)性去解決我們的問(wèn)題,Vue 2.6.0版本發(fā)布了一個(gè)新的API Vue.observable( object ),這個(gè)方法可以讓一個(gè)對(duì)象變得可響應(yīng),Vue內(nèi)部就是用這樣的方式處理data函數(shù)返回的對(duì)象,現(xiàn)在Vue把這樣的能力暴露出來(lái)給我們使用。這個(gè)返回的對(duì)象可以直接用于渲染函數(shù)和計(jì)算屬性,并且會(huì)在發(fā)生改變時(shí)觸發(fā)相應(yīng)的更新。

const state = Vue.observable({ count: 0 })

const Demo = {
  render(h) {
    return h('button', {
      on: { click: () => { state.count++ }}
    }, `count is: ${state.count}`)
  }
}

你看,Vue已經(jīng)幫我們把臟活累活都干好了,距離我們的目標(biāo)就差一步了,我們只需要封裝一下,提供一個(gè)獲取數(shù)據(jù)和更新數(shù)據(jù)的接口就ok了。所以,話不多說(shuō),請(qǐng)看代碼:

import Vue from 'vue'
import { cloneDeep } from 'lodash'

function _updateCreator(state) {
  return (newState) => {
    if (typeof newState === 'function') {
      const oldState = cloneDeep(state)
      newState = newState.call(null, oldState)
    }
    Object.keys(newState).forEach(key => {
      if (!(key in state)) {
        throw new Error(`unknown state: ${key}`)
      }
      state[key] = newState[key]
    })
    return new Promise(resolve => {
      Vue.nextTick(() => {
        resolve(cloneDeep(state))
      })
    })
  }
}

function _mapGetters (state) {
  return (getters) => {
    const res = {}
    normalize(getters).forEach(({key, val}) => {
      res[key] = function () {
        if (! (val in state)) {
          throw new Error(`unknown state: ${val}`)
        }
        return state[val]
      }
    })  
    return res
  }
}

function normalize(map) {
  return Array.isArray(map) ?
    map.map(key => ({
      key,
      val: key
    })) :
    Object.keys(map).map(key => ({
      key,
      val: map[key]
    }))
}

function Vuebx (defaultValue = {}) {
  const state = Vue.observable(defaultValue)

  const getState = _mapGetters(state)
  const setState = _updateCreator(state)

  return [getState, setState]
}

export default Vuebx

雖然說(shuō)代碼只有60行,但看起來(lái)也挺長(zhǎng)的,我們不妨將代碼分解一下:

function Vuebx (defaultValue = {}) {
  const state = Vue.observable(defaultValue)

  const getState = _mapGetters(state)
  const setState = _updateCreator(state)

  return [getState, setState]
}

export default Vuebx

這是我們的主要模塊,主要的作用就是接受一個(gè)對(duì)象,然后用Vue.observable生成一個(gè)observable對(duì)象充當(dāng)state,最后利用閉包生成stategettersetter。然后我們?cè)賮?lái)看一下具體是怎么生成的:

function _mapGetters (state) {
  return (getters) => {
    const res = {}
    normalize(getters).forEach(({key, val}) => {
      res[key] = function () {
        if (! (val in state)) {
          throw new Error(`unknown state: ${val}`)
        }
        return state[val]
      }
    })  
    return res
  }
}

function normalize(map) {
  return Array.isArray(map) ?
    map.map(key => ({
      key,
      val: key
    })) :
    Object.keys(map).map(key => ({
      key,
      val: map[key]
    }))
}

其實(shí)這和Vuex的mapGetter實(shí)現(xiàn)方式是一樣的,所以使用方式一模一樣,作用都是將state映射到組件的計(jì)算屬性中。這個(gè)方法的實(shí)現(xiàn)原理是將傳進(jìn)來(lái)的數(shù)組或者對(duì)象序列化后,然后再?gòu)?code>state中提取出具體的字段包裝成函數(shù),與在組件中定義computed的函數(shù)是一樣的。最后,我們看一下setter是怎么實(shí)現(xiàn)的:

function _updateCreator(state) {
  return (newState) => {
    if (typeof newState === 'function') {
      const oldState = cloneDeep(state)
      newState = newState.call(null, oldState)
    }
    Object.keys(newState).forEach(key => {
      if (!(key in state)) {
        throw new Error(`unknown state: ${key}`)
      }
      state[key] = newState[key]
    })
    return new Promise(resolve => {
      Vue.nextTick(() => {
        resolve(cloneDeep(state))
      })
    })
  }
}

這是一個(gè)高階函數(shù),作用是利用閉包綁定state,返回的函數(shù)接受一個(gè)對(duì)象或者一個(gè)可以返回對(duì)象的函數(shù)來(lái)更新state的值,最后函數(shù)會(huì)返回一個(gè)promise,promise會(huì)在state更新后resolve,所以可以在then中獲取到最新的state。
ok,一個(gè)簡(jiǎn)單的狀態(tài)管理工具就完成了,實(shí)現(xiàn)的代碼加起來(lái)一共只有60行,包大小連1K都沒(méi)有,可以說(shuō)是相當(dāng)輕量了。而且接口定義非常簡(jiǎn)潔,只有兩個(gè)方法,用起來(lái)十分清爽。

// store/index.js
import vuebx from 'vuebx'

const state = {
  count: 1
}
const [getter, setter] = vuebx(state)
export default {
  getter,
  setter
}

// Counter.vue
<template>
  <p>{{ count }}<p>
  <p>
    <button v-on:click="increment">-</button>
    <button v-on:click="decrement">+</button>
  </p>
</template>
<script>
  import { getter, setter } from './store'
  export default {
    name: 'Counter',
    computed: {
      ...getter(['count'])
    },
    methods: {
      increment () {
        setter((state) => {
          return {
            count: state.count + 1
          }
        })
      },
      decrement () {
        setter((state) => {
          return {
            count: state.count - 1
          }
        })
      }
    }
  }
</script>

總結(jié)

我們利用了Vue的響應(yīng)性的能力,創(chuàng)建了一個(gè)輕量級(jí)的狀態(tài)管理工具,使用起來(lái)非常方便,適用于小型的Vue項(xiàng)目,當(dāng)然中大型的項(xiàng)目還是推薦使用Vuex,畢竟是官方負(fù)責(zé)維護(hù)的,質(zhì)量有保證。


??????
我是naecoo,前端打雜工程師,偶爾寫(xiě)寫(xiě)灌水文章...
Github
博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容