Compound Component 複合元件 - React 元件設計模式

alexianalexian
2024-01-29
react前端開發JavaScript

寫程式還要學化學嗎?

作為 React 的開發者,你或許有接觸過 Material UI ,Material UI 是一個 React UI 元件庫,有眾多的基礎元件可以使用,其中有些元件是透過多個 Component 組合而成,例如 toggle button 透過使用 ToggleButtonGroup 與 ToggleButton 作組合實現切換效果,也可以透過在 ToggleButtonGroup 上加入屬性,改變整體的佈局。這種把單一元件切分為父元件與一個或多個子元件,並且共享部分屬性而實現應用功能的模式,就是 Compound Component (複合元件)設計模式。作為 React 基礎元件的設計模式(design pattern)當中一種有趣的設計模式。

原理與實作

要解釋其原理和實作方式,我們分別使用傳統的元件建構方式與複合元件設計模式來表達使用方式的範例,來做個直接的比較:

範例需求

這次的範例需求要建造簡單的 Alert 元件,以下是我的需求:

  1. 使用顏色區塊顯示提示文字內容
  2. 有三種不同的狀態(status):"success" | "error" | "warning” 對應不同的樣式
  3. 最左側可以放置選配的 Icon,根據不同狀態顯示不同 Icon。
  4. 最右側可以放置選配的行動按鈕,且可選擇正面行為與負面行為兩種樣式;

傳統元件建造方式

在傳統 React 元件開發中,通常會通過 props 傳遞數據和配置,元件中依據 props 內容進行條件渲染, props 中包含函式、開關、顯示文案等的內容。以這個方式開發和使用的元件,這邊我統稱為傳統元件的模式。以下是我用傳統 props 模式建構的例子,使用上需要傳入各種 props,雖然只要引入一個元件,卻很難從 JSX 內容中,看出元件的佈局輪廓。

import PropsAlert from "./PropsAlert";

<PropsAlert
  status="success"
  message="This is a success alert"
  onAction={onConfirm}
  actionType="confirm"
  actionText="Confirm"
/>

傳統元件使用方式

複合元件建構方式

接下來我打算把元件拆分為以下架構:

  • 主元件 Alert 只需接受 status props,剩下內容以 children 接受
  • 子元件 AlertIcon
  • 子元件 AlertText
  • 子元件 AlertButton,接受 onClick 與 buttonType

使用 Compound Component 模式建構的使用方式如下,雖然單純看使用方式,會認為其結構較 Props 模式來得更複雜,但相對在 JSX 架構下,可以看出其佈局,提升了可讀性,不再需要猜測 actionText、onAction 和 actionType 在 Alert 元件中的使用方式。

import CompoundAlert, {
  AlertButton,
  AlertIcon,
  AlertText,
} from "./CompoundAlert";

<CompoundAlert status="success">
  <AlertIcon />
  <AlertText>This is a success alert</AlertText>
  <AlertButton buttonType="confirm" onClick={onConfirm}>
    Confirm
  </AlertButton>
</CompoundAlert>

複合元件建造方式

不知道你會不會好奇,我的 AlertIcon 明明沒有傳入 status 的參數,它是如何得知要根據 status 切換為不同的 Icon 呢?其實原理很簡單,就是在子層元件之間共享一組元件狀態。聽起來是不是很熟悉呢?在元件層加入區域狀態的 API ,沒錯,是 context,我加了 context。

是 Context 我加了 Context

透過 context API 在頂層元件建立 Provider,便可以在各子元件中取得預設或是自定義的設定內容,可以說複合元件的本質,就是局部性的 theme provider。

const AlertContext = createContext<Omit<CompoundAlertProps, "children">>({
  status: "success",
});
const useAlertContext = () => useContext(AlertContext);

const CompoundAlert = (props: CompoundAlertProps) => {
  const { status, children } = props;

	// 父元件把 props 傳入 context 
  return (
    <AlertContext.Provider value={props}>
      <div className={`alert ${status}`}>{children}</div>
    </AlertContext.Provider>
  );
};

const AlertIcon = () => {
  // 子元件從 context 取得狀態
  const { status } = useAlertContext();

  switch (status) {
    case "success":
      return <FaCheckCircle size="20" />;
    case "warning":
      return <RiErrorWarningFill size="24" />;
    case "error":
      return <MdCancel size="24" />;
    default:
      return null;
  }
};

範例 CodeSandbox

範例 Repo

使用 Compound Component 複合元件模式的優缺點?

作為一個設計模式,總不可能在所以場景下都適用,必須在適合的場景下使用,才能發揮其優勢,以下是複合元件模式的一些優缺點:

優點一、提高可讀性

這React 使用 jsx 進行開發,其中一個優勢是提高 Layout 的可讀性,使用複合元件模式正好利用這一個特性,也在前述的範例也有表現出來,假如使用 props 方式傳入各部件,需要進入元件才能看出其佈局方式。

優點二、提高重用性與佈局彈性

使用元件化的方式開發,其中一個原因是有較佳的維護性與調整彈性,而使用複合元件模式開發,則可以提供更佳的佈局彈性。假設剛剛的 Alert 元件需要進行調整,例如可以根據使用情境把 Icon 放到最右邊、或是一次需要顯示多個行動按鈕,如果使用 props 模式建構的元件,就需要新增 props,也要注意會不會影響原本使用該元件的頁面,而複合元件底層幾乎無需調整,也就不用擔心影響現有使用的頁面

<p>Icon at right</p>
<CompoundAlert status="success">
  <AlertText>This is a success alert</AlertText>
  <AlertIcon />
</CompoundAlert>

<p>Two buttons</p>
<CompoundAlert status="success">
  <AlertIcon />
  <AlertText>This is a success alert</AlertText>
  <div>
    <AlertButton buttonType="confirm" onClick={onConfirm}>
      Confirm
    </AlertButton>
    <AlertButton buttonType="cancel" onClick={onCancel}>
      Cancel
    </AlertButton>
  </div>
</CompoundAlert>

缺點、過度靈活性,需要使用教學

水能載舟,亦能覆舟。複合元件提供了非常高的使用彈性,但也由於複合元件並沒有強制範定子元件的排序與使用方式,因此入門開發者會對它的使用方式感到困惑,或是不瞭解其原理而傳入錯誤的 props 或資訊。因此建立複合元件後,最好搭配元件開發輔助工具例如 Storybook ,以便於與 UI 或協同開發者訂定該元件使用或樣式的共識。

結論

複合組件(Compound Component)設計模式在 React 應用開發中提供了一種高效且靈活的組件組合方式。通過這種模式,開發者可以創建高度可重用和可定制的組件,同時保持代碼的清晰和易於維護。然而,正如任何設計模式一樣,適當地使用和管理複合組件對於避免潛在的缺點和挑戰至關重要。通过深入理解並恰當運用複合組件,開發者可以在 React 應用中實現更高效和靈活的組件組合策略。

參考資料

  • Photo by Mourizal Zativa on Unsplash
  • MUI toggle button
  • 範例 Repo

上一篇

什麼是語法糖(Syntactic sugar)? - 2024 你要知道的 JS 語法糖 🍭

下一篇

透過 Google Search Console 拯救我的SEO (with Gatsby.js)


Alex Ian
Alex Ian

ReactJS, VueJS 畫畫/寫作/前端開發

Alex Ian
Alex Ian

ReactJS, VueJS 畫畫/寫作/前端開發


共29篇文章
文章分類
  • 前端開發14
  • JavaScript7
  • 那些前端開發應該要知道的小事5
  • Gatsby.js3
  • ChromeExtension2
  • NetlifyCMS2
  • 生產力工具2
  • 關於我1
  • 非Coding1
  • Svelte1
  • raycast1
  • toolRecommend1
  • productivity1
  • MutationObserver1
  • observer pattern1
  • 開發效率1
  • will-change1
  • GitHubCopilot1
  • 生產力1
  • ChatGPT1
  • AI1
  • Copilot1
  • WebAssembly1
  • SEO1
  • GoogleSearchConsole1
  • react1
  • applescript1
  • 閱讀1
  • css1
  • grid1
  • micro-frontend1

Build with GatsbyJS and React 18.2.0. Hosted on Netlify.

The original code is open source and available at calpa/gatsby-starter-calpa-blog

Copyright ©AlexIan's blog 2025.

  1. 從頭開始上架 Chrome extension

    上回提到我們在本地端建好一個開發版本的 Chrome …
  2. Gatsby 部落格更新記錄 1.0

    首先把 Blog 連結貼在最前面:https://alex-ian.me/ 許多工程師 …
  3. Debounce & Throttle - 那些前端開發應該要知道的小事(一)

    前言 也許一開始接觸前端開發的新手們,都有使 …
  4. 使用 GitHub Copilot 一個月心得

    後悔自己為何沒有早點課金 在 Modern Web Conference 2023 觀後感 …
  5. 什麼是 Headless CMS?以探索 Netlify CMS 為例

    小弟的部落格使用 Gatsby.js 框架來架設,作為一個靜 …
  6. Modern Web Conference 2023 觀後感

    https://modernweb.tw/ 前言 Modern Web Conference (MWC) 作為每年度經典的網頁開 …
  7. 什麼是語法糖(Syntactic sugar)? - 2024 你要知道的 JS 語法糖 🍭

    語法糖能吃嗎? 在算數學時,不知道你什麼時候開 …
  8. 閉包 - 那些前端開發應該要知道的小事(三)

    前言 開始這個系列的原因,是因為雖然在程式中 …
  9. Raycast — Mac 提升工作效率的啟動工具

    Mac 的使用者,應該對 Spotlight 這個系統預設的啟動工具 …
  10. 使用 Notion 管理我的年度目標 暨 2023 回顧 & 2024 展望

    都進入 2024 才來展望 想要成為流量的奴隸,自然要 …
  11. 慢半拍來初嚐 Svelte

    Svelte 都出來兩年多的框架現在才來玩,有夠慢半拍 …
  12. 從頭開始學習開發 Chrome extension (v3 版本)

    Chrome extension 經歷過 v1, v2 後,來到了 v3,雖然曾小量接觸 v2 …
  13. 留言簿

    歡迎留言
  14. addEventListener的第三個參數 - 那些前端開發應該要知道的小事(二)

    前言 開始這個系列的原因,是因為雖然在程式中 …
  15. 在中大型企業下微前端的實現與挑戰

    我們先聊工程面,人的部分先不要 小弟任職前端 …
  16. will-change — 那些前端開發應該要知道的小事(七)

    你要做動畫你要先說啊 前端開發少不免會需要 …
  17. 關於我

    關於我 我是Alex Ian,是個小小的前端工程師,出來工 …
  18. 開發效率提升 - 操作鍵盤 & 快捷鍵篇

    我國中的電腦課,會教導學生倉頡和速成輸入法 …
  19. macbook-pro(retina) late2012 換電池記錄

    這篇文章其實與Coding無關,主要是記錄mbp換電池的 …
  20. polyfill是什麼?

    人在江湖,身不由己,在接案的過程中,總有一些你 …
所有結果