[아티클 리뷰] UI 패턴으로 리액트 어플리케이션을 모듈화하기

Seungrok Yoon (Lethe)·2023년 11월 17일
0

아티클 간단 소개

React는 편리합니다. 순수 함수로 UI를 표현할 수 있고, 자동으로 DOM과 싱크를 맞춰주어, 생산성과 가독성이 높아집니다.

그렇지만 리액트를 사용하면서 아쉬운 점도 존재합니다. 리액트는 단순히 UI 라이브러리이기 때문에, 추가적인 기능(로그인, 보안, 캐싱) 등을 사용하기 위해서는 어쩔 수 없이 사이드 이팩트가 발생한다는 점이지요. 그리고 이것들은 리액트 컴포넌트들 덜 순수하게 만듭니다.

특히 서버에서 데이터를 불러오려고 하는 순간, UI의 순수성은 크게 훼손됩니다. 서버 데이터와 연관된 많은 비즈니스 로직들이 리액트 컴포넌트의 가독성을 해치고 말죠.

이미 위대한 과거의 선배 개발자들이 정립해놓은 디자인 패턴들을 프론트엔드 코드에 적용하는 것이 쉽지 않아 보입니다. 솔직히 저는 부끄럽지만 에라 모르겠다식으로 컴포넌트에, 커스텀 훅에 데이터 페칭 함수를 우겨넣은 적도 많았음을 고백합니다 😭

가끔은 프론트엔드의 코드들은 일정한 체계가 없이 관리되는 것처럼 생각이 되기도 했습니다. 일정한 체계가 없이 모듈들이 구성되었다는 것은 적절한 추상화가 이뤄지지 않았다는 것이고, 재사용성이 그만큼 낮아진다는 뜻이기도 합니다.

UI와 비즈니스 로직이 각각의 책임을 가지고 잘 분리되고, 의존성을 줄이면서도 유기적으로 동작하는 코드를 작성할 수 있는 방법은 없을까요? 아직 프론트엔드 개발자로서의 뚜렷한 주관이 성립되지 않은 저로서는 머리가 아팠습니다.

개발자 Juntao는 하지만 프론트엔드에서도 이미 정립된 디자인 패턴들을 적용하여 모듈화된 코드를 작성할 수 있다고 주장하고 있습니다.

아래 코드 스니펫은 가상의 쇼핑몰에서 지불 방법을 선택하는 UI 컴포넌트와 관련된 예시 코드입니다. 저는 이 코드가 나쁘지 않다고 생각을 했습니다. 만약 여러분들도 저와 비슷한 생각을 가지고 계셨다면, 이 글을 읽어보시는 것을 정말로 추천드립니다. 개인적으로 저는 이 글을 읽고, 리팩토링에 대한 큰 힌트를 얻었습니다.

그럼 아래 링크를 통해 Juntao의 아이디어를 엿보도록 할까요?

Modularizing React Applications with Established UI Patterns

src/Payment.tsx…

  export const Payment = ({ amount }: { amount: number }) => {
    const [paymentMethods, setPaymentMethods] = useState<LocalPaymentMethod[]>(
      []
    );
  
    useEffect(() => {
      const fetchPaymentMethods = async () => {
        const url = "https://online-ordering.com/api/payment-methods";
  
        const response = await fetch(url);
        const methods: RemotePaymentMethod[] = await response.json();
  
        if (methods.length > 0) {
          const extended: LocalPaymentMethod[] = methods.map((method) => ({
            provider: method.name,
            label: `Pay with ${method.name}`,
          }));
          extended.push({ provider: "cash", label: "Pay in cash" });
          setPaymentMethods(extended);
        } else {
          setPaymentMethods([]);
        }
      };
  
      fetchPaymentMethods();
    }, []);
  
    return (
      <div>
        <h3>Payment</h3>
        <div>
          {paymentMethods.map((method) => (
            <label key={method.provider}>
              <input
                type="radio"
                name="payment"
                value={method.provider}
                defaultChecked={method.provider === "cash"}
              />
              <span>{method.label}</span>
            </label>
          ))}
        </div>
        <button>${amount}</button>
      </div>
    );
  };

아티클을 읽고 난 후 프로젝트 리팩토링

(아직 작성중인 부분입니다)
아래 제가 최근에 작성한 프로젝트의 디렉토리 구조를 아티클의 내용을 반영하여 리팩토링을 진행해 보려 합니다.

UI컴포넌트들과 관련된 비즈니스 로직을 커스텀 훅으로 분리하는 전형적인 리액트 프로젝트의 디렉토리 구조를 가지고 있습니다.

프론트엔드 개발 직군의 소통이란

프론트엔드라는 분야는 다른 직군(기획, 디자인, 백엔드)들의 작업들에 큰 의존성을 가지고 있다. 그래서 협업하는 과정에서 다른 팀의 영향을 많이 받는다 생각한다(고려해야 할 점이 많다).

프론트엔드는 디자인을 보면서 디자이너의 '의도'를 잘 파악할 수 있어야 하고, 서버에서 전달되는 데이터의 '의미'도 파악하며 작업을 해야 한다.

회의단계에서 디자이너와는 개발 일정과 디자인 시스템, UI 재사용 가능성 등을 협의해야 하고, 백엔드 개발자와는 필요한 API와 데이터 스키마를 협의해야 한다.

이런 협의 과정이 없다면, UI컴포넌트 재사용이 전혀 되어 있지 않은 디자인을 그대로 구현하느라, 필요한 데이터가 여러 API에 산개되어 불필요한 데이터 호출과 데이터 핸들링을 하느라 정작 필요한 기능구현이 늦어지는 경우가 발생할 것이다.

그래서 프론트엔드 개발자는 프론트 개발을 최대한 효율적으로하기 위해서 미리 관련자들과 사전 조율(최대한 관련자들의 의도를 살리면서 동시에 내 일을 편하게 만들기 위해 협의하는 작업)을 할 수밖에 없고, 이것이 프론트엔드 개발 직군의 현실적인 소통이라 생각한다. 그렇지 않다면 말그대로 던져주는 대로 마지막 작업을 하게 되는 잡부가 되어 모든 일정압박을 홀로 견디게 것이다.

얼핏 보면 이런 사전 조율은 프론트개발자만을 위한 이기적인 행위일 수 있지만, 서비스의 말단인 프론트엔드 코드를 구조적으로 관리하려는 노력의 일환이기도 하다.

이런 소통을 잘 하기 위해서는 서비스에 대한 이해도가 높아야 하는 것 같다. 어떤 기능들에 어떤 데이터가 필요한지를 정확하게 이해하고 있어야 하고, 이 기능을 위해 어떤 UI가 적절한지 주관이 서 있어야 한다는 말이다.

난 아직 주니어 중 가장 주니어지만, 예전 기억을 되돌아보면 나는 서비스에 대한 이해도가 낮았었던 것 같다.

profile
안녕하세요 개발자 윤승록입니다. 내 성장을 가시적으로 기록하기 위해 블로그를 운영중입니다.

0개의 댓글