Published on

변경에 유연한 컴포넌트 만들기

변경에 유연한 컴포넌트 만들기

변경에 유연한 컴포넌트를 만들기 위해서는 컴포넌트를 적절하게 분리해야 한다.
이를 위한 방법으로 다음 세가지를 정리한다.

  1. Headless 기반의 추상화하기 (hooks로 모듈화하기)
    변하는 것 VS 상대적으로 변하지 않는 것

  2. 각 컴포넌트는 한 가지 역할만 하고 합성(Composition)으로 컴포넌트를 조합하기

  3. 도메인 분리하기 (도메인을 포함하는 컴포넌트와 그렇지 않은 컴포넌트 분리하기)


1. Headless 기반의 추상화하기

아래의 예제에서 hooks로 모듈화하여 데이터 추상화와 동작 추상화를 하였다.

[데이터 추상화]

export default function Calendar() {
  const { headers, body, view } = useCalendar();

  return (
    <Table>
      <Thead>
        {headers.weekDays.map(({ key, value }) => {
          return <Th key={key}>{format(value, 'E', { locale })}</Th>;
        })}
      </Thead>
      <Tbody>
        {body.value.map(({ key, value: days }) => (
          <Tr key={key}>{days}</Tr>
        ))}
      </Tbody>
    </Table>
  );
}

[동작 추상화]

export function PressButton(props: Props) {
    const longPressProps = useLongPress();

    return <Button {...longPressProps} {...props} />
}

function useLongPress() {
    return {
        onKeyDown={e => {
            // ...
        }}
        onKeyUp={e => {
            // ...
        }}
        onMouseDown={e => {
            // ...
        }}
        onMouseUp={e => {
            // ...
        }}
    }
}

2. 각 컴포넌트는 한 가지 역할만 하고 합성(Composition)으로 컴포넌트 조합하기

아래의 framework select UI를 구현하기 위해서 필요한 컴포넌트들이 각각 한가지 역할만 하고, 서로 합성을 통해 연결될 수 있도록 설계한다.

아래 예제에서 Select와 trigger로 전달된 InputButton은 서로의 존재에 대해 몰라도 된다. (커플링이 약하다.)
이렇게 합성 가능 하도록 설계하고, 각 컴포넌트들이 한 가지 역할만 하도록 한다면 각 컴포넌트가 변경에 유연해진다.

function Select({ label, trigger, value, onChange, options }: Props) {
  return (
    <Dropdown label={label} value={value} onChange={onChange}>
      <Dropdown.Trigger as={trigger} />
      <Dropdown.Menu>
        {options.map((option) => (
          <Dropdown.Item>{option}</Dropdown.Item>
        ))}
      </Dropdown.Menu>
    </Dropdown>
  );
}

function FrameworkSelect() {
  const {
    data: { frameworks },
  } = useFrameworks();
  const [selected, change] = useState();

  return (
    <Select
      trigger={<InputButton value={selected} />}
      value={selected}
      onChange={change}
      options={frameworks}
    />
  );
}

3. 도메인 분리하기 (레이어링)

위에서 본 예제에서 FrameworkSelect는 데이터에 접근하며 비즈니스 로직을 가지고 있다. (도메인을 포함한다.)
반면에 Select는 도메인을 모른다.
따라서 FrameworkSelect에서 비즈니스 로직은 스스로 처리하되 UI로직은 위임하고 있다.

이렇게 도메인 로직을 가진 컴포넌트를 분리하는 것은 변경에 유연하게 대응할 수 있도록 한다.


컴포넌트를 분리하는 이유

컴포넌트를 분리하는 이유는 다음과 같다.

  1. 컴포넌트를 분리하여 복잡도를 낮춘다.
  2. 재사용 가능한 컴포넌트를 만든다.

컴포넌트를 분리할때는 위의 이유중 어떤 이유로 분리하는지 잘 생각해보자.
또한 컴포넌트를 분리하여 만들때는 내부 구현보다 컴포넌트의 인터페이스를 먼저 생각해보면 설계에 도움이 된다.


참조