先把成品放出來(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
研铆,最后利用閉包生成state
的getter
和setter
埋同。然后我們?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ì)量有保證。