스타일과 로직을 제대로 분리해보자. VAC 패턴!

강경석(핸디)·2021년 8월 26일
8

Design-pattern

목록 보기
1/1

시작

업무외로 개인프로젝트를 시작하며 잘 안되는 디자인적 재능을 가지고 컴포넌트에 아름다움을 입히고 있었습니다.
그리고 완성된 것을 보니 감탄을 금할 수 없었습니다. 끔찍했거든요.
그래서 디자이너 친구에서 조언을 구하고 컴포넌트를 전체적으로 리펙토링하며 디자인을 입히고 있었습니다.

문제 발생

그러던 중 답답한 친구가 자기도 같이 해보겠다라고 하여 냉큼 권한을 줬는데..

계속해서 conflict가 발생하더군요.

이유는 바로 react 개발 트렌드에 있었습니다. 저는 CSS-in-JS를 좋아합니다.

컴포넌트와 디자인이 한파일내에 있기때문에 적절히 컴포넌트를 분리한다면 한눈에 파악할 수 있기 때문입니다.

그래서 작은 컴포넌트는 CSS-in-JS로 디자인을 입히고 여러 컴포넌트가 결합되는 Page는 scss로 약간의 디자인을 더할 뿐입니다.

혼자 일할때는 이것만큼 좋은게 없습니다. 하지만 위에서 말씀드렸다시피 협업을 하게 되니 문제가 발생했습니다.

VAC pattern에 대한 필요성

컴포넌트와 CSS가 한곳에 있다 보니 점점 디자인과 컴포넌트 개발하는데 힘들었고, 나중에 컴포넌트를 추가하고 리펙토링할때도 번잡스러워졌습니다.

그런 와중에 VAC 패턴에 대한 글을 읽어보게되었습니다.

VAC pattern

VAC pattern 이란?

VAC는 View Asset Component의 약자로 렌더링를 관리하는 JSX와 스타일을 관리하는 컴포넌트를 의미합니다. 또한 UI와 비지니스 로직에서 분리된 컴포넌트이기도 합니다.

VAC 패턴에 대해 깊게 들어가기 전에 저의 개발 순서를 한번 살펴보겠습니다.
일반적으로 저의 개발 순서는 다음과 같습니다.

  1. 일단 동작하는 컴포넌트를 만든다. JSX, style, business logic이 혼합됨
  2. 완성된 동작을 확인하고 한 숨을 쉰 뒤 리펙토링을 진행한다.
  3. business logic를 제거할 수 있으면 제거한다.(주로 한 곳에서 모아서 관리하는 쪽으로 뺀다)
  4. 공통 스타일 관리 및 css를 최적화하거나 이쁘게 바꾼다.

저는 저의 실력에 대한 아직 확고한 믿음이 없기때문에 동작을 확인한다음에 리펙토링을 하는 것을 선호합니다. 그래서 최대한 고민하면서 구현하되 항상 리펙토링의 가능성을 생각합니다.

그리고 여기서 VAC 패턴은 마지막 4단계 공통 스타일 관리 및 CSS를 다시금 리펙토링하여
JSX와 Style를 분리시켜 별도의 View 컴포넌트내에서 디자인을 처리하자 라는 것이 제 의견입니다.

VAC pattern의 특징

다시 문서를 살펴보면 VAC에는 다음과 같은 특징이 있습니다.

  1. 반복이나 조건부 노출, 스타일 제어와 같은 렌더링과 관련된 처리만을 수행합니다.
  2. 오직 props를 통해서만 제어되며 스스로의 상태를 관리하거나 변경하지 않는 stateless 컴포넌트입니다.
  3. 이벤트에 함수를 바인딩할 때 어떠한 추가 처리도 하지 않습니다.

구현 예제

버튼 2개를 가지고 값을 증감시킬수 있는 컴포넌트가 있습니다.

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

  return (
    <div>
      <button onClick={() => setValue(value - 1)}>-</button>
      <span>{value}</span>
      <button onClick={() => setValue(value + 1)}>+</button>
    </div>
  );
};

Reactoring 시작~~~

Props Object 생성

Props Object는 Props, State를 가지는 객체로 하위 View Component(VAC)에 그대로 넘겨주는 역할을 합니다.

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

  // Props Object
  const SpinBoxProps = {
    value,
    onDecrease: () => setValue(value - 1),
    onIncrease: () => setValue(value + 1),
  };

  // Rendering은 이제 VAC에서 담당합니다.
  return <div></div>;
};

VAC로 분리

일단 VAC를 만듭니다.

const SpinBoxView = ({ value, onIncrease, onDecrease }) => (
  <div>
    <button onClick={onDecrease}>-</button>
    <span>{value}</span>
    <button onClick={onIncrease}>+</button>
  </div>
);

그리고 기존 컴포넌트에 VAC로 바꿔끼웁니다.

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

  const SpinBoxProps = {
    value,
    onDecrease: () => setValue(value - 1),
    onIncrease: () => setValue(value + 1),
  };

  // JSX를 VAC로 교체
  return <SpinBoxView {...SpinBoxProps} />;
};

props object를 선언하는 방법은 여러가지입니다. 하지만 저는 react + typescript 조합을 쓰는데 인터페이스를 선언할때 OOOOProps를 선언하기 때문에 요런식으로 작성해줬습니다.

디자이너와 협업하기

이제 VAC 패턴을 도입하여 협업을 진행해보겠습니다.
컴포넌트 로직과 디자인이 분리되었기 때문에 아래와 업무가 분리되었습니다.

  • 로직을 추가한다 --> SpinBox에서 SpinBoxProps에 추가작업 한다
  • 디자인을 추가한다 --> SpinBoxView에서 디자인을 추가작업 한다.

CSS-in-JS를 좋아하는 react 덕후답게 디자이너에게 당당하게 css로 말고 styled-component 또는 makeStyle로 해달라고 요구합니다(저는 MUI를 좋아하기 때문에 makeStyle를 쓰기도 합니다)

로직을 추가한다

만약 Box의 값이 0~100사이에서만 동작해야한다고 요구가 들어왔습니다.
기존이라면 아무런 고민없이 컴포넌트에 onClick에서 처리하던 setValue 내부에서 처리하던 상관없었습니다.
하지만 이젠 VAC 패턴을 적용하였기 때문에 onClick은 건드리면 안됩니다.
onClick은 VAC의 영역이지 컴포넌트 로직이 침범할 곳이 아니기 때문입니다. 우리가 줄 수 있는건 Props Object밖에 없습니다. 따라서 setValue를 처리해야합니다.

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

  // Props Object <- 여기에다 추가
  const SpinBoxProps = {
    value,
    onDecrease: () => setValue(Math.max(value - 1, 0)),
    onIncrease: () => setValue(Math.min(value + 1, 100)),
  };

  // Rendering은 이제 VAC에서 담당합니다.
  return <div></div>;
};

디자인을 추가한다

버튼의 배경색을 빨간색으로 해달라는 요구사항이 들어왔습니다.
MUI의 makeStyles를 이용해 처리해봅시다.

const useStyles = makeStyles((theme: Theme) => ({
  btn: {
    backgroundColor: "red",
  },
}));

const SpinBoxView = ({ value, onIncrease, onDecrease }) => {
  const classes = useStyles();
  return (
    <div>
      <button onClick={onDecrease} className={classes.btn}>
        -
      </button>
      <span>{value}</span>
      <button onClick={onIncrease} className={classes.btn}>
        +
      </button>
    </div>
  );
};

보시다시피 개발자는 O컴포넌트, 디자이너는 OView컴포넌트만 신경쓰면 되는 분리가 일어났습니다.

마치면서

디자인 패턴의 세계는 무궁무진합니다. 어느순간 새롭게 생겨나고 사라지지만 사람들에게 유용하다가 느껴지는 순간 바이블처럼 남아있기도 합니다.
그 중에 프론트엔드의 디자인 패턴은 아직 바이블처럼 고정된 패턴이 없다고들 합니다.
최근 들어와서 SPA가 대세가 되고 react, vue 등이 주류가 되면서 이를 토대로 하는 디자인패턴들이 나오고 있다고 생각합니다.
따라서 VAC 또한 어느날 사라진 패턴일 수 도 있습니다. 하지만 이런 고민들과 토론들이 쌓여 바이블과 같은 디자인패턴을 만들어내리라 믿어 의심치 않습니다.

저 또한 VAC를 접하기전에 VAC와 같은 개념을 도입하고자 했었습니다.
사이드메뉴에 stateless한 컴포넌트를 주고 디자인과 state 변경 logic만 주는 구조로 말입니다.
그리고 이렇게 VAC 패턴이라는 개념을 접하니 그때 했던 고민들이 참 값진 경험이었구나라는 생각이 듭니다.

앞으로 있을 새로운 디자인패턴이 어떤 모습, 어떤 개념, 어떤 목표를 담고 올지 모르겠지만 저에게 이로움을 주는 패턴이길 기대해봅니다. ㅎㅎ

참고자료

profile
frondend dev

0개의 댓글