公司里面接手一個前端老項目,采用的是vuex
狀態管理方案,Store目錄結構是按照vuex
官方的module
方案實現的,不熟悉Vuex
的小伙伴,可以點擊此處查閱。整個代碼的實現,在我看來是比較符合規范的。奈何,同事們認為現有Store架構,太繁瑣了,查找一個問題,需要從action發起的源頭一直追到mutation。大家認為首先查找問題比較麻煩(這個我是不贊同的,如果合理且充分使用好vue-devtools
效率還是很高的。其次,vuex
這種架構模式寫起來太繁瑣了,有很多模版代碼。我看了一下其他同事沒有采用soter
方案的代碼,數據請求邏輯和數據處理邏輯基本上是直接放在vue
單文件組件里面的,有比較多的子組件對父組件的強依賴,這種方案確實比較直接,寫代碼的人很爽,維護代碼的人只需要看那個組件就行了。但是,這是在一個組件不復雜的情況下,比較好的處理方式,一旦業務邏輯復雜起來,這種方式實現的代碼大概率會走向腐化。為了照顧大家對代碼邏輯的簡潔需求以及架構穩固性,我比較vuex
的架構和大部分同事的組件代碼。確定了重構的方向:
-
首先,
vuex
方案對於現在的項目規模來說,還是偏重,需要實現一種偏簡潔的方案。根據我以前寫react的經驗,大家可能對mobx
那種輕量化的狀態管理方案比較喜歡,所以我只需要實現一個極簡響應式狀態管理。我心想,vue
這種響應式原理不是和mobx
一樣么。按照這個思路,我重新看了以下vue
官方文檔。剛好我們使用的vue
版本是2.6.11,vue在這個版本已經在暴露出它的響應式編程核心能力,也就是reactive
api,通過這個api可以實現一個極簡的store方案。 -
其次,改善
vuex
store方案的強侵入性。例如需要mapState
將狀態侵入型的注入到組件中,我知道vuex
有通過頂層vue
實例注入的能力,但這樣也會形成子組件對頂層組件的強依賴。我希望實現的是,一個vue
組件對外部store是無感知的,我們只需要關注它需要的數據prop
,以及它對外作出的狀態變更請求emit
事件。這里我參考了react
里面使用mobx
通過高階組件inject
注入state
和action
的思路。
當我在實現向vue
組件注入狀態的高階組件時,一切變得不是那么容易了。vue
不像react
那樣,所有的對外依賴都是通過prop
傳入,總之vue
的組件不夠函數式,更像是一個webpack
配置文檔,通過對象的形式去描述一個組件的渲染邏輯以及對外依賴。就在我一籌莫展之際,我又翻閱起了官方文檔。在[渲染函數](https://cn.vuejs.org/v2/guide/render-function.html)這一節,我看到了曙光。以下是官方文檔:
向子元素或子組件傳遞 attribute 和事件
在普通組件中,沒有被定義為 prop 的 attribute 會自動添加到組件的根元素上,將已有的同名 attribute 進行替換或與其進行智能合並。
然而函數式組件要求你顯式定義該行為:
Vue.component('my-functional-button', { functional: true, render: function (createElement, context) { // 完全透傳任何 attribute、事件監聽器、子節點等。 return createElement('button', context.data, context.children) } })
通過向 createElement 傳入 context.data 作為第二個參數,我們就把 my-functional-button 上面所有的 attribute 和事件監聽器都傳遞下> 去了。事實上這是非常透明的,以至於那些事件甚至並不要求 .native 修飾符。
看了以上官方文檔大家應該知道我要干什么了,首先我需要實現一個高階函數,它接受一個組件對象作為參數,然后返回一個經過篡改的函數式組件。有人可能要問了為什么要篡改了,因為,這個函數式組件不僅需要inject
store,還需要透傳父組件傳遞的prop
和設置的listeners
,這樣就可以實現被包裝組件的無感知了。以下是實現的為代碼:
// 這里store可以不傳入,而使用閉包下的store
const closureStore = Vue.observable({ count: 1 })
const actions = {
add() {
store.count += 1
},
sub() {
if (store.count === 1) return
store.count -= 1
}
}
function withInject(component, config = { state = closureStore, actions = actions}) {
return Vue.component(`hoc-${component.name}`, {
functional: true,
render(createElement, context) {
const data = context.data
Object.assign(data.attrs, { ...state })
Object.assign(data.on, actions)
return createElement(component, data, context.children)
}
})
}
// 在需要使用某個組件的位置,對其進行包裝,然后這個組件就可以通過注冊到任意vue組件中進行使用了
const SomeComponetWithInject = withInject(SomeComponent)
以上代碼,不是全部,但核心邏輯都在上面。從`react`到開始寫`vue`已接近兩個多月,說實話,`vue`對開發者更友好,不需要思考太多代碼架構,就像要是做菜,材料和菜譜都給你准備好,就等你去煎炒燉煮了。然而,以上優點也是`vue`的弊端,在實現一些復雜設計模式時,不夠靈活,沒有`react`提供那么多供你設計的可能性,不過,以上需求都在於代碼書寫者的執行能力和思維方式。也許我們不用那么折騰,少思考一點,把代碼邏輯寫的大家都能接受就OK了。看到這里的人,應該是認為我的文章有價值,感謝你們。近期加入了新公司,比較忙,有很多想寫的技術文章都沒時間寫,2020年后期得加油了。