HOC 是什麼?
HOC 它不是 React 提供的 Api,它其實跟我們一般 JS 中所使用的 Higher Order Function (高階函式)很雷同,而高階函式接受一個或多個函式當作參數,做了一些事情後,最終 return 一個函式,就像ES6中常用的 map 、reduce 都是高階函式。
HOC(高階組件) 如同官方自己所描述的,他可以接收一個或多個參數,參數可以是 component ,並且最後 return 一個新的 component,是不是和高階函式超像?! 由此來看,我們可以用高階函式的概念,來看待 HOC 是可行的!
HOC 最好是能遵循 functional programming (FP)的原則,設計成 pure 沒有其他副作用的元件,關於詳細的 FP 原則,可以參考下面保哥的這篇,這邊不再多說。
HOC 能做什麼?
我們先各定義一個簡易的 HOC 與 元件,來看看 HOC 到底能對元件做什麼用
可以看到把某個 component 傳入 HOC,做一些需要完成的事,並回傳一個新的 component ,範例中我們在回傳的新元件加上 props say。
假設當有多個 component 都要做 say good morning 這件事,只要都掛入 HOC 即可,不用在每個元件都重寫 say 的代碼,這就是會要用它的原因。
如果對 const {say} = this.props 這種用法有疑問的,可以參考下面這篇。
HOC 的用法有哪些
前面範例屬於最簡易的 HOC 用法,接下來會介紹其他較進階的用法
- 傳參數的 HOC
簡易的 HOC,傳入一個元件並回傳新元件,那麼既然 HOC 是函式,也就意味著可以傳入參數去搭配使用,這裡定義一個 UserGenderHOC 並傳入參數去顯示性別。
- 組合使用多個 HOC
HOC 可以使用多層包覆的方式,讓一個元件共用多個抽象化的邏輯,我們再定義一個 CountHOC 來計數,把它與上面的 UserGenderHOC 組合,完成一個有性別及人數的簡易使用者清單。
可以看到第一層 UserGender 回傳帶有顯示性別的元件,再傳入 CountHOC 回傳帶有 incrementCount 方法的新元件,而性別與計數的方法都透過 props傳遞下來使用。
- HOC 與 Decorator
使用 HOC 時,前面的例子都是定義 const 後來使用,如下
除了這個方法,還可以用 @ 符號,也就是 Decorator 方式來使用 HOC,這邊不再多說 Decorator,詳細可以參考下面這篇
JavaScript Decorators: What They Are and When to Use Them
接下來用 Decorator 的方式來改寫一下
2個 HOC 一樣保持不變,在要使用的 Component 上裝飾需要的 HOC,效果雖然與前面定義 const 方式相同,但是這樣的閱讀性提升了許多,之後的範例也都使用 Decorator 的方式呈現。
HOC 實際使用的情境
前面對 HOC 做了解說與簡單的舉例,再來看實際使用的情況會更有感覺。
- 使用情境: 購物網站
網站內有很多不同的商品元件,這邊以水果類的元件為例,當使用者要購買時,都要執行驗證身分,再統計購買資料,接著送出訂單,這3個流程的代碼,未使用 HOC 的話大概像下面這樣
一旦商品種類越多,前述的 3個流程代碼,會重複的寫在各商品元件中,若使用 HOC 的話,就可避免這種情況發生。
接下來,我們把驗證、數量控制、送訂單,改成用 HOC 的方式使用,先定義出 3個 HOC ,如下
接著在要使用的 Component 裝飾上去3個 HOC 就可以達到一樣的效果!
如果現在要再增加茶類與糖果類的商品,同樣把2者掛入 HOC 即可,如下
- 情境的延伸思考:
- HOC 的順序
使用者清單例子中,性別與計數 HOC,因操作邏輯沒有先後的差異,傳遞的props 也沒有相依性,所以掛入順序是可以變換的,但購物網站,購買時需先確認身分,從 UserAuthHOC 中的 props 取得 token 並在 SubmitOrderHOC 中使用,若沒注意順序,會導致送訂單 API 的 request 出錯。
為了測試,更改一下購物網站 HOC 掛入的順序, 將 UserAuthHOC 改成最先掛入,至於掛入及執行順序,下圖正確版有標示出來,只要是使用 Decorator 都是照這個順序來跑的
結果呼叫 API request 的 token,本應從 props 取得,但現在卻是空的,如下
由此可證,使用多個 HOC 時,需要注意傳遞 props 之間的順序跟前後的相依性!
2. 呼叫 API 的 HOC
購物網站例子中,送單 Call API 的部分我們做一隻 HOC 負責,如果照這樣的方式,是否可以把所有 Call API 抽出來做個 HOC ,以後要用都掛入這個就好了呢?! 讓我們來看看行不行,先定義 Call API HOC
與購物網站例子相比,可以發現很明顯的問題,Call API 無法變更需要的參數量,而且例子中的 Method 一但要改變,例如改成 get 就會產生錯誤。
購物網站在購買時,可以確定商品元件送訂單的 API request, 每次的 Method,但是上面的例子會有 Method 與要變換的狀況出現,因此不適合抽成 HOC。
建議,當能明確定義出該元件的職責單一性,再來思考抽成 HOC 的模式!
3. HOC 除了抽出共用代碼外,還帶來什麼效益
前述的例子主要都在表達 HOC 可以 " 抽出共用代碼 " 這件事,但使用它還會帶來甚麼好處呢?! 以購物網站的例子來看看
當在未使用 HOC 的情況下,每當要使用 FruitProduct 元件時,會一起拿到其中的 state 、生命週期、元件的 Method 等等
若今天我們只想要拿元件本身,並不想要其他雜七雜八的東西時,這隻Component 很可能需要再匯出一個 Pure 的版本,如下
可以看到 Pure 這段代碼非常多餘,且所有的東西必須透過外部丟 props 下來才能用,這點與 HOC 相同都是透過 props 取用,但相比之下使用 HOC 不用多餘的代碼,加上前述的可以依照需求掛入的優點,模組化的好處顯而易見!
結論
HOC 是一個非常好用的工具,它具有以下的優點
- 可以減少重複代碼,增加共用
- 更模組化專案的架構
如果還沒使用過的開發者,非常建議你試試,當然它也有一些使用上要注意的問題,上面的情境延伸裡也都提過了,希望這篇文章對你有幫助 ,最後附上文章範例的 Github 。