Compound Pattern

前言

大家好,我是 Johnny,今天要紀錄分享的是 Patterns 筆記系列的 Compound Pattern

介紹

Compound Pattern 是透過組合多個組件、元件來完成一個整體功能,常常在 select, dropdown, menu 等功能看到這個 pattern 的應用,底下以一個 React Dropdown 功能當作範例

React Dropdown

使用 useContext 來製作跨組件之間的局部狀態共享,把狀態封裝在局部組件當中,相關組件拿取共同的狀態,但組件邏輯又可獨立進行編寫

import React, { useState, useContext } from 'react';

const DropdownContext = React.createContext();

const Dropdown = (props) => {
  const [open, toggle] = useState(false);

  return (
    <DropdownContext.Provider value={{ open, toggle }}>
      {props.children}
    </DropdownContext.Provider>
  );
};

const Toggle = (props) => {
  const { open, toggle } = useContext(DropdownContext);

  return (
    <div onClick={() => toggle(!open)}>
      {props.children}
    </div>
  );
};

const Content = (props) => {
  const { open } = useContext(DropdownContext);

  return open && props.children;
};

Dropdown.Toggle = Toggle;
Dropdown.Content = Content;

export default Dropdown;

如上我們構建一個 Compound 的 Dropdown,並可快速調用該功能相關聯的組件,不僅使用方便,且功能狀態完全封裝在 Compound 組件中了,不會污染到使用的組件環境

const App = () => {
  return (
    <>
      Hello
      <Dropdown>
        <Dropdown.Toggle>
          toggle
        </Dropdown.Toggle>
        <Dropdown.Content>
          <ul>
            <li>item 1</li>
            <li>item 2</li>
          </ul>
        </Dropdown.Content>
      </Dropdown>
    </>
  );
};

評價 Compound Components

這麽寫的好處是

  • 功能只需要 import 一個組件,相關的組件已經全部包含在內
  • 不需要另外維護許多功能相關狀態

React.Children.map

另一個實現方式是透過 React.Children.map 結合 cloneElement,將我們的功能狀態透過改寫 children 的 props 完成

import React, { useState } from 'react';

const Dropdown = (props) => {
  const [open, toggle] = useState(false);

  return (
    <>
      {React.Children.map(props.children, child =>
        React.cloneElement(child, { open, toggle })
      )}
    </>
  );
};

const Toggle = ({ open, toggle, children }) => {
  return (
    <div onClick={() => toggle(!open)}>
      {children}
    </div>
  );
};

const Content = ({ open, toggle, children }) => {
  return open && children;
};

Dropdown.Toggle = Toggle;
Dropdown.Content = Content;

export default Dropdown;

寫法上雖然避免掉了使用 useContext 以及 Provider,變得更為精簡,但也有一個缺點,因為是直接傳遞 props 給 children,狀態只會傳遞到 Dropdown 組件內的第一層,如下用法就會出現問題,並且 props 是 shallow merge,如果出現名稱衝突將會被覆蓋導致問題

const App = () => {
  return (
    <>
      Hello
      <Dropdown>
        {/* This breaks */}
        <div>
          <Dropdown.Toggle>
            toggle
          </Dropdown.Toggle>
          <Dropdown.Content>
            <ul>
              <li>item 1</li>
              <li>item 2</li>
            </ul>
          </Dropdown.Content>
        </div>
      </Dropdown>
    </>
  );
};

結論

總結 Compound Pattern 有下列優點

  • 功能狀態的隔離封裝
  • 功能組件的完整性,同一功能的相關組件可以明確定位劃分
  • 使用方便,減少重複撰寫類似功能的邏輯 缺點如下
  • 狀態、邏輯被封裝,無法快速看出具體組件內做了什麼

綜合評斷下來,Compound Pattern 適用在目標明確、單一的功能上,避免將過多不相關的邏輯狀態全部放在一個 Compound 中,使用上就較不容易造成功能模糊不清、開發者難以理解的狀況

感謝收看,下一篇見拉~

Last Updated:
Contributors: johnnywang