본문 바로가기
STUDY/REACT

복합 컴포넌트 패턴(Compound Components Pattern) | 리액트 디자인 패턴

by bottlesun 2022. 12. 31.
728x90

복합 구성요소 패턴(Compound Components Pattern) 이란?

프롭 드릴링(prop drilling) 없이 하나의 컴포넌트를 구성하는 State를 공유하도록 작성하는 컴포넌트 패턴이다. view를 구성하는 코드에 커스터마이징이 용이하다는 장점이 생긴다.

import React, { useState } from "react";

const Test = () => {
  const [_value, _setValue] = useState("value2");
  const optionChangeValue = (event) => {
    _setValue(event.target.value);
  };

  return (
    <div>
      <h3>{_value}</h3>
      <select value={_value} onChange={optionChangeValue}>
        <option value="value1">Label1</option>
        <option value="value2">Label2</option>
        <option value="value3">Label3</option>
      </select>
    </div>
  );
};

export default Test;

이처럼 서로 상태를 공유하며 내부적인 결합이 되어 있는 컴포넌트를 의미한다고 한다.

components

// components/count.jsx
import React from "react";
import { useCounterContext } from "../useCounterContext";

const Count = ({ max }) => {
  const { count } = useCounterContext();
  const MaximemCount = max <= count ? max : count;
  return <div>{MaximemCount}</div>;
};

export { Count };

// components/button.jsx
import React from "react";
import { useCounterContext } from "../useCounterContext";

const Button = ({ value }) => {
  const { handleDecrement, handleIncrement } = useCounterContext();
  const isClickChange = value === "-" ? handleDecrement : handleIncrement;
  return <button onClick={isClickChange}>{value}</button>;
};

export { Button };

// components/index.jsx
export * from "./button";
export * from "./count";

Counter.jsx

//Counter.jsx
import React, { useEffect, useRef, useState } from "react";
import { CounterProvider } from "./useCounterContext";
import { Button, Count } from "./components";

const Counter = ({ children, onChange, initialValue = 0 }) => {
  const [count, setCount] = useState(initialValue);

  const firstMounded = useRef(true);
  useEffect(() => {
    if (!firstMounded.current) {
      onChange && onChange(count);
    }
    firstMounded.current = false;
  }, [count, onChange]);

  const handleIncrement = () => {
    setCount(count + 1);
  };

  const handleDecrement = () => {
    setCount(Math.max(0, count - 1));
  };

  const styles = {
    display: "flex",
    borderRadius: "0.25rem",
    gap: "5px",
    height: "20px",
    alignItems: "center",
    justifyContent: "center"
  };

  return (
    <CounterProvider value={{ count, handleIncrement, handleDecrement }}>
      <div style={styles}>{children}</div>
    </CounterProvider>
  );
}

Counter.Button = Button;
Counter.Count = Count;

export { Counter };

Usage.jsx

//Usage.jsx
import React from "react";
import { Counter } from "./Counter";

const Usage = () => {
  const handleChangeCounter = (count) => {
    console.log("count", count);
  };

  return (
    <Counter onChange={handleChangeCounter}>
      <p>Count</p>
      <Counter.Count max={10} />
      <Counter.Button value={"+"} />
      <Counter.Button value={"-"} />
    </Counter>
  );
};

export default Usage;

컴포넌트 layout 변경

    <Counter onChange={handleChangeCounter}>\\
      <Counter.Button value={"+"} />
      <p>Count</p>
      <Counter.Count max={10} />
      <Counter.Button value={"-"} />
    </Counter>

장점

  • 컴포넌트의 복잡도를 낮출 수 있고, 매우 직관적으로 사용이 쉽다.

하나의 컴포넌트 안에서 State를 공유하여 사용하는 형태로, 단순한 구조를 만들 수 있다.

  • 컴포넌트의 각 역할마다 명확한 분리를 할 수 있어서 재 사용성이 높아 유지보수가 수월하다.
  • 하위 컴포넌트의 위치나 정렬에 있어 커스터마이징을 할 수 있는 유연성을 가질 수 있다.

단점


[참고자료]

5 Advanced React Patterns

프롭 드릴링(prop drilling)

프로퍼티 내리꽂기(또는 나사 구멍 내기)는 리액트 의 컴포넌트 트리에서 데이터를 전달하기 위해,

상위 컴포넌트 에서 필요한 컴포넌트 로 값을 계속 내리는 것을 의미한다.

728x90

댓글