WIL 4주차 - 컴포넌트 설계 패턴

컨스탄트·2026년 7월 12일

Loopers

목록 보기
4/8

4주차는 컴포넌트 설계패턴과 관련된 발제로 멘토님께서 진행해주셨다.

같은 로직인데 UI가 완전히 다른곳이 2곳이상 : Headless
props가 늘고, 사용처가 구조를 바꾸고 싶다 : Compound
open/value를 안에서도 밖에서도 : Controlled / UnControlled
어디서든 소환해야 (알림, 확인창) : Provider vs Singleton
단순 UI, props 3개 이하 : 그냥 props (패턴 x)

사실 위의 내용을 프론트엔드 공부를 하면서 들어보았지만 이번 발제를 통해 다시 생각해볼 수 있는 계기가 되었다.

특히 Headless! 나는 Headless 형태가 그냥 밑에처럼 컴포넌트 조각들로 제공해주는 방법만 알고 있었다.

<Select>
  <Select.Trigger className="my-trigger">
    상품 선택
  </Select.Trigger>

  <Select.Options className="my-options">
    <Select.Option value="apple">Apple</Select.Option>
    <Select.Option value="banana">Banana</Select.Option>
  </Select.Options>
</Select>

근데 커스텀 훅으로 제공을 해주는 케이스도 있었다.

function useSelect() {
  const [isOpen, setIsOpen] = useState(false);
  const [value, setValue] = useState<string | null>(null);

  return {
    isOpen,
    value,
    triggerProps: {
      onClick: () => setIsOpen((prev) => !prev),
      "aria-expanded": isOpen,
    },
    getOptionProps: (option: string) => ({
      onClick: () => {
        setValue(option);
        setIsOpen(false);
      },
      "aria-selected": value === option,
    }),
  };
}
const select = useSelect();

return (
  <div>
    <button {...select.triggerProps}>
      {select.value ?? "선택하세요"}
    </button>

    {select.isOpen && (
      <ul>
        <li {...select.getOptionProps("apple")}>Apple</li>
        <li {...select.getOptionProps("banana")}>Banana</li>
      </ul>
    )}
  </div>
);

또 한가지 배운점은 SingleTon!
모듈에서 하나의 전역 인스턴스를 만들어 두고, React 컴포넌트가 아닌 곳에서도 직접 호출하는 방식이다.

type ToastListener = (message: string) => void;

const listeners = new Set<ToastListener>();

export const toast = {
  success(message: string) {
    listeners.forEach((listener) => listener(message));
  },

  subscribe(listener: ToastListener) {
    listeners.add(listener);

    return () => {
      listeners.delete(listener);
    };
  },
};

Provider 방식과 다른점은 Provider는 리액트 트리 내에서 Provider 내부에서만 사용을 할 수 있는데 SingleTon은 리액트 트리와 무관없이 다음과 같이 호출을 할 수 있다.

export async function request<T>(url: string): Promise<T> {
  const response = await fetch(url);

  if (!response.ok) {
    toast.error("요청 처리에 실패했습니다.");
    throw new Error("Request failed");
  }

  return response.json();
}

다시 한번 컴포넌트 설계 패턴을 정리하고 필요할 때 적절한 방법을 선택하는게 중요하겠다고 생각을 하였다.

profile
꾸준히 개발을 즐기는 사람입니다

0개의 댓글