Design Pattern In React — Compound component (複合元件)
相信大家對 design pattern (設計模式) 這個詞並不陌生,並在聽到的當下可能腦中會浮現一些較為人所知的 pattern,例如 Singleton、Factory、Proxy…等。而程式開發中所謂的 pattern ,則代表著 “經過開發者反覆的使用與測試,被大多數人知道與認同最終被歸類的程式開發經驗總結。” pattern 所帶來的好處有使測試更容易、程式碼更簡潔與易維護、理解該 pattern 的共同開發者可以快速接手專案…等。然而什麼時機下需要使用何種 design pattern 卻是需要經過慎重衡量的,要是在錯誤的狀況下使用錯誤的 pattern,對於開發與維護可能是帶來反效果的。
Design Pattern In React
隨著近幾年 web app 漸趨複雜,前端的程式碼也隨之變得越來越複雜與龐大,因此也更需要注重程式碼的可維護性與可擴展性。React 的開發是建立在 component 這個基礎上的,整個頁面是由大大小小的 component 所組成,因此社群中發展出了一些著重在 component 上的 design pattern,希望能提升 component 在開發上與使用上的易用性與維護性。
Compound Component 複合元件
所謂的 compound component 可以理解為兩個或兩個以上的 component 結合成一個擁有特定功能或是能夠達成特定任務的 component,通常會有一個 component 為 parent,其他則為 children,而當其中一個 component 單獨存在時,是沒有意義的。以下舉一個純 HTML 元素的例子:
<select> <option value="value1">key1</option> <option value="value2">key2</option></select>
這是大家應該都很熟悉的選擇元件,如果只有 select 而沒有 option,或是只有 option 存在,外面沒有被 select 包覆,這樣的語法都是沒有意義的,這樣應該可以清楚理解上面提到的 compound component 的特性了吧。
參考 Kent C 的文章 ,如果要做一個可以 toggle 的 button,也就是可以依據 state 是 on 或是 off 來顯示不同的文字或樣式,你會怎麼做?
你可能會想到利用 props 傳入不同的文字來決定在不同狀態時要顯示什麼。
然而這樣子的做法卻有幾個明顯的缺點:
- 顯示順序不易控制
- props 越來越多時易造成混亂
這時透過 compound component 則可以輕鬆解決這些問題。compound component 主要是將要渲染的 UI 以 props.children 的方式傳入父層元件,父層元件提供 state 供子元件做使用,對於使用者來說,他只需要傳入想要的 子元件,不用知道父層與子層之間如何溝通,顯示的順序更可以輕易按自己意志調換,父子元件達成隱含狀態的共享(implicit state)。
在過去 class component 主宰的時代,要實現 compound component 大多靠 React.cloneElement 這個方式,但現今 react 已經進入 hook 時代,我們就應該學習用較新的方式去達成ㄧ樣的設計模式,於是主角登場啦 — Context API!
Parent component 的工作即是 context provider,並把子元件會用到的 state 帶進 provider 中, 最後 return 包住 props.children 的 Context Provider 就 ok 了,如此一來子元件的內容是完全依照使用者的需求去塞入的,當需要特定的 state 來做 UI 的邏輯判斷時再去跟 context 拿來用就好囉,這樣的 API 設計明顯比第一種方法好上許多吧!
程式碼實作
我參考 Kent C 的文章做了點修改,並且使用 TypeScript 實作一個簡單的透過按鈕可以切換兩種背景顏色的範例:
這邊也就是一些基本的 React 應用就不多去解釋,值得一提的是這兩行
ToggleElement.Container = Container;ToggleElement.Btn = Btn;
這樣的寫法是不強制的,但我個人偏好將 child component 存成 parent component 的 static property,會提高整體的易讀性。
使用上就會長這個樣子
剛剛也提及了使用 compound component 是有可以自行決定 children UI 顯示順序或次數這個優勢的,如果你嫌一個 btn 不夠,你可以改成
四個擁有一樣功能的按鈕就出現了呢!
(當然這是最簡單的示例,你可以再去對元件做任何想要的擴展。)
結語
本篇介紹了 Compound Component 的概念,並透過一個簡單與粗陋(廢話,看 code 跟成品就知道有多粗陋了)的範例解釋 Compound Component 的實作方式與優勢。然而還是要謹記文章開頭提及的:“設計模式需要在適當的情況下才能發揮作用”,因此在選擇應用某個 design pattern 前一定要謹慎評估當前的狀況適不適合使用該設計模式。