본문 바로가기

FrontEnd

Refactoring React(리팩토링 리액트) : Conditional Rendering(조건부 렌더링)

반응형

 리액트의 조건부 렌더링 구문을 프로처럼 사용하는 방법을 알아봅시다.

react logo

TL;DR - 3줄요약

  • 얼리 리턴
  • 삼항 연산자보다 &&, 연쇄 조건은 js 변수에 할당
  • 부모의 조건부 렌더링 책임을 자식에게 (null 리턴)

Early Return(조기 리턴)

대부분의 경우 추천합니다!

안티 패턴이라 생각하는 분들이 많으나, 아래 글을 읽어봅시다.

https://blog.mathpresso.com/algebraic-effects-of-react-suspense

 

Algebraic Effects of React Suspense

Suspense Based on Algebraic Effects

blog.mathpresso.com

댄 아브라모프의 대수적 효과와 서스펜스도 사실 early return이나 마찬가지입니다.

서스펜스와 에러바운더리는 일종의 guard clause(기드절)와 같은 역할을 합니다.

이를 잘 사용하면 인지 부하를 줄이며 클린한 코드를 작성할 수 있습니다.

 

clean-code-javascript와 같은 곳에선 안티 패턴이라 칭하지만,

마틴 옹의 클린 코드는 class 기반 OOP를 염두에 두고 작성되었으므로,

함수형 패턴과 classless한 구문을 사랑하는 프론트엔드 개발자들은, 서브타이핑 기반 다형성에 목멜 필요가 없습니다.

export function ItemList() {
  const { data, error, isLoading } = useGetData();

  if (isLoading) {
    return (
      <LoadingSpinner />
    );
  }
  if (error) {
    return (
      <Fallback />
    );
  }

  return data.items.map((item) => <Item key={item.id} item={item} />);
}

export function ListPage() {
  return (
    <Layout>
      <ItemList />
      <MoreComponents>
        {
          //... more components here
        }
      </MoreComponents>
    </Layout>
  );
}

유사한 관점에서 아래와 같은 방식을 시도할 수 있습니다.

https://blog.bitsrc.io/react-beautiful-conditional-rendering-with-renderwhen-d8e4d5d962a2

 

React Beautiful Conditional Rendering With <RenderWhen />

Write highly readable, clean and beautiful conditionals in React with this simple and quick technique using <RenderWhen />

blog.bitsrc.io


Optional Chaining Operator ?

배열이 존재하는 경우에만 목록을 렌더링하려는 경우 사용합니다.
function ListPage() {
  // data.tags may be null or undefined
  const { data } = useGetData();

  return (
    <Layout>
            <Post post={data.post} />
      {data.tags?.map((tag) => (
        <Tag key={tag.id} tags={tag} />
      ))}
    </Layout>
  );
}

위 코드는 아래와 효과가 동일합니다.

// data.tags가 null이거나 undefined면
<Layout>
    <Post post={data.post} />
  {undefined}
</Layout>

// data.tags가 정의되어 있으면
<Layout>
    <Post post={data.post} />
  <Tag key="tag1" tags={tag1} />
  <Tag key="tag2" tags={tag2} />
  <Tag key="tag3" tags={tag3} />
</Layout>

삼항 연산자

조금만 경우의 수가 많아지면 읽기 어려워집니다.

최대한 쓰지 맙시다. 아래의 Logical And 연산자로 대체합니다.

쓰더라도 JSX 내에서는 지양하는게 좋아보입니다.


Logical AND Operator &&

렌더링 하거나 전혀 렌더링 할 필요가 없으면 좋습니다.

undefined, null,false가 아닌 falsy 값은 실수로 렌더링 할 수 있으니 아래와 같은 불리언 캐스팅을 염두에 둡니다.

  • double negate the value !!data.items.length
  • use a boolean check like data.items.length > 0
  • case to a boolean with Boolean(error).

해당 패턴의 경우도 여러 조건이 JSX에 이어져 있으면 지저분해 집니다.

function ListPage() {
  const { data, isLoading, error } = useGetData();

  return (
    <Layout>
            {isLoading && <LoadingSpinner />}
      {!isLoading && !!error && <ErrorMessage message={error.message} />}
      {!isLoading && !error && !!data && data.items.map((item) => (
        <Item key={item.id} item={item} />
      ))}
    </Layout>
  );
}
코드의 가독성을 높이려면 연결된 조건(chained condition)을 변수로 추출합니다.
삼항 연산자보다 훨씬 낫습니다.
function ListPage() {
  const { data, isLoading, error } = useGetData();

  const showError = !isLoading && !!error;
  const showData = !isLoading && !error && !!data;

  return (
    <Layout>
            {isLoading && <LoadingSpinner />}
      {showError && <ErrorMessage message={error.message} />}
      {showData && data.items.map((item) => (
        <Item key={item.id} item={item} />
      ))}
    </Layout>
  );
}

Return null(널 리턴)

자식이 렌더링 로직을 캡슐화 하도록 하는 패턴입니다.
아래 코드는 자식을 렌더링하는 책임을 부모가 갖고 있습니다.

export function ItemList({ items }) {
  return items.map((item) => <Item key={item.id} item={item} />);
}

function ListPage() {
  const { data } = useGetData();
  return (
    <Layout>
      {!!data?.items && <ItemList items={data.items} />}
    </Layout>
  );
}

 

자식 컴포넌트 스스로 자신을 렌더링 하도록 하여 조건부 렌더링 책임을 위임합니다.

export function ItemList({ items }) {
  if (!items) {
    return null;
  }

  return items.map((item) => <Item key={item.id} item={item} />);
}

function ListPage() {
  const { data } = useGetData();
  return (
    <Layout>
      <ItemList items={data?.items} />
    </Layout>
  );
}

해당 패턴의 단점은

자식 컴포넌트에서 API 호출과 같은 데이터 인텐시브한 작업을 수행할 경우 성능에 영향을 미칠 수 있다는 점입니다.

(훅의 법칙)

렌더링 조건은 해당 연산을 전부 처리한 뒤에 처리할 수 있기 때문이죠.

또한 컨테이너 컴포넌트와 프리젠테이션 컴포넌트의 다중 중첩 구조를 만들게 됩니다.

export function ItemList({ isHidden }) {
    const { data } = useGetData();

  if (isHidden) {
    return null;
  }

  return data.items.map((item) => <Item key={item.id} item={item} />);
}

function ListPage({ isItemListHidden }) {
  return (
    <Layout>
      <ItemList isHidden={isItemListHidden} />
    </Layout>
  );
}

하지만 컨테이너 패턴을 좋아하는 분들이라면 유지보수 및 확장성에 큰 도움이 되므로 권할만 한 패턴입니다.


렌더 변수

컴포넌트에 JSX를 할당할 수 있습니다.

function ListPage() {
  const { data, isLoading } = useGetData();

  let content = <LoadingSpinner />
  if (!isLoading) (
    content = data.items.map((item) => <Item key={item.id} item={item} />)
  );

  return <Layout>{content}</Layout>;
}

물론 JSX 자체는 JS 오브젝트일 뿐이지만,

JSX는 마크업(구조와 의미)를 나타내므로, 비즈니스 로직이 있는 코드 영역에서는 지양하는게 좋을 듯 합니다.

props로 JSX를 받거나, 최대한 리액트 컴포넌트(함수)를 사용하는 것으로 대체하되,

함수 본문에서는 아래와 같은 로직을 피하고 최대한 JSX를 파라미터와 리턴, 탑 레벨 컨텍스트 쪽으로 보내는게 좋아보입니다.


Component Map / Enum

JSX를 enum처럼 사용할 수도 있습니다.

const components = {
  "loading": <LoadingSpinner />,
  "error": <ErrorMessage />,
  "success": <ItemList />
}

function ListPage() {
  const { status } = useGetData();

  return <Layout>{components[status]}</Layout>;
}

리액트 컴포넌트도 enum처럼 사용할 수 있는데, 좀 더 장황합니다.

const components = {
  "loading": () => <LoadingSpinner />,
  "error": ({ error }) => <ErrorMessage message={error.message} />,
  "success": ({ data }) => <ItemList items={data.items} />
}

function ListPage() {
  const { data, error, status } = useGetData();

  return <Layout>{components[status]({ data, error })}</Layout>;
}

실전성은 의문이나, early return과 유사한 맥락에서 활용할 수 있어보입니다.


참고

https://profy.dev/article/react-conditional-render

 

Conditional Rendering In React - With A Focus On Readability And Clean Code

There are many ways to conditionally render in React. But what are the best options regarding readability? Here's when to use early returns, ternary, &&, and others.

profy.dev

https://medium.com/swlh/return-early-pattern-3d18a41bba8

 

Return Early Pattern

A rule that will make your code more readable.

medium.com

 

반응형