apollo-boost에서 @apollo/client v3로 업그레이드

jhj46456·2020년 9월 15일
0

2020년 9월 15일 기준으로 apollo-client가 v3.0을 맞이했습니다.

그러면 무엇을 해야할까요.

레거시에서 마이그레이팅해야합니다...

분명 20년 5월에 apollo 공식 문서 get started에서 apollo-boost 튜토리얼이 있던게 어제 같은데

레거시가 되어버린 https://github.com/Kunune/mo-gak-ko-front-public 코드를 업데이트해야 합니다.


달라진 점

  • Local Resolverdeprecated 되었습니다.
  • @apollo/react-hooksreact-apollo가 코어 패키지인 @apollo/client로 병합됩니다.

ApolloClient

legacy

기존의 코드는 이렇습니다.

수정할 부분으로 마킹해봤습니다.

📚 src/apollo/client.tsx
            👇
import ApolloClient from "apollo-boost";  ❗
import { defaults, resolvers } from "./localState"; ❗

export default new ApolloClient({
  uri: ❗
    process.env.NODE_ENV === "production" ❗
      ? process.env.REACT_APP_PROD_API_URL ❗
      : process.env.REACT_APP_DEV_API_URL, ❗
  clientState: { ❗
    defaults, ❗
    resolvers, ❗
  }, ❗
  request: (operation) => { ❗
    const token = localStorage.getItem("token"); ❗
    operation.setContext({ ❗
      headers: { ❗
        authorization: token ? `Bearer ${token}` : "", ❗
      }, ❗
    }); ❗
  }, ❗
}); ❗
--------------------------------------------------------------------
📚 src/apollo/localState.ts
              👇
import { Resolvers } from "apollo-boost"; ❗

export const defaults = { ❗
  isLoggedIn: Boolean(localStorage.getItem("token")) || false, ❗
}; ❗

export const resolvers: Resolvers = { ❗
  Mutation: { ❗
    logUserIn: (_, { token }, { cache }) => { ❗
      localStorage.setItem("token", token); ❗
      cache.writeData({ ❗
        data: { ❗
          isLoggedIn: true, ❗
        }, ❗
      }); ❗
      return null; ❗
    }, ❗
    logUserOut: (_, __, { cache }) => { ❗
      localStorage.removeItem("token"); ❗
      cache.writeData({ ❗
        data: { ❗
          isLoggedIn: false, ❗
        }, ❗
      }); ❗
      window.location.href = "/"; ❗
      return null; ❗
    }, ❗
  }, ❗
}; ❗

이런! 그냥 새로 작성하는 것이 나을 정도입니다!

brand new

📌 local state를 cache에 저장하는 법
📌 모든 request에 header > authorization을 보내는 법

📚 src/apollo/client.tsx
            👇
import { ApolloClient, InMemoryCache, HttpLink, ApolloLink, concat, } from "@apollo/client";
import { isLoggedIn } from "./cache";

const httpLink = new HttpLink({
  uri:
    process.env.NODE_ENV === "production"
      ? process.env.REACT_APP_PROD_API_URL
      : process.env.REACT_APP_DEV_API_URL,
}); 👈 GraphQL API 주소를 정의하는 Link

const authMiddleware = new ApolloLink((operation, forward) => {
  // add the authorization to the headers
  operation.setContext({
    headers: {
      authorization: localStorage.getItem("token") || null,
    },
  });
  return forward(operation);
}); 👈 인증 미들웨어로 httpLink와 같이 동작하는 함수

const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        isLoggedIn: {
          read() {
            return isLoggedIn();
          },
        },
      },
    },
  },
}); 👈 문서에서 이렇게 cache에 local state를 저장하라고 한다.

export default new ApolloClient({
  link: concat(authMiddleware, httpLink), 👈 인증 미들웨어, API URL 병합
  cache,
});

---------------------------------------------------------

📚 src/apollo/cache.ts
            👇
import { makeVar } from "@apollo/client";

export const isLoggedIn = makeVar(
  Boolean(localStorage.getItem("token")) || false
); 👈 makeVar로 값을 지정해두면 그 변수는 localState가 된다.

export const logIn = (token: string) => {
  localStorage.setItem("token", token);
  isLoggedIn(true);
}; 👈 makeVar로 만들어진 변수는 고차 함수, Closure같은 개념으로 작동한다.

export const logOut = () => {
  localStorage.removeItem("token");
  isLoggedIn(false);
  window.location.reload();
};

ApolloProvider

React와 연결하는 ApolloProvider는 패키지 명만 바뀝니다.

legacy

import React from "react";
import ReactDOM from "react-dom";
import { ApolloProvider } from "@apollo/react-hooks"; 👈
import App from "./components/App";
import client from "./apollo/client";

ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById("root")
);

brand new

import React from "react";
import ReactDOM from "react-dom";
import { ApolloProvider } from "@apollo/client"; 👈
import App from "./components/App";
import client from "./apollo/client";

ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById("root")
);

로그인

이 앱은 로그인 버튼을 누르면 github auth로 넘어가고 callback url로 이동한 컴포넌트에서 토큰 처리 후 메인 페이지로 이동합니다.

로그아웃도 동일한 방법으로 수정.

legacy

📚 해당 기능의 컴포넌트 중 코드 일부분
               👇
import { useMutation } from "@apollo/react-hooks";
import { LOG_USER_IN } from "../queries/globalQueries";

function Auth() {
  const [logUserIn] = useMutation(LOG_USER_IN);
  
  useEffect(() => {
    async function getToken() {
      const { token } = await fetch(
        process.env.NODE_ENV === "production"
          ? `${process.env.REACT_APP_PROD_API_URL}/auth/github?code=${code}`
          : `${process.env.REACT_APP_DEV_API_URL}/auth/github?code=${code}`,
        {
          method: "POST",
        }
      ).then((res) => res.json());
      logUserIn({ variables: { token } }); ❗
    }
    getToken();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
}

brand new

import { logIn } from "../apollo/cache"; 👈 InMemoryCache에 넣어둔 그것

function Auth() {
  ...
  useEffect(() => {
    async function getToken() {
      const { token } = await fetch(
        process.env.NODE_ENV === "production"
          ? `${process.env.REACT_APP_PROD_API_URL}/auth/github?code=${code}`
          : `${process.env.REACT_APP_DEV_API_URL}/auth/github?code=${code}`,
        {
          method: "POST",
        }
      ).then((res) => res.json());
      logIn(token); 👈 인자로 string을 받던 그 함수 맞습니다
    }
    getToken();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  return ...
}

0개의 댓글