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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市肆氓,隨后出現(xiàn)的幾起案子袍祖,更是在濱河造成了極大的恐慌,老刑警劉巖谢揪,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蕉陋,死亡現(xiàn)場(chǎng)離奇詭異捐凭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)凳鬓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)柑营,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人村视,你說(shuō)我怎么就攤上這事官套。” “怎么了蚁孔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵奶赔,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我杠氢,道長(zhǎng)站刑,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任鼻百,我火速辦了婚禮绞旅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘温艇。我一直安慰自己因悲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布勺爱。 她就那樣靜靜地躺著晃琳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪琐鲁。 梳的紋絲不亂的頭發(fā)上卫旱,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音围段,去河邊找鬼顾翼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛奈泪,可吹牛的內(nèi)容都是我干的适贸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼段磨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼取逾!你這毒婦竟也來(lái)了耗绿?” 一聲冷哼從身側(cè)響起苹支,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎误阻,沒(méi)想到半個(gè)月后债蜜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體晴埂,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年寻定,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了儒洛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡狼速,死狀恐怖琅锻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情向胡,我是刑警寧澤恼蓬,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站僵芹,受9級(jí)特大地震影響处硬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拇派,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一荷辕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧件豌,春花似錦疮方、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至棘街,卻和暖如春蟆盐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遭殉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工石挂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人险污。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓痹愚,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蛔糯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拯腮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355