본문 바로가기

개발/React & React Native

[React Native] 리액트 네이티브에서 Apollo Client로 GraphQL 사용하기 (with. SpaceX)

728x90

 

 

Apollo Client란?

Apollo Client는 GraphQL API와의 통신을 간편하게 도와주는 클라이언트 라이브러리이다.

  • 쿼리(Query)와 뮤테이션(Mutation) 요청: 클라이언트에서 원하는 데이터를 서버에 요청하거나 변경 작업 수행
  • 캐싱: 서버에서 가져온 데이터를 클라이언트 측에 캐싱하여 네트워크 요청을 최소화
  • 서버 상태 관리: 애플리케이션의 로컬 상태와 서버 상태를 통합적으로 관리

 

REST API와의 차이점은 이전 포스팅을 참고!

 

REST API와 GraphQL의 차이점 알아보기

REST API vs GraphQL API개발에서 데이터를 전달하고 처리하는 방법은 매우 중요하다.REST API는 오랜 시간 동안 표준으로 자리 잡아 왔고 GraphQL API의 등장 이후로 해당 방식을 채택하는 기업이 늘고 있

ramveloper.tistory.com

 

 

 

 

 

Apollo Client 기본 설정

https://www.apollographql.com/docs/react/integrations/react-native

 

Integrating with React Native

Search Apollo content (Cmd+K or /)

www.apollographql.com

 

 

apollo/client 설치

npm install @apollo/client graphql

 

 

Apollo Client를 설정하려면 다음과 같이 GraphQL 서버의 URL캐시 설정이 필요하다.

// apolloClient.ts

import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://spacex-production.up.railway.app/graphql',
  cache: new InMemoryCache(),
});

export default client;

 

 

ApolloProvider를 사용해 Apollo Client를 애플리케이션 전체에서 사용할 수 있도록 설정한다.

// App.tsx

import React from 'react';
import { ApolloProvider } from '@apollo/client';
import client from './src/apolloClient';
import MainScreen from './src/MainScreen';

const App = () => {
  return (
    <ApolloProvider client={client}>
      <MainScreen />
    </ApolloProvider>
  );
};

export default App;

 

 

 

 

 

 

GraphQL 요청 처리

GraphQL 쿼리를 정의하려면 Apollo Client에서 제공하는 gql 태그를 사용한다.

예시 코드는 SpaceX API에서 과거 로켓 발사 정보를 가져오는 쿼리이다.

페이지네이션을 구현하기 위해 limit, offset 변수를 추가해 주었다.

(https://studio.apollographql.com/public/SpaceX-pxxbxen/variant/current/home 참고)

import { gql } from '@apollo/client';

const GET_LAUNCHES = gql`
  query GetAllLaunches($limit: Int!, $offset: Int!) {
    launches(limit: $limit, offset: $offset) {
      id
      mission_id
      mission_name
      launch_date_utc
      launch_success
      rocket {
        rocket_name
        rocket_type
      }
      launch_site {
        site_name_long
      }
      links {
        flickr_images
        video_link
        mission_patch
      }
      details
    }
  }
`;

 

 

React 컴포넌트에서 데이터를 가져오려면 Apollo Client의 useQuery 훅을 사용한다.

이 훅은 로딩 상태와 에러, 데이터를 반환한다.

import { useQuery } from '@apollo/client';

const useAllLaunches = (limit: number, offset: number) => {
  return useQuery<LaunchesPastData>(GET_ALL_LAUNCHES, {
    variables: {limit, offset},
    fetchPolicy: 'cache-and-network',
  });
};

 

 

fetchPolicy
'cache-and-network'로 설정하여 Apollo Client가 먼저 캐시에서 데이터를 반환하고 네트워크 요청으로 최신 데이터를 가져와 빠른 응답과 최신 데이터 보장이 가능하도록 했다.

이 외의 apollo client에서 지원되는 fetch policy의 정보는 다음과 같다.

 

 

반환 데이터 타입
Typescript를 사용하기 때문에 데이터 타입은 LaunchesPastData 인터페이스를 생성 및 정의해 반환 데이터 구조를 안전하게 관리하도록 했다.

 

 

 

 

 

 

fetchMore와 updateQuery 활용

import React, {useCallback, useEffect, useState} from 'react';
import {
  FlatList,
  ListRenderItem,
  ListRenderItemInfo,
  StyleSheet,
  View,
} from 'react-native';

import {Props, ScreenName} from 'views';
import {Launch} from 'models';
import {LaunchesAPI} from 'network/LaunchesAPI';
import {Colors} from 'styles';
import LaunchItem from './LaunchItem';
import Loading from 'views/commons/Loading';

const PAGE_SIZE = 50;

const MainScreen = ({
  navigation,
}: Props<ScreenName.MainScreen>): React.ReactElement => {
  const [launches, setLaunches] = useState<Launch[]>([]);
  const [isLoading, setIsLoading] = useState<boolean>(false);

  const {data, loading, fetchMore} = LaunchesAPI.useAllLaunches(PAGE_SIZE, 0);

  const loadMoreData = useCallback(async () => {
    if (loading) {
      return;
    }
    setIsLoading(true);

    try {
      await fetchMore({
        variables: {
          offset: launches.length,
          limit: PAGE_SIZE,
        },
        updateQuery: (prev, {fetchMoreResult}) => {
          if (!fetchMoreResult) {
            return prev;
          }

          const newLaunches = fetchMoreResult.launches.filter(
            newLaunch =>
              !prev.launches.some(prevLaunch => prevLaunch.id === newLaunch.id),
          );

          return {
            launches: [...prev.launches, ...newLaunches],
          };
        },
      });
    } finally {
      setIsLoading(false);
    }
  }, [launches.length, loading, fetchMore]);

  const appendData = useCallback(() => {
    if (data && data.launches) {
      setLaunches(prevLaunches => [...prevLaunches, ...data.launches]);
    }
  }, [data]);

  useEffect(() => {
    appendData();
  }, [appendData]);

  // ...

  return (
    <View style={styles.container}>
      <FlatList
        keyExtractor={(item, index) => `${item.id}_${index}`}
        style={styles.list}
        data={launches}
        numColumns={2}
        renderItem={renderLaunchItem}
        onEndReached={loadMoreData}
        onEndReachedThreshold={1}
      />
      {(loading || isLoading) && <Loading />}
    </View>
  );
};

const styles = StyleSheet.create({
  // ...
});

export default MainScreen;

 

  • fetchMore: fetchMore는 Apollo Client의 기능으로, 기존 쿼리의 변수 값을 변경하거나 추가 데이터를 요청하여 기존 결과와 병합할 수 있도록 도와준다. 위 코드에서는 새로운 데이터를 요청하고 기존 데이터에 결합하여 페이지네이션 기능에 활용했다.
  • updateQuery: fetchMore가 반환한 데이터를 기존 Apollo 캐시에 저장된 데이터와 병합하는 방식을 정의한다. 위 코드에서 중복 데이터를 제거하고 새로운 데이터를 기존 데이터 뒤에 추가하는 로직이 포함되어 있다.

 

 

 

 

화면

 

FlatList의 numColumns를 활용하여 Grid 형식으로 리스트를 보여주었다.

최신순으로 보여주고 싶었지만 현재 SpaceX의 GraphQL API들이 관리가 이루어지지 않는지 order와 sort변수가 적용되지 않아 부득이하게 과거 날짜부터 보여지도록 설정했다.

 

REST API의 Over-fetching과 Under-fetching 문제가 해소되어 클라이언트 단에서 유연하게 데이터의 형태를 정의할 수 있어 활용도가 매우 높은 것 같다!😄

 

 

300x250