노마드코더에서 우버클론코딩을 하면서 GraphQL, Apollo를 배우고 있다.

Typescript와 함께 쓰면서 백엔드와 타입을 일치시켜서 보호하는 법도 배워서 매우 유용하다 생각해서 정리를 해보려고 한다!

Apollo만을 정리하기 때문에 GraphQL서버는 이미 구현되어있다고 가정을 한다.

  1. set up
yarn add @apollo/client graphql

2. apollo client 파일 생성(이름은 마음대로)

import { ApolloClient, createHttpLink, InMemoryCache, makeVar } from '@apollo/client';
import { LOCALSTORAGE_TOKEN } from './constants';
import { setContext } from '@apollo/client/link/context';

const token = localStorage.getItem('token');
export const tokenVar = makeVar(token); //null

const http = createHttpLink({
  uri: 'graphql server',
});
const auth = setContext((_, { headers }) => {
  return {
    headers: {
      ...headers,
      'token': authTokenVar() || '',
    },
  };
});

export const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache({
    typePolicies: { 
      Query: {
        fields: {
          token: {
            read() {
              return tokenVar();
            },
          },
        },
      },
    },
  }),
});
  • Apollo client를 생성할 때는 uri, cache가 기본으로 요구된다. 해당 코드에서는 link를 사용했는데 그 이유는 header에 token을 넣고 통신하기 위해서다(auth)
  • 1. uri를 정의한 http와 header를 이용해 context를 설정한 auth를 concat해서 link를 정의한다.
  • 2. cache를 생성한다. (일단 typePolicies는 다음에 보자)
  • 3. makeVar()로 responsive variable을 생성한다. responsive variable은 local state를 읽고 수정하기 위한 기능이다.
  • *** 여기서 local state는 graphql schema에는 없지만 apollo에서 다룰 값을 의미한다. 로그인 상태나 다크모드 등이 있다.
  • 4. cache에서 typePolicies를 이용해 resVar(3)을 read함수로 정의해서 반환하도록 한다.
  • NOTE!! 사실 makeVar만 사용해도 local state를 사용할 수 있다. 하지만 typePolicies를 같이 사용하는 이유는 graphql server에서 받아오는 데이터와 함께 사용할 수 있기 때문인 듯하다. (apollo blog에서도 해당 방법을 이용하는 것이 local state를 관리하기에 더 편한 이유라고 설명하고 있다.)

2. apollo code generation 적용하기

code:gen을 적용하는 이유는 GraphQL서버의 Schema의 타입을 apollo에서 지키도록 하기 위함이다. 처음에는 번거로운 일이 아닌가했는데 한번 적용하고 나니 훨씬 편리하고 실수가 적어졌다.

2-1. 먼저 apollo를 설치한다.

yarn add global apollo

2-2. apollo.config.js

module.exports = {
  client: {
    includes: ["./src/**/*.tsx"],
    tagName: "gql",
    service: {
      name: "something",
      url: "graphql server",
    },
  },
};

3. package script에 적용

"apollo": "rm -rf src/__api__ && apollo client:codegen src/__api__ --target=typescript --outputFlat",
"start": "yarn apollo:codegen & react-scripts start",
    

script를 실행하면 사용된 Query나 Mutation의 type을 정의한 파일을 생성한다.

3. Query 사용예시 : 사용자의 정보를 받아오는 커스텀 훅 useClient()

import { gql, useQuery } from '@apollo/client';
import { clientQuery } from '../__api__/meQuery';

const CLIENT_QUERY = gql`
  query clientQuery {
    client {
      id
      email
    }
  }
`;

export const useClient = () => {
  return useQuery<clientQuery>(CLIENT_QUERY);
  // {data, loading, error} = useClient();
};

4. Mutation 사용예시 :

import { gql, useMutation } from '@apollo/client';
//created by codegen
import { signupMu, signupMuVariables } from '../__api__/signupMu'; 
import { UserRole } from '../__api__/globalTypes';

const SIGNUP_MUTATION = gql`
  mutation signupMu($input: SignupInput!) {
    signUp(input: $input) {
      ok
      error
    }
  }
`;
// output : data{signUp{error, ok}}

export const Signup = () => {
  const onCompleted = (data: signupMu) => {
    //logic ...
  };
  const [signupMu, { data: isSignup, loading }] = useMutation<signupMu, signupMuVariables>(SIGNUP_MUTATION, {
    onCompleted,
  });

  const onSubmit = () => {
    if (!loading) {
      signupMu({
        variables: {
          input: {
            email,
            password,
          },
        },
      });
    }
  };
  return ( ... )
}

5. Cache update 사용예시

먼저 Apollo extension으로 캐시를 확인한다.

import { gql, useApolloClient, useMutation } from '@apollo/client';

export const ConfirmEmail = () => {
  const client = useApolloClient();
  const onCompleted = (data: Type) => {
    client.writeFragment({
      id: `User:${user?.me.id}`,
        fragment: gql`
          fragment Something on User {
            verified
        }`,
      data: {
        verified: true,
      },
  });
}
  • id는 수정할 캐시를 식별하도록 하는데 위의 이미지에서 분홍색 박스에 해당한다.
  • fragment에 수정할 속성(key), data에는 실제로 수정할 값을 넣는다.

Leave a comment

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다