React VAC 패턴

MyeonghoonNam·2023년 5월 9일
0

시작하며

아래와 같은 코드가 있다. 흔히 컴포넌트를 구성하는 스타일, 비지니스(hook, handler 등), JSX 코드가 하나의 파일에 구성되어있다.

import { useCallback, useState } from 'react';
import styled from '@emotion/styled';

const Count = styled.span`
  display: block;
  font-size: 36px;
`;

const Counter = () => {
  const [value, setValue] = useState(0);

  const handleIncrease = useCallback(() => {
    setValue((prev) => prev + 1);
  }, []);

  const handleDecrease = useCallback(() => {
    setValue((prev) => prev - 1);
  }, []);

  return (
    <div>
      <Count>{value}</Count>
      <button type="button" onClick={handleIncrease} disabled={value >= 5}>
        +
      </button>
      <button type="button" onClick={handleDecrease} disabled={value <= 0}>
        -
      </button>
    </div>
  );
};

export default Counter;

이러한 작은 규모의 컴포넌트의 경우 각 코드의 역할 구분이 어렵지않다. 하지만 이것보다 큰 규모의 컴포넌트가 존재한다면 여러개의 스타일, 비지니스, 렌더링 코드에 의해 가독성이 떨어질 것 이다.

내가 직접 경험한 트러블 슈팅이였고 관심사에 따른 로직 분리에 대해 내가 존경하는 개발자에게 직접 물어보았다.

그 때 리액트의 VAC 디자인 패턴에 대한 키워드를 들을 수 있었고, 여러 자료들을 찾아보며 디자인 패턴에 따른 가독성 향상 효과에 대해 학습할 수 있었고 코드들을 리팩토링 하며 감을 익힐 수 있었다.

그럼 이제 VAC 디자인 패턴에 대해 알아보고 위의 여러 로직이 섞인 일반 코드를 리팩토링해보자.

VAC 패턴

VAC는 View Asset Component의 약자로 오직 렌더링에 필요한 JSX코드와, 스타일 영역을 제어하는 컴포넌트를 뜻한다.

기존 컴포넌트에서 JSX, 스타일 영역을 View 컴포넌트로 분리하고 Props에 비지니스 로직들을 전부 포함시켜 전달함으로써 오직 UI 측면의 렌더링에만 관심을 가지도록 컴포넌트의 역할을 분리하는 것 이다.

View 컴포넌트의 중요한 특징은 다음과 같다.
1. 반드시 Props를 통하여 제어되며 전달받은 Props의 상태 변화에 관여하지 않는 stateless 컴포넌트이다.
2. 조건부 노출, 스타일 제어와 같은 오직 렌더링과 관련된 동작을 수행한다.
3. 이벤트핸들러에 Props에 전달받은 함수를 바인딩 하는 과정에서 어떠한 추가 처리도 하지 않는다.

구현

보다 높은 이해를 위해 시작하며 다루었던 코드를 직접 VAC 패턴으로 변환하여 보자.

// Counter.tsx
import { useCallback, useState } from 'react';

import VCounter from './view';

const Counter = () => {
  const [value, setValue] = useState(0);

  const handleIncrease = useCallback(() => {
    setValue((prev) => prev + 1);
  }, []);

  const handleDecrease = useCallback(() => {
    setValue((prev) => prev - 1);
  }, []);

  const props = {
    value,
    disabledIncrease: value >= 5,
    disabledDecrease: value <= 0,
    onIncrease: handleIncrease,
    onDecrease: handleDecrease,
  };

  return <VCounter {...props} />;
};

export default Counter;


// VCounter.tsx
import styled from '@emotion/styled';

interface Props {
  value: number;
  disabledIncrease: boolean;
  disabledDecrease: boolean;
  onIncrease: () => void;
  onDecrease: () => void;
}

const Count = styled.span`
  display: block;
  font-size: 36px;
`;

const VCounter = ({
  value,
  disabledIncrease,
  disabledDecrease,
  onIncrease,
  onDecrease,
}: Props) => {
  return (
    <div>
      <Count>{value}</Count>
      <button type="button" disabled={disabledIncrease} onClick={onIncrease}>
        +
      </button>
      <button type="button" disabled={disabledDecrease} onClick={onDecrease}>
        -
      </button>
    </div>
  );
};

export default VCounter;

기존 Counter 컴포넌트에서 비지니스 로직만을 담당하는 Counter 컴포넌트와 렌더링과 관련된 View 기능만을 담당하는 VCounter 컴포넌트를 통해 관심사에 맞는 로직들만을 관리할 수 있게 되었다.

이를통해 독립접인 컴포넌트로 재활용성을 높일 수 있는 효과를 얻을 수 있고 협업간에 각각 다른 관심사의 코드를 개발한다면 충돌발생빈도 역시 줄일 수 있을 것으로 기대된다.

profile
꾸준히 성장하는 개발자를 목표로 합니다.

0개의 댓글