리액트 앱에서의 중앙 집중식 API 에러 핸들링


원문: https://itnext.io/centralizing-api-error-handling-in-react-apps-810b2be1d39d 저자 웹 사이트: https://aggelos.dev/

image1.jpeg

이 글에서는 사용 중인 상태 관리 라이브러리(Redux, Apollo, etc..)에 상관없이 API 오류를 중앙 집중식이며, 쉽게 확장 가능한 방식으로 한번에 처리하는 방법을 제시한다.

대부분의 최신 앱들은 API를 통해 데이터를 가져온다. RESTful API든 GraphQL이든 웹에서 발생하는 요청은 대부분의 앱에서 필수적인 부분이다. 대부분 요청이 성공적으로 반환되지만, 순조롭게 진행이 되지 않는 경우도 있다. 유저에게 드러날 수 있는 404나 403, 500 같은 무서운 API 응답에 대해 이야기 할 것이다. 이러한 응답은 특성상 한 곳에서 모두 처리하는 것이 이상적이다.

불행하게도, 리액트에서는 각각의 앱이 모두 다른 접근법을 사용하는 경우가 많기 때문에 관리하기가 더 어렵다. 이 글에서는 사용중인 상태 관리 라이브러리(Redux, Apollo 등)에 관계없이 API 오류를 중앙 집중식으로 한 번에 확장 가능한 방식으로 처리하는 방식을 제시한다. 또한, 이 방식은 클라이언트 렌더링이나 서버 사이드 렌더링 여부에 상관 없이 모든 앱에서 재사용할 수 있다. 더 이상 지체하지 말고 파고 들어보자!

시나리오 만들기

이 글에서는 설명을 위해 일반적인 HTTP 오류 상태 코드를 반환하는 RESTful API가 있다고 가정하지만, GraphQL 또는 다른 API에도 유사하게 적용된다. 또한 react-router를 사용한다고 가정하고 설명하지만, @reach/router 또는 다른 React 라우팅 라이브러리를 사용해도 유사하게 적용할 수 있다.

두 페이지가 있는 작은 앱을 작성한다고 가정하겠다. 첫번째 페이지는 강아지 품종을 보여줄 것이다. 품종을 클릭하면 각 품종 페이지로 이동할 것이며, 해당 품종의 강아지 사진을 무작위로 표시할 것이다. 앱을 작성하면 아래와 같다. (UI나 코드는 이 글의 설명 범위를 넘어가는 부분이니 신경쓰지 마라)

import React from "react";
import { BrowserRouter, Route, useParams, Link } from "react-router-dom";
import DogPage from "./DogPage";
import IndexPage from "./IndexPage";

// 선택할 수 있는 강아지 품종 리스트를 보여주는 페이지다.
const breeds = ["husky", "akita", "pitbull"];
const IndexPage = () => {
  return (
    <div>
      <h1>View some nice pictures of a dog breed</h1>
      <ul>
        {breeds.map((breed) => (
          <li key={breed}>
            <Link to={`/dogs/${breed}/`}>{breed}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
};

// 선택한 품종 강아지의 무작위 이미지를 보여주는 페이지다. 품종은 URL 파라미터로 넘긴다.
const DogPage = () => {
  const { breed } = useParams();
  const [imageSrc, setimageSrc] = React.useState();

  React.useEffect(() => {
    fetch(`https://dog.ceo/api/breed/${breed}/images/random`)
      .then((data) => data.json())
      .then((data) => setimageSrc(data.message));
  }, [breed]);

  return (
    <div>
      <div>
        <Link to="/">back</Link>
      </div>
      {!imageSrc && <p>Loading...</p>}
      {imageSrc && <img alt={`A nice ${breed}`} src={imageSrc} height={200} />}
    </div>
  );
};

// 작성한 모든 컴포넌트를 바인딩한다.
const App = () => {
  return (
    <BrowserRouter>
      <Route exact path="/" component={IndexPage} />
      <Route exact path="/dogs/:breed/" component={DogPage} />
    </BrowserRouter>
  );
};

이 앱은 동작하지만, 아직 404인 경우를 처리하지 않았다. 사용자가 존재하지 않는 페이지를 방문하면 "Four:oh:four"라는 텍스트를 갖는 404 페이지를 표시 할 것이다. 이 페이지는 아래 두가지 시나리오에 나타나야 한다.

  • 잘못된 URL 정규식을 방문했을 때(i.e. /cats/husky )
  • 유효한 URL 정규식을 방문했지만 잘못된 품종을 입력했을 때(이 경우는 사용자가 주소창에서 URL을 수동으로 바꿀 때 발생할 수 있을 것이다.)

첫 번째 경우는 다루기 쉽다. 대부분의 튜토리얼에서 권장하는 것처럼, 앱의 마지막 라우트로써 react-router의 <Switch/> 컴포넌트의 모든 라우트를 랩핑하는 "catch-all" 라우트를 추가하면 된다. 코드로 작성하면 아래 처럼 된다.

import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
import DogPage from "./DogPage";
import IndexPage from "./IndexPage";

// 404 컴포넌트(일반적으로 ./Page404.jsx 에 구현된다)
const Page404 = () => <h1>Four:oh:four</h1>;

// 이전 예제의 앱 구성요소. 모든 컴포넌트를 바인딩한다.
const App = () => {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/" component={IndexPage} />
        <Route exact path="/dogs/:breed/" component={DogPage} />
        <Route component={Page404} />
      </Switch>
    </BrowserRouter>
  );
};

이제 사용가가 //dogs/<BREED> 형태 외의 URL을 입력하게 되면 404 페이지로 이동하게 된다.

두 번째 경우는 API에 의존하기에 처리하기가 조금 더 어렵다. 사용자가 입력한 품종이 유효한지 여부를 미리 알지 못한다. 세상에 존재하는 모든 품종을 알지 못하기 때문이다.(또는 적어도 우리가 알지 못한다고 가정해 보자) 404 페이지를 표시하기 위해선, 서버의 응답을 기다려하고, 받은 응답의 상태에 따라 동작해야 한다. 그럼 <DogPage /> 컴포넌트를 수정해보자.

import React from "react";
import { useParams, Link } from "react-router-dom";
import Page404 from "./Page404";

const DogPage = () => {
  const { breed } = useParams();
  const [imageSrc, setimageSrc] = React.useState();
  const [httpStatusCode, setHttpStatusCode] = React.useState();

  React.useEffect(() => {
    fetch(`https://dog.ceo/api/breed/${breed}/images/random`)
      .then((data) => data.json())
      .then((data) => {
        setHttpStatusCode(data.code);
        if (data.status === "success") {
          setimageSrc(data.message);
        }
      });
  }, [breed]);

  if (httpStatusCode === 404) {
    return <Page404 />;
  }

  return (
    <div>
      <div>
        <Link to="/">back</Link>
      </div>
      {!imageSrc && <p>Loading...</p>}
      {imageSrc && <img alt={`A nice ${breed}`} src={imageSrc} height={200} />}
    </div>
  );
};

응답 코드를 저장함으로써 값에 따라 다른 컴포넌트를 렌더링 할 수 있다. 위의 예에서 상태 코드가 404인 경우 404 페이지를 렌더링 했다. 이 방식은 잘 작동하지만, 만약 이런 방식으로 코드를 작성했다면 분명 문제가 발생할 것이다. 어떤 문제점이 있는지 한 번 살펴보자.

  • 중첩 컴포넌트에서 404 처리

최상위 컴포넌트에서 <Page404 />는 쉽게 렌더링 할 수 있지만 컴포넌트가 컴포넌트 트리에서 "깊게" 위치한다면 어떻게 할 것인가? 그렇다면 <Page404 />는 부모 컴포넌트의 한 부분으로 렌더될 것이다. 이것은 전체 화면으로 나타나지 않는 것을 의미할 뿐만 아니라 다른 많은 컴포넌트요소들과 함께 렌더 될 수 있는 것을 의미한다.

  • 반복적인 코드 & 로직

현재 하나의 컴포넌트에서 404를 처리했지만, API를 호출하는 모든 페이지/컴포넌트에서 이 작업을 수행해야 한다. 이는 여러 컴포넌트에 걸쳐 반복적인 코드를 작성해야 하는 것을 의미한다.

  • 여러 에러 응답 다루기

404는 가장 일반적인 상태 중 하나지만, 401, 403, 500 등 에서도 동일한 작업을 수행할 수 있어야 한다. 즉, 이런 상태들을 처리하기 위해 더 많은 코드와 로직을 추가해야 한다.

  • API 호출이 컴포넌트 외부에서 작성 될 때 상태를 prop으로 넘기기 어려움

여기서 API 호출은 컴포넌트 내에서 수행되지만, redux를 사용하는 경우에는 thunk나 saga, observable 등에서 발생할 수 있다. 깔끔하고 쉽게 공통적으로 컴퍼넌트에 적용할 수 있는 방법이 무엇이 있을까?

"redirect" 사용하기

가장 일반적이고 쉽게 사용할 수 있는 방식은 <Page404 /> 를 렌더링 하는 /404 url 로 이동시키는 것이다. 이 방법은 작동하지만, 사용자는 현재 위치의 컨텍스트를 잃는다. 사용자들은 404 페이지를 보게 되겠지만, 기존의 잘못 입력했던 URL이 변경되므로 "어떤 것을 못찾았는지" 알 수 없다. 우리가 원하는 것은 404 페이지가 표시되는 동안 잘못 입력되었던 원래 URL이 유지되는 방법이다.

"hook" 사용하기

첫 번째 방법은 재사용 가능한 커스텀 훅을 사용하는 것이다. 이렇게 될 경우 모든 컴포넌트에서 API 상태 코드를 처리해야 하는 코드를 재작성하지 않아도 된다. 코드를 작성해보면(매우 기본적인 내용의 구현이다) 다음과 같다.

import React from "react";

const useQuery = ({ url }) => {
  const [statusCode, setStatusCode] = React.useState();
  const [apiData, setApiData] = React.useState();

  React.useEffect(() => {
    fetch(url)
      .then((data) => data.json())
      .then(({ code, status, ...apiData }) => {
        setStatusCode(code);
        setApiData(apiData);
      });
  }, [url]);

  return { data: apiData, statusCode };
};

<DogPage /> 컴포넌트에서 쉽게 사용할 수 있다.

import React from "react";
import { get } from "lodash";
import { useParams, Link } from "react-router-dom";
import useQuery from "./useQuery";
import Page404 from "./Page404";

const DogPage = () => {
  const { breed } = useParams();
  const { data, statusCode } = useQuery({
    url: `https://dog.ceo/api/breed/${breed}/images/random`,
  });

  if (statusCode === 404) {
    return <Page404 />;
  }

  const imageSrc = get(data, "message");
  return (
    <div>
      <div>
        <Link to="/">back</Link>
      </div>
      {!imageSrc && <p>Loading...</p>}
      {imageSrc && <img alt={`A nice ${breed}`} src={imageSrc} height={200} />}
    </div>
  );
};

이 방법은 상태 코드를 처리하는 반복적인 코드는 해결 되었지만, 상태 코드에 따라 오류 페이지를 다르게 렌더링 하는 반복적인 로직은 해결하지 못했다. 또한, 또한, 여전히 각 컴퍼넌트 별로 구현되어야 한다. 클래스 기반 컴포넌트는 훅을 사용할 수 없으므로 조금 오래된 클래스 기반의 코드가 있다면 사용할 수 없다.

"render-props" 사용하기

"render-props" 컴포넌트를 사용하면 클래스 기반 컴포넌트와 호환성을 유지하면서, 반복적인 코드 작성을 줄일 수 있다. 이전에 만든 useQuery 훅을 사용해 간단한 render-prop 컴포넌트를 작성해보자.

import React from "react";
import Page404 from "./Page404";

const Query = ({ url }) => {
  const { data, statusCode } = useQuery({ url });

  if (statusCode === 404) {
    return <Page404 />;
  }

  // ... 등 여기서 여러 HTTP 응답 코드 처리

  return children({ data });
};

<DogPage /> 에서 아래처럼 사용할 수 있다.

import React from "react";
import { get } from "lodash";
import Query from "./Query";

const DogPage = () => {
  const { breed } = useParams();
  return (
    <Query url={`https://dog.ceo/api/breed/${breed}/images/random`}>
      {({ data }) => {
        const imageSrc = get(data, "message");
        return (
          <div>
            <div>
              <Link to="/">back</Link>
            </div>
            {!imageSrc && <p>Loading...</p>}
            {imageSrc && (
              <img alt={`A nice ${breed}`} src={imageSrc} height={200} />
            )}
          </div>
        );
      }}
    </Query>
  );
};

이 방법을 사용하면 반복적인 로직들을 제거할 수 있으며, 여러 HTTP 코드를 처리할 수 있고, 클래스 기반 컴포넌트와 호환되면서 전반적으로 깔끔한 코드를 작성할 수 있다. 이제 해결해야 하는 유일한 부분은 <Page404 />독립적으로 렌더링 하는 것 뿐이다. 요약해보면, 404 페이지는 DogPage 컴포넌트에 렌더링 되지만 DogPage가 최상위 컴포넌트가 아닌 상위 컴포넌트가 많으면 어떻게 될까? 그렇다면 404 페이지는 다른 컴포넌트와 함께 전체 UI의 일부로 렌더링 될 것이다.

"top-level state" 사용하기

마지막 문제를 해결하기 위한 유일한 옵션은 다른 페이지가 렌더링 되기 전에 404 페이지를 렌더링 하는 것이다. 이를 위해 오류 페이지(404, 403, 500 등)을 표시하도록 최상위 컴포넌트에 알리는 상태 관리 라이브러리가 필요하다. 이를 위해 여러 라이브러리를 사용할 수 있지만, 여기서는 내장되어 있는 Context API를 사용하겠다.

필요한 것은 상태 코드를 기반으로 올바른 오류 페이지를 렌더링하며, 모든 컴퍼넌트에서 이 동작(에러 페이지를 렌더링)을 발생시킬 수있게 해주는 컴포넌트이다. Context에 따라 적절한 동작을 하는 상위 컴포넌트를 작성해보자.

import React from "react";
import { useHistory } from "react-router-dom";

// 컨텍스트를 사용해 컴포넌트 트리를 낮춰 오류 페이지를 표시한다
const ErrorStatusContext = React.createContext();

// 앱의 핵심 기능을 랩핑하는 최상위 컴포넌트
const ErrorHandler = ({ children }) => {
  const history = useHistory();
  const [errorStatusCode, setErrorStatusCode] = React.useState();

  // 사용자가 새 URL을 탐색할 때 마다 이 상태코드를 "제거" 해야한다. 그렇지 않을 경우 사용자는 오류 페이지에 영원히 "갇히게" 된다.
  React.useEffect(() => {
    // 현재 위치의 변경 사항을 하는 리스너
    const unlisten = history.listen(() => setErrorStatusCode(undefined));
    // unmount될 때 리스너 제거
    return unlisten;
  }, []);

  // 컴포넌트를 렌더하는 부분이다.
  // API 오류와 일치하는 errorStatusCode가 있으면 오류 페이지를 렌더링한다. 오류 상태가 없다면 자식 컴포넌트를 정상적으로 렌더링한다.
  const renderContent = () => {
    if (errorStatusCode === 404) {
      return <Page404 />;
    }

    // ... 다른 HTTP 코드는 여기서 관리

    return children;
  };

  // 성능상의 이유로 useMemo로 랩핑. 더 궁금하다면 링크 확인
  // https://kentcdodds.com/blog/how-to-optimize-your-context-value/
  const contextPayload = React.useMemo(() => ({ setErrorStatusCode }), [
    setErrorStatusCode,
  ]);

  // 컨텍스트의 값을 컴포넌트에 노출하는 동시에 화면에 적절한 컨텐츠를 렌더링
  return (
    <ErrorStatusContext.Provider value={contextPayload}>
      {renderContent()}
    </ErrorStatusContext.Provider>
  );
};

// 컨텍스트 값을 빠르게 읽을 수 있는 커스텀 훅이다.
// 빠른 import를 위해 여기서만 허용된다.
const useErrorStatus = () => React.useContext(ErrorStatusContext);

이 컴포넌트는 다소 복잡하니, 자세한 설명을 위해 인라인으로 작성된 주석을 읽어라.

이 컴포넌트를 사용하기 위해 아래처럼 핵심 로직을 랩핑하겠다.

import React from "react";
import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
import DogPage from "./DogPage";
import IndexPage from "./IndexPage";
import Page404 from "./Page404";

const App = () => {
  return (
    <BrowserRouter>
      <ErrorHandler>
        <Switch>
          <Route exact path="/" component={IndexPage} />
          <Route exact path="/dogs/:breed/" component={DogPage} />
          <Route component={Page404} />
        </Switch>
      </ErrorHandler>
    </BrowserRouter>
  );
};

우리는 잘못된 상태 코드를 반환할 때마다 오류 페이지 표시를 자동으로 표시를 "트리거" 하도록 useQuery를 수정해야 한다. 즉 ErrorHandler에 의해 더 이상 관리 되지 않기 때문에 로컬 state에서 statusCode가 필요하지 않음을 의미한다. 우리가 필요한 것은 에러 상태 코드를 설정하는 액션을 실행하는 것이다.

import { useErrorStatus } from "./ErrorHandler";

const useQuery = ({ url }) => {
  const { setErrorStatusCode } = useErrorStatus();
  const [apiData, setApiData] = React.useState();

  React.useEffect(() => {
    fetch(url)
      .then((data) => data.json())
      .then(({ code, status, ...apiData }) => {
        if (code > 400) {
          setErrorStatusCode(400);
        } else {
          setApiData(apiData);
        }
      });
  }, [url]);

  return { data: apiData };
};

마침내 ErrorHandler 컴포넌트에 에러를 핸들링 하는 기능이 추가 되었으므로 더 이상 다른 컴포넌트에서 오류 상태를 처리할 필요가 없어졌다. 오류 상태를 처리하는 코드를 없앤 최종 <DogPage />컴포넌트를 살펴보자.

import React from "react";
import { useParams, Link } from "react-router-dom";
import { get } from "lodash";
import useQuery from "./useQuery";

const DogPage = () => {
  const { breed } = useParams();
  const { data } = useQuery({
    url: `https://dog.ceo/api/breed/${breed}/images/random`,
  });

  const imageSrc = get(data, "message");
  return (
    <div>
      <div>
        <Link to="/">back</Link>
      </div>
      {!imageSrc && <p>Loading...</p>}
      {imageSrc && <img alt={`A nice ${breed}`} src={imageSrc} height={200} />}
    </div>
  );
};

지금까지 우린 쉬운(훅을 사용한), 자동화 된(이후 추가되는 컴포넌트에 에러 핸들링을 고려할 필요 없는), 확장 가능한(ErrorHandler에서 필요한 모든 오류를 처리할 수 있는) 방법을 알아봤다.

범용적인 해결책 (마지막이다, 진짜로)

위 방법은 잘 작동하지만, 모든 리액트 프로젝트에서 재사용하기는 힘들다. 이 접근법은 리액트의 경계에서는 잘 작동하지만, redux에서 API를 호출할때는 장황해진다. redux thunk에서 setErrorStatus를 어떻게 발생시킬 것인가? 단순하게 할 수 없다. redux state를 변경하기 위한 액션을 트리거 해야하고 ErrorHandler가 redux state를 읽게 해야 한다. 하지만 해당 일을 수행하기 위해 많은 보일러플레이트 코드가 들어가게 된다. Apollo에서도 동일하다. 단지 상태 코드를 변경하기 위해 apollo-link-state를 포함 시키고 mutation을 디스패치해야한다.

이러한 부분을 염두하면서, 정확히 무엇을 달성해야 하는지 다시 생각해보자. URL을 상태 코드와 연결하고 상태 코드의 값에 따라 렌더링할 대상을 결정해야 한다. 음.. 우리가 많이 사용하지 않는 브라우저 기본 기능을 활용하면 이 부분을 쉽게 해결할 수 있다.

현재 위치의 상태다

브라우저의 기본 history API는 각 위치에 따라 원하는 어떤것이든 저장 할 수 있는 상태 키를 첨부할 수 있다. 로컬 state 대신 statusCode를 저장하는데 사용하지 않을 이유가 없다. 이로써 얻을 수 있는 보너스들을 살펴보자.

  • 다른 위치로 이동을 할 때 state를 정리할 필요가 없다. 따라서 새 위치에서는 깨끗한 state를 가진다.
  • (이전 버튼의 상태가 브라우저의 기록 스택에 저장되어 있기 떄문에) 이전으로 이동하기를 눌렀을 때 API 요청을 새로 하지 않아도 404 페이지를 자동으로 보여준다.
  • 명령적(imperative)으로 위치에 상태를 할당하기 때문에, 다른 어떤 상태 관리 라이브러리와도 쉽게 통합할 수 있다.
  • (status code 뿐만 아니라) 원하는 어떤 키든 저장하고 관리할 수 있으므로 구성이나 확장면에서 매우 뛰어나다. 즉, 오류 페이지 구성 요소에 props로써 전달 될 데이터를 저장할 수도 있다.

이를 염두해두고 ErrorHandler를 단순화해보자.

import React from "react";
import { useLocation } from "react-router-dom";
import { get } from "lodash";
import Page404 from "./Page404";

const ErrorHandler = ({ children }) => {
  const location = useLocation();

  switch (get(location.state, "errorStatusCode")) {
    case 404:
      return <Page404 />;

    // ... 다른 타입의 상태 코드를 처리한다

    default:
      return children;
  }
};

그리고 우리 시스템의 모든 모듈들은(리액트에 존재하든 아니든 상관없이) 현재 위치에 에러 코드를 추가하거나 대체할 수 있게 된다. 이를 보여주기 위해 useQuery를 수정해보겠다.

import React from "react";
import { useHistory } from "react-router-dom";

const useQuery = ({ url }) => {
  const history = useHistory();
  const [apiData, setApiData] = React.useState();

  React.useEffect(() => {
    fetch(url)
      .then((data) => data.json())
      .then(({ code, status, ...apiData }) => {
        if (code > 400) {
          history.replace(history.location.pathname, {
            errorStatusCode: code,
          });
        } else {
          setApiData(apiData);
        }
      });
  }, [url]);

  return { data: apiData };
};

이 방법의 장점은 useQuery 훅이 ErrorHandler컴포넌트 존재 자체를 알지 못한다는 것이다. 현재 history에 특정 상태를 추가하기위해 해야할 일은 'side-effect'로써 위치를 수정하는 것 뿐이다. 즉, useQueryErrorHandler는 상호 의존적이지 않으므로 아무 이슈없이 바꿀 수 있다는 것을 의미한다. 게다가, ErrorHandler에 원하는 사용자 정의 로직들을 추가할 수 있다. 예를 들어 404 오류를 무시하는 화이트리스트 쿼리 목록이나 여러 에러들을 동시에 처리하는 로직들을 추가 할 수 있다. 각 앱에 따라 요구사항이 다르기 때문에, 이런 부분들은 비즈니스와 연결되어 있다.

이제 완성이다. ErrorHandleruseQuery 훅만 있다면 중앙 집중식으로 API 에러를 핸들링할 수 있게 된다. 반드시 얘기해야 하는 부분이 있는데, 만약 상태 관리 라이브러리를 사용한다면 useQuery를 사용하지 않을 가능성이 있다. 그럴 경우, 라이브러리마다 다른 HTTP fetch 로직의 부분에서 수동으로 history.replace를 트리거해야 한다. 가장 대중적인 라이브러리에서 이 부분을 어떻게 할 수 있는지 살펴보자.

Redux 유저들에게

위 방법을 redux에서 구현하기 위해선, history에 접근을 해야 할 것이다. 여러가지 방법이 있는데, createStore함수에 history를 매개변수로 전달하고 미들웨어를 생성하거나 connected-react-router를 이용해 history를 직접 조작할 수 있는 액션 생성자를 노출시킬 수 있다. 올바른 매개변수로 액션을 발생시키면 된다! 이 문서는 어떻게 수행하는지 많은 내용이 담겨있다.

Apollo 유저들에게

Apollo client에서 처리하기 위해서는, history.replace를 호출하기 위해 apollo-link-error를 사용해 history에 접근할 수 있는 에러 링크를 만들어 줘야 한다. 가장 쉬운 방법은, history 인스턴스를 만들어(react-router를 사용하는 경우 createBrowserHistory를 통해) 링크 내에서 직접 사용하는 것이다. 동일한 인스턴스가 <Router> 컴포넌트에 전달 되어야 한다. 또 다른 옵션으로는 useHistory 훅을 통해 history를 읽고, 이를 매개 변수로 클라이언트에 전달한 뒤(리액트 컴포넌트 내에서 작성해야 한다) 에러 링크로 전달하는 것이다. 후자는 history 인스턴스를 라우터에 의존하려는 경우에 고려해야 하는 접근법으로, 프로덕션 환경과 테스트 환경에서 동일한 Apollo client를 유지할 수 있다.(의존성 주입으로 인해)

마치면서

이 글에서 작성한 많은 코드들이 최종적으로 사용되지는 않았지만, 내가 경험한 여러가지 접근법들의 문제점을 보여주어야 했다. 이 글의 목표는 모든 React앱에서 API 에러를 한번에 처리할 수 있는 좋은 방법을 소개하는 것이었다. 이 방법을 사용하면 context나 state, re-render, 훅 등 많은 것을 생각하지 않아도 된다. 다시 말해서 API 오류를 처리하기 위해 필요한 것은 현재 위치의 상태를 읽고 그에 따라 history를 수정할 수 있는 어떤 종류의 중앙 "API 모듈"과 결합된 최상위 컴포넌트다. 이 방법은 컴포넌트간 결합을 강요하지 않을 뿐더러, 추상적이고 일반적이기 때문에 모든 프레임워크에 쉽게 이식할 수 있는 장점이 있다. 지금까지 이 코드는 대부분의 내 앱에서 훌륭하게 작동되고 있어 기쁘게 생각하고 있다. 하지만, 많은 사람들의 생각이 궁금하다. 최종 코드는 여기서 확인할 수 있다.

궁금한 점이 있거나 문제를 해결하는 더 좋은 방법이 있으면 알려주길 바란다. 읽어줘서 고맙다!

P.S.더 많은 팁들은 내 트위터에서 확인할 수 있다.

한정2020.06.23
Back to list