React 리팩토링(w/Storybook): UI와 비즈니스 로직 분리

React Refactoring (w/Storybook): Separate UI and Business Logic



이전 회사에 입사했을 때 프로젝트 코드에 대해 아는 사람이 없었고 프로젝트를 만든 개발자가 떠났습니다.
구성 요소가 많았는데 어떤 구성 요소에 어떤 비즈니스 로직이 있는지 몰랐습니다. 재사용하고 싶었던 구성 요소가 있었지만 다른 구성 요소의 동작에 영향을 미칠 수 있는 복잡한 비즈니스 로직이 있었기 때문에 재사용할 수 없었습니다. 이로 인해 컴포넌트를 재활용하고 UI를 재사용하기가 어려우며 때로는 비슷한 모양으로 이미 존재하는 컴포넌트를 만들기도 했습니다.

어느 날 나는 그것들을 리팩토링하기로 결정했습니다. 이를 위해 UI와 비즈니스 로직을 분리하고 구성 요소 나열에 storybook를 사용했습니다. 이 작업 후에는 구성 요소의 UI를 쉽게 재사용하고 어떻게 보이는지 확인할 수 있었습니다.

이에 대한 예를 보여드리겠습니다.
방문자 수를 표시하는 구성 요소TodayVisitorCard가 있습니다.

import { useEffect, useState } from "react";
import styled from "@emotion/styled";

const CardWrapper = styled.div`
  border: 1px solid black;
  border-radius: 8px;
  box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
  padding: 24px;
  text-align: center;
  width: fit-content;
`;

interface VisitorResponse {
  total: number;
  today: number;
}

const TodayVisitorCard = () => {
  const [data, setData] = useState<VisitorResponse | null>();
  const [hasError, setHasError] = useState(false);

  const fetchVisitor = async () => {
    try {
      const res = await fetch("http://localhost:7777/visitor");

      if (res.status !== 200) {
        throw new Error(`Response status is ${res.status}.`);
      }

      const data = await res.json();
      setData(data);
    } catch (e) {
      console.error(e);
      setHasError(true);
      setData(null);
    }
  };

  useEffect(() => {
    fetchVisitor();
  }, []);

  if (data === undefined) return <div>loading...</div>;

  return (
    <CardWrapper>
      {hasError && <div>Something went wrong!</div>}
      {data && (
        <>
          <h2>Total: {data.total}</h2>
          <span>Today: {data.today}</span>
        </>
      )}
    </CardWrapper>
  );
};

export default TodayVisitorCard;




논리를 분리합시다.

import { useEffect, useState } from "react";
import styled from "@emotion/styled";

const CardWrapper = styled.div`
  border: 1px solid black;
  border-radius: 8px;
  box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
  padding: 24px;
  text-align: center;
  width: fit-content;
`;

interface VisitorResponse {
  total: number;
  today: number;
}

const TodayVisitorCard = () => {
  const [data, setData] = useState<VisitorResponse | null>();
  const [hasError, setHasError] = useState(false);

  const fetchVisitor = async () => {
    try {
      const res = await fetch("http://localhost:7777/visitor");

      if (res.status !== 200) {
        throw new Error(`Response status is ${res.status}.`);
      }

      const data = await res.json();
      setData(data);
    } catch (e) {
      console.error(e);
      setHasError(true);
      setData(null);
    }
  };

  useEffect(() => {
    fetchVisitor();
  }, []);

  if (data === undefined) return <div>loading...</div>;

  return <TodayVisitorCardUI {...data} hasError={hasError} />;
};

const TodayVisitorCardUI = ({
  hasError,
  total,
  today,
}: {
  hasError?: boolean;
  total?: number;
  today?: number;
}) => {
  return (
    <CardWrapper>
      {hasError && <div>Something went wrong!</div>}
      {total && <h2>Total: {total}</h2>}
      {today && <span>Today: {today}</span>}
    </CardWrapper>
  );
};

export { TodayVisitorCard };
export default TodayVisitorCardUI;


이제 UI로 다른 비즈니스 로직을 쉽게 사용할 수 있으며,
아래와 같이 storybook로 컴포넌트 UI를 나열할 수 있습니다.



import { ComponentStory, ComponentMeta } from "@storybook/react";

import TodayVisitorCard from ".";

export default {
  title: "\"components/TodayVisitorCard\","
  component: TodayVisitorCard,
  args: {
    hasError: false,
  },
  argTypes: {
    today: {
      type: "number",
    },
    total: {
      type: "number",
    },
  },
} as ComponentMeta<typeof TodayVisitorCard>;

const Template: ComponentStory<typeof TodayVisitorCard> = (args) => (
  <TodayVisitorCard {...args} />
);

export const Example = Template.bind({});

Example.args = {
  today: 6522,
  total: 139,
};

export const Error = Template.bind({});

Error.args = {
  hasError: true,
};







결론



실제 세계에서는 더 복잡할 것입니다. 고려해야 할 사항이 많을 수 있습니다. 구조 및 명명 규칙이 그 중 하나일 수 있습니다. 프로젝트에 맞는 나만의 전략을 세워야 합니다.

누군가에게 도움이 되길 바랍니다.
행복한 코딩!

좋은 웹페이지 즐겨찾기