[디자인패턴] VAC 패턴

정수현·2023년 3월 9일
8

React

목록 보기
6/8
post-thumbnail

VAC 패턴이란?

View Asset Component

  • HTML과 거의 유사한 뷰(View) 컴포넌트와 비즈니스 로직(Business Logic)만 담고 있는 컴포넌트를 만드는 것으로 역할을 완전히 분리하는 것
  • 모든 컴포넌트를 뷰 파일과 로직 파일로 분리한다는 것이 특징

비즈니스 로직(Business Logic)과 뷰(View)

비즈니스 로직(Business Logic)

컴퓨터 프로그램에서 실세계의 규칙에 따라 데이터를 생성, 표시 저장, 변경하는 부분을 일컫는다.

뷰(View)

데이터를 표시만 해주는 것. 화면에 나타나는 부분(UI)

VAC 패턴 도입 이유

  • 관심사가 분리되어 있어 유지 보수하기 편리하다.
    • 디자인이 변경될 경우 View 컴포넌트만 보면 되고, 비즈니스 로직이 변경될 경우 비즈니스 로직 영역만 다루면 된다.
  • 타입스크립트를 적용하기 편리할 것 같다 생각했다. (내 생각)
    • JSX 컴포넌트로 작성된 프로젝트에 타입스크립트를 도입하여 모든 컴포넌트를 TSX로 변환하고자 했다. VAC 패턴은 관심사를 분리하고 props를 object 형태로 넘겨주기 때문에 타입스크립트를 적용하기 편리할 것이라는 생각이 들었기 때문이다.

프로젝트에 VAC 패턴 도입

기존 컴포넌트

다이어리를 작성하는 다음과 같은 컴포넌트가 있다.

비즈니스 로직 찾기

다음 컴포넌트에서 비즈니스 로직과 뷰 로직을 분리해보자.

  • 인풋 창에 변화가 있을 때마다 title과 text를 변경하는 handleData 함수
  • form 을 submit할 때 실행되는 handleSubmit 함수
  • useEffect 내부 로직
    - submit이 성공했을 때 콘솔을 찍고, title, text를 초기화
import { useEffect, useState, useContext } from 'react'
import { useFirestore } from '../../hooks/useFirestore';
import * as S from "./diaryForm.style";
import DragCont from '../../components/DragContainer/DragCont';
import { useDispatch, useSelector } from 'react-redux';

export default function DiaryForm({ uid }) {

    const [title, setTitle] = useState('');
    const [text, setText] = useState('');
    const { addDocument, response } = useFirestore('myDiary');
    const dispatch = useDispatch();
    const status = useSelector(state => state.form)

    const handleData = (event) => {
        if (event.target.id === 'tit') {
            setTitle(event.target.value);
        } else if (event.target.id === 'txt') {
            setText(event.target.value);
        }
    }

    const handleSubmit = (event) => {
        event.preventDefault();
        console.log(title, text);
        addDocument({
            uid, title, text, 
        });
    }

    useEffect(() => {
        if (response.success){
            console.log(response)
            setText('');
            setTitle('')
        }
    }, [response.success])

    return (
        <DragCont>
            <S.FormCont onSubmit={handleSubmit}>
                <fieldset>
                    <S.FormTit>
                        일기 쓰기
                        <S.CloseBtn
                        onClick={() => {
                            dispatch({type: "toggleForm"})
                        }}
                        >x</S.CloseBtn>
                    </S.FormTit>
                    <S.FormContent>
                    <S.InpLabel htmlFor="tit">일기 제목 :</S.InpLabel>
                    <S.Inp id="tit" type="text" required value={title} onChange={handleData} />

                    <S.InpLabel htmlFor="txt">일기 내용 : </S.InpLabel>
                    <S.TxtArea id="txt" required value={text} onChange={handleData}></S.TxtArea>

                    <S.SubmitBtn><u></u>장하기</S.SubmitBtn>
                    </S.FormContent>
                </fieldset>
            </S.FormCont>
        </DragCont>
    )
}

VAC 패턴 도입

비즈니스 로직 컴포넌트(DiaryForm.jsx)

import { useEffect, useState } from 'react'
import { useFirestore } from '../../hooks/useFirestore';
import DiaryFormView from './DiaryFormView';

export default function DiaryForm({ uid }) {
    const [title, setTitle] = useState('');
    const [text, setText] = useState('');
    const { addDocument, response } = useFirestore('myDiary');

    const handleData = (event) => {
        if (event.target.id === 'tit') {
            setTitle(event.target.value);
        } else if (event.target.id === 'txt') {
            setText(event.target.value);
        }
    }

    const handleSubmit = (event) => {
        event.preventDefault();
        console.log(title, text);
        addDocument({
            uid, title, text, 
        });
    }

    useEffect(() => {
        if (response.success){
            console.log(response)
            setText('');
            setTitle('')
        }
    }, [response.success])

  // view로 내려줄 props
    const props = {
        title,
        text,
        handleData,
        handleSubmit,
    }

    return (
      // props를 스프레드 문법으로 전달
        <DiaryFormView {...props}/>
    )
}

view에서 필요한 모든 데이터를 props의 형태로 view 영역에 전달한다.
props 정의 위 비즈니스 로직 부분은 수정된 부분 없이 동일하다.

뷰 컴포넌트(DiaryFormView.jsx)

import React from 'react'
import { useDispatch } from 'react-redux';
import DragCont from '../DragContainer/DragCont';
import * as S from "./diaryForm.style";

export default function DiaryFormView({
    title,
    text,
    handleData,
    handleSubmit,
}) {
    const dispatch = useDispatch();

    return (
        <DragCont>
            <S.FormCont onSubmit={handleSubmit}>
                <fieldset>
                    <S.FormTit>
                        일기 쓰기
                        <S.CloseBtn
                        onClick={() => {
                            dispatch({type: "toggleForm"})
                        }}
                        >x</S.CloseBtn>
                    </S.FormTit>
                    <S.FormContent>
                    <S.InpLabel htmlFor="tit">일기 제목 :</S.InpLabel>
                    <S.Inp id="tit" type="text" required value={title} onChange={handleData} />

                    <S.InpLabel htmlFor="txt">일기 내용 : </S.InpLabel>
                    <S.TxtArea id="txt" required value={text} onChange={handleData}></S.TxtArea>

                    <S.SubmitBtn><u></u>장하기</S.SubmitBtn>
                    </S.FormContent>
                </fieldset>
            </S.FormCont>
        </DragCont>
    )
}

뷰 컴포넌트에서도 내려준 props의 형태 동일하게 받으면 된다. 기존 return 문 안에 있던 뷰 부분을 모두 가져오기만 했다.

타입스크립트 적용하기

비즈니스 로직에서 props의 타입 interface로 지정하기

비즈니스 로직에서 object 형태의 props를 정의해서 그대로 view 컴포넌트로 넘겨주었다.

const props = {
  title,
  text,
  handleData,
  handleSubmit,
}

넘겨줄 props를 하나로 모아 두었기 때문에 타입스크립트로 타입을 정의하기 너무나 편리하다.

타입스크립트의 interface로 위 props 객체 타입을 정의해줄 건데, view 컴포넌트에서도 사용할 것이기 때문에 export 키워드로 정의해준다.

export interface PropsType {
    title: string;
    text: string;
    handleData: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void
    handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void
}

뷰 로직에서 props의 타입을 지정

// 비즈니스 로직 컴포넌트에서 정의한 interface import
import { PropsType } from './DiaryForm';

...
export default function DiaryFormView({
    title,
    text,
    handleData,
    handleSubmit,
// props 타입 통째로 import 해온 인터페이스로 적용!
}: PropsType) {
  return (
   // View .. . . .
  )
}

시각화

예시로 첨부한 DiaryForm 컴포넌트의 VAC 패턴 도입과 타입스크립트 마이그레이션 과정을 피그잼으로 시각화해보았다. 그림이 너무 커서 아래 확대하기 보기 버튼을 클릭하면 글자가 잘 보인다...ㅎㅎ

🔍 이미지 확대해서 보기 👀

0개의 댓글