Vue中使用高階組件實現依賴注入


公司里面接手一個前端老項目,采用的是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在這個版本已經在暴露出它的響應式編程核心能力,也就是reactiveapi,通過這個api可以實現一個極簡的store方案。

  • 其次,改善vuexstore方案的強侵入性。例如需要mapState將狀態侵入型的注入到組件中,我知道vuex有通過頂層vue實例注入的能力,但這樣也會形成子組件對頂層組件的強依賴。我希望實現的是,一個vue組件對外部store是無感知的,我們只需要關注它需要的數據prop,以及它對外作出的狀態變更請求emit事件。這里我參考了react里面使用mobx通過高階組件inject注入stateaction的思路。

當我在實現向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 修飾符。

看了以上官方文檔大家應該知道我要干什么了,首先我需要實現一個高階函數,它接受一個組件對象作為參數,然后返回一個經過篡改的函數式組件。有人可能要問了為什么要篡改了,因為,這個函數式組件不僅需要injectstore,還需要透傳父組件傳遞的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年后期得加油了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM