컴파운드 컴포넌트 패턴

임동·2024년 5월 17일

서론

매일 하는 팀미팅을 진행하면서 한 가지 단어를 알게 되었다.
디자인 패턴이라는 단어이다.

디자인 패턴이 뭐지? 디자인 구조를 잘 잡기 위한 디자인의 기술 중 하나인가? 라는 생각이 들었고 바로 구글링을 해보았다.

디자인 패턴은 "기존 환경 내에서 반복적으로 일어나는 문제들을 어떻게 풀어나갈 것인가에 대한 일종의 솔루션"이라고 한다.

그리고 디자인 패턴은 자바스크립트에서만 활용하는 것이 아닌 다른 프로그래밍 언어에서도 다양한 디자인 패턴이 있었고, 그 중에서 React 디자인 패턴에 대해 검색해보았다.

React에서 많이 사용하는 디자인 패턴 중 하나는 Compound Components 패턴이라고 한다. Compound Components 패턴은 기존에 사용하던 컴포넌트에 Prop을 내려주던 방법과는 다른 생소한 방법이라 현업에서 사용하는 지 멘토님께 여쭤보았고 멘토님의 대답은 자주 사용하는 패턴이라 하여 Compound Components 패턴에 대해 알아보고 얕게나마 사용해보았다.

Compound Components 패턴

단어가 너무 길어서 컴파운드 패턴이라고 말하겠다.

컴파운드 패턴은 "하나의 컴포넌트를 여러 가지 집합체로 분리한 뒤 분리된 각 컴포넌트에서 사용하는 쪽에서 조합해 사용하는 방식"이라고 한다.

말만 들으면 너무 어렵다..
그래서 팀원 분께 여쭤보았고, 아토믹 패턴을 참고하면 좋다고 말씀하셨다.

아토믹 패턴

아토믹 패턴을 딥하게 들어가지 않기 위해 개념만 말하면 코드의 가독성과 확장성을 위해 사용하는 디자인 패턴이며 원자, 분자, 유기체, 템플릿, 페이지로 나누어져 있다.
1. 원자: 제일 작은 단위를 말하며 button, label, input과 같은 기본적인 HTML 태그들을 말한다.
2. 분자: label, input, button이 모여서 form을 만드는 것을 분자라고 한다.
3. 유기체: 검색 분자, 메뉴 분자 같은 기능들이 모아져서 만들어진 것을 유기체라 한다.
4. 템플릿: 유기체들이 모인 상태들이며 아직 디자인이나 사용자 관점에서 개선되지 않은 상태를 말한다.
5. 페이지: 템플릿을 사용자 관점에서 ui, ux, 기능들을 개선시킨 것을 말한다.
아 그럼 컴파운드 패턴은 작은 원자 단위로 컴포넌트를 나누고 레고 같이 컴포넌트를 조합해 나가는 방식이구나 라는 생각이 들었다.

기존 문제점

기존에 사용하던 코드 작성법은 컴포넌트에 prop을 전달해주고 그 값을 받아서 화면을 구성하는 방법을 사용했었다.

하지만 이런 방법은 prop의 수가 적다면 괜찮겠지만 전달 받아야할 prop의 수가 많아질수록 가독성이 안 좋아지고, 컴포넌트가 무거워지게 된다는 단점이 있다.

  <form>
    <Label htmlFor="username">Username:</Label>
    <Input
      id="username"
      type="text"
      value={username}
      onChange={(event) => setUsername(event.target.value)}
    />
  </form>

Label과 Input이라는 컴포넌트를 만들고 코드의 재사용성을 위해 id, type, value, onChange라는 데이터를 prop으로 받는 코드이다.

지금은 괜찮지만 넘겨야할 prop이 많아질수록 가독성과 유지보수하기 힘든 상황이 생길 것이다.

개선 코드

컴파운드 패턴은 최상위 컴포넌트에서 context로 데이터를 관리하고 하위 요소에게 전달되는 형식으로 진행된다.

const FormContext = createContext();

export function Form({ children }) {
  const [inputValue, setInputValue] = useState({
    id: "",
    password: "",
  });
  
  const setValue = (name, value) => {
    setInputValue({
      ...inputValue,
      [name]: value,
    });
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(inputValue);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <FormContext.Provider value={{ inputValue, setValue }}>
        {children}
      </FormContext.Provider>
    </form>
  );
}

function FormInput({ type, id }) {
  const { inputValue, setValue } = useContext(FormContext);
  const handleInput = (e) => {
    setValue(id, e.target.value);
  };
  return (
    <input
      id={id}
      type={type}
      value={inputValue[id]}
      onChange={handleInput}
    />
  );
}

function FormLabel({ htmlFor, children }) {
  return (
    <label htmlFor={htmlFor}>
      {children}
    </label>
  );
}

Form.Input = FormInput;
Form.Label = FormLabel;

로그인 폼을 만드는 예제이고, id Input, password Input을 만들기 위해 코드를 추가했다.
재사용성을 위해 아토믹 패턴에서 봤던 분자 단위로 모든 컴포넌트를 나누어서 코드를 작성한다.

import { Form } from "./compound";

export default function Compound() {
  return (
    <Form>
      <Form.Label htmlFor="id">ipnut : </Form.Label>
      <Form.Input type="text" id="id" />
      <Form.Label htmlFor="password">password : </Form.Label>
      <Form.Input type="password" id="password" />
    </Form>
  );
}

위와 같은 코드로 이전 코드에서 export 했던 Form을 불러오고 코드를 작성하면 가독성이 기존 코드 작성 방법보다 가독성이 향상된다는 장점이 있다.

prop을 사용하긴 하지만 prop 활용의 수가 더 적어질 수 있다.

단점

컴파운드 패턴의 좋은 부분만 보여줬지만 무조건 긍정적인 효과만 주는 것이 아니다.

컴파운드 패턴의 주요 장점으로 가독성을 말했지만 Form 컴포넌트의 내부를 볼 때 전체를 다 봐야하기 때문에 사람에 따라 가독성이 떨어질 수 있다는 단점이 있다.

그리고 코드의 양이 기존보다 많아지고, 구현 난이도가 올라가기 때문에 작업 속도가 느려질 수 있다는 단점이 있다.

결론

항상 prop을 내려주는 방법으로 코드를 작성했었다.
이번에 컴파운드 패턴을 알아보고, 사용해 보면서 코드 작성의 고정관념에 갇혀 공부하고 있다고 생각이 들었다.

그리고 현업에서도 자주 사용하는 방법이라고 하니 지금부터라도 사용을 해보는 습관을 갖고 한 가지 방법의 코드 작성이 아니라 다른 방법도 찾아보고 상황에 맞게 사용해야겠다!

profile
FRONTEND DEV.

0개의 댓글