본문 바로가기

FrontEnd

리액트 디자인패턴 : View Asset Component (VAC 패턴)

반응형

TLDR : 렌더링의 관심사를 분리하여 stateless component를 만든다.

https://wit.nts-corp.com/2021/08/11/6461

이미 너무 좋은 글이 있지만, 데이터 페치와 렌더링 관심사 분리 관점에서 해당 패턴을 소개해본다.

해당 패턴은 사실 이전에 존재하던 컨테이너-프리젠터 패턴의 fancy-alias일 뿐이다.

 

최근에 스켈레톤 컴포넌트를 만들 일이 있었다.

즉 로딩 시에 대체 컴포넌트를 보여주는 것이다.

설명용이지 실제가 아니다.

 

스켈레톤 컴포넌트

위 예제 링크

간단하게 생각하면 원래 위치의 빵꾸에 가짜 데이터를 넣으면 될 것이다.

하지만 보통 props drilling을 피하기 위해 아래와 같이 컴포넌트 안에 query 로직을 집어넣는다.

import "../App.css";
import axios from "axios";
import React from "react";

const WithoutSkeleton = () => {
  const [data, setData] = React.useState([]);
  const [isLoading, setIsLoading] = React.useState(true);
  React.useEffect(() => {
    setIsLoading(true);

    // Intentionally delay the function execution
    new Promise((res) => {
      setTimeout(() => {
        res();
      }, 3000);
    }).then(() => {
      axios.get("https://reqres.in/api/users?page=2").then((res) => {
        setData(res.data.data);
        setTimeout(() => setIsLoading(false), 2000);
      });
    });
  }, []);
  return (
    <div>
      <h1>Without Skeleton</h1>
      {isLoading &&
        data.map((item) => {
          return (
            <li key={item.id} className="item">
              <div>
                <img className="img" src={item.avatar} alt="" />
              </div>
              <div className="info">
                <p>
                  <strong>
                    {item.first_name} {item.last_name}
                  </strong>
                </p>
                <p>{item.email}</p>
              </div>
            </li>
          );
        })}
    </div>
  );
};

export default WithoutSkeleton;

 

여러분의 코드베이스에도 분명 데이터 fetch 로직과 렌더링 로직이 섞인 경우가 있을것이다.

해당 문제를 컨테이너 패턴과 VAC 패턴으로 해결해보자.

 

VAC 컴포넌트는 다음과 같은 조건을 만족해야 한다.

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

VAC를 이용한 해법은 정말 간단하다. VAC 컴포넌트로 렌더링의 관심사를 분리하면 되는 것이다.

const WithoutSkeleton = ({ data }) => {
  return (
    <div>
      {data?.map((item) => {
        return (
          <li key={item.id} className="item">
            <div>
              <img className="img" src={item.avatar} alt="" />
            </div>
            <div className="info">
              <p>
                <strong>
                  {item.first_name} {item.last_name}
                </strong>
              </p>
              <p>{item.email}</p>
            </div>
          </li>
        );
      })}
    </div>
  );
};

그리고 fetch가 끝나면 데이터를 전달한다.

const WithFetch = ({ children }) => {
  const { data } = useSWR(
    "https://reqres.in/api/users?page=2",
    (url) =>
      new Promise((res) => setTimeout(() => res(), 5000)).then(() =>
        axios.get(url).then((res) => res.data.data)
      ),
    { suspense: true }
  );

  return data ? children(data) : null;
};

그리고 스켈레톤은 Suspense의 fallback으로 사용한다.

 

 

또한 이 방법은 스토리북과 함께 사용할 때 좋다.

msw나 decorator를 이용한 모킹 없이 가짜 데이터로 컴포넌트를 시각화할 수 있다.

오히려 백엔드의 db, 서비스 모킹보다 훨씬 효과가 좋다 생각한다.

백엔드는 실제 db와 스키마가 다르거나, 연계 서비스가 틀어지면 무의미한 테스트이지만,

데이터를 잘 렌더링하는지 보는건 시각적 테스트이기 때문에 항상 의미가 있다.

반응형