STUDY/REACT

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

bottlesun 2022. 12. 31. 22:19

복합 구성요소 패턴(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