react-native 어플리케이션에 Apollo client 통합하기 #1
dooboolab 님께서 작성한 Integrate apollo client in react-native app #1 글의 번역입니다. 피드백 환영합니다.
이 포스트는 react native application 에서 graphql API를 사용하기 위해 apollo client 를 통합하는 과정을 다루고 있습니다.
우선, Apllo 와 graphql 이 제공하는 필수 패키지를 설치합니다.
yarn add @apollo/react-hooks apollo-boost apollo-link apollo-link-context apollo-link-error apollo-link-ws apollo-utilities graphql
다음은 블로그 작성 시점에 설치한 패키지 버전입니다.
"@apollo/react-hooks": "^3.1.3",
"apollo-boost": "^0.4.7",
"apollo-link": "^1.2.13",
"apollo-link-context": "^1.0.19",
"apollo-link-error": "^1.1.12",
"apollo-link-ws": "^1.0.19",
"apollo-utilities": "^1.3.3",
"graphql": "^14.5.8",
프로젝트 내부에서 동일한 버전의 graphql
버전을 사용하기 위해 package.json
에 resolutions
항목을 추가하는 것이 좋습니다. 이미 설치한 다른 패키지와 서로 다른 버전의 graphql
을 사용하면 부수효과로 인해 버그가 발생할 수도 있습니다.
"resolutions": {
"graphql": "14.5.8"
}
@apollo/react-hooks
를 컴포넌트 내부에서 사용하기 위해서 부모 컴포넌트를 ApolloProvider
로 감싸줍니다. 과거에는 @trojanowski 의 react-apollo-hooks
패키지를 사용하여 이런 형태를 구현할 수 있었지만, 이제는 Apollo 에서 공식적으로 구현하고 있습니다.
<ApolloProvider client={client}>
<App />
</ApolloProvider>
위 코드에서 눈여겨 봐야하는 것은, ApolloProvider
에 전달하는 client
입니다. 이제부터 이 변수에 대해 이야기 해보겠습니다.
ApolloProvider
에는 client = new ApolloClient
와 같이 ApolloClient
클래스 변수를 전달해 주어야 합니다.
Apollo
는 ApolloClient
를 초기화 하는 다양한 방법을 제공해주고 있으며, 이를 통해 client.ts
라는 별도의 파일로 따로 분리하여 ApolloClient
를 초기화 하겠습니다.
- graphql API 의 엔드포인트에 접속하기 위한 http link 생성하기
const httpLink = new HttpLink({
uri: `${GRAPHQL_URL}`,
});
2. subscription에 사용할 web socket link 생성하기
const wsLink = new WebSocketLink({
uri: `ws://${GRAPHQL_URL}`,
options: { reconnect: true },
});
3. 보다 나은 성능을 위해 Apollo
가 제공하는 InMemoryCache
를 사용해 보겠습니다. fragments 와 함께 query
나 mutation
쿼리로 동일한 결과값을 얻을 때 이 값들을 캐싱할 수 있어서 유용합니다.
const cache = new InMemoryCache();
4. 네번째 단계는 대부분의 어플리케이션이 설정하는 부분이기 때문에 상당히 유용합니다. 대다수의 어플리케이션들이 auth flow
를 다루기 때문에, Apollo
는 context
개념을 사용하는 미들웨어를 설정할 수 있도록 제공합니다.
이 방법은 React
와는 조금 다를수도 있습니다. React
에서는 AsyncStorage
대신 브라우저 캐시를 사용할 수도 있기 때문입니다.authLink
를 만든 후에 앞서 만든 httpLink
를 연결합니다.
const authLink = setContext(async (_, { headers }) => {
const token = await AsyncStorage.getItem('token');
return {
headers: {
...headers,
Authorization: token,
},
};
});const httpAuthLink = authLink.concat(httpLink);
5. (선택사항 — 사실 앞서 설명한 단계들도 선택사항으로 볼 수 있지만, 개인적으로 굉장히 필수적인 부분이라 생각합니다.)
이 단계에서는 에러 로그를 제공하기 위해 errorLink
를 추가합니다. 이 에러 로그들은 엔드포인트에서 예상치 못한 결과값이 나왔을 때 유용하게 사용할 수 있습니다. 별도의 패키지로 제공하는 apollo-link-error 로도 같은 기능을 수행할 수 있습니다.
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) =>
// eslint-disable-next-line no-console
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
}
// eslint-disable-next-line no-console
if (networkError) console.log(`[Network error]: ${networkError}`);
});
6. subscription
과 다른 graphql
쿼리들을 구분하기 위해, apollo-link 에서 제공하는 split
함수를 사용합니다. 여기서는 2단계에서 만든 websocket link 와 4단계에서 만든 httpAuthLink 를 사용합니다. 여기서 주의해야 할 점은, 아래 코드와 같이 반드시 httpAuthLink 를 최하단에 위치시켜주어야 한다는 것입니다. httpAuthLink 위치와 관련된 몇몇 이슈들이 있기 때문에 꼭 지켜주도록 합니다.
const link = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpAuthLink,
);
7. 마지막으로, 앞에서 설정했던 값들로 ApolloClient
를 생성하고 export 합니다.
export default new ApolloClient({
link: ApolloLink.from([
errorLink,
link,
]),
cache,
});
어플리케이션에 Apollo
클라이언트를 통합하는 것은 다소 혼란스러운 과정일 수 있습니다. 통합하는 방법이 여러가지가 있으며 패키지들 또한 나누어져 있기 때문입니다. 이것은 다양성을 주면서도 과정을 난해하게 만드는 양날의 검과 같습니다. 하지만 사용하기 전에 이 모든 것을 전부 이해해야 하는 것은 아니기 때문에 걱정하지 않아도 좋습니다. 또한, 다른 좋은 예시들을 찾아볼 수도 있습니다.
지금까지 이야기한 것들을 다시 정리하여 목록으로 요약해보겠습니다.
query
와mutation
을 제공하는graphql
api 사용을 위해Apollo
클라이언트를 어플리케이션에 통합한다.- 사용자가 이미 인증을 거쳤다면, header 정보와 함께
graphql
쿼리를 요청하기 위해auth flow
기능을 추가한다. subscription
쿼리 사용을 위해 web socket 을 결합한다.- in-memory caching 을 사용한다
- 디버깅을 위해
errorLink
를 추가한다.
코드가 어떻게 작동하는지 이해했다면, 아래 코드를 단순히 복사 붙여넣기 하여 사용할 수 있습니다.
또한, 더 나은 best practice 가 있다면 Client.ts
를 더 발전시킬 수 있도록 공유해주세요!