글 작성자: 택시 운전사
반응형

Apollo Client

Apollo Client는 GraphQL을 이용하여 로컬과 리모트 데이터를 다룰 수 있는 자바스크립트의 포괄적인 상태 관리 라이브러리이다. 이를 이용해서 데이터를 가져오거나, 캐시, 애플리케이션 데이터의 수정 이 모든 것을 UI를 업데이트하면서 할 수 있다.

Apollo Client는 현대의 개발 방식과 동일하게 코드를 경제적이고, 예상가능하며 선언적인 방식으로 짤 수 있게 해준다. @apollo/client 라이브러리는 리액트와의 통합성을 제공하며, 다른 유명 라이브러리/프레임워크를 위한 통합도 거대한 아폴로 커뮤니티를 통해 유지되고 있다.

특징

  • 선언적인 데이터 패칭: 직접 로딩 상태를 따라갈 필요 없이 쿼리를 작성하고 데이터를 가져올 수 있다.
  • 휼륭한 개발자 경험: 타입스크립트, 크롬 / 파이어폭스 개발자 도구, VS Code 등을 위한 편의 도구를 제공해준다.
  • 최신 리액트를 위해 디자인된 라이브러리: 최신 리액트 문법인 React Hooks를 지원하는 useQuery, useMutation을 사용하는 등 리액트와 찰떡궁합
  • 점진적 적용 가능성: 아폴로를 자바스크립트 앱에 몇 방울 뿌려가면서 기능별로 적용해나갈 수 있다.
  • 보편적 호환성: 어떠한 빌드 설정이나 어떠한 GraphQL API도 사용할 수 있다.
  • 커뮤니티 중심: 관련 지식을 GraphQL 커뮤니티에 잇는 수 많은 개발자들과 나눌 수 있다.

왜 Apollo Client인가?

왜 데이터를 다루는 데, Apollo Client를 선택해야하는가?

데이터 관리는 자고로 어려워선 안된다고 생각한다! 만약 리액트 앱에서 리모트와 로컬 데이터를 어떻게하면 간단하게 관리할 수 있는 지 궁금하다면, 잘 왔다. 해당 문서를 통해, 아폴로의 똑똑한 캐싱과 데이터 패칭에 대한 선언적 접근이 어떻게 코드는 적게 사용하면서 반복적인 작업은 도울 수 있는 지 배우게 될 것이다. 바로 출발해보자! 🚀

선언적 데이터 패칭

데이터 패칭에 대한 아폴로의 선언적 접근을 통해, 데이터를 받는 것, 로딩과 에러 상태를 추적하는 것 그리고 UI를 업데이트하는 것까지 모든 로직을 useQuery 훅을 통해 캡슐화할 수 있다. 이 캡슐화를 통해 쿼리 결과를 프레젠테이션 컴포넌트에 쉽게 통합할 수 있다! Apollo Client와 리액트를 통해 실제로 어떤 모습인지 알아보자:

function Feed() {
  const { loading, error, data } = useQuery(GET_DOGS);
  if (error) return <Error />;
  if (loading) return <Fetching />;

  return <DogList dogs={data.dogs} />;
}

여기서는 useQuery 훅을 사용하여 GraphQL 서버에서 몇 개의 개 데이터들을 가져와 목록에 표시하고 있다. useQuery는 리액트의 Hooks API를 활용하여 쿼리를 구성 요소에 바인딩하고 쿼리 결과에 따라 렌더링한다. 데이터가 반환되면 구성 요소는 필요한 데이터에 따라 반응적으로 업데이트된다.

Apollo Client는 로딩 및 오류 상태 추적을 포함하여 처음부터 끝까지 요청의 사이클을 처리한다. 첫 번째 요청 전에는 작성할 미들웨어나 상용 소프트웨어가 없으며, 응답의 변환 및 캐싱에 대해 걱정할 필요가 없다. 컴포넌트에 필요한 데이터를 설명하고 Apollo Client가 어려운 작업을 수행할 수 있도록 하면 된다. 💪

Apollo Client로 전환하면 데이터 관리와 관련된 불필요한 코드를 많이 삭제할 수 있다. 애플리케이션에 따라 정확한 양은 달라지겠지만, 일부 팀에서는 최대 수천 코드를 줄였다고 한다. 여러분이 아폴로로 코드를 덜 쓰겠지만, 그것이 여러분이 기능들과 타협해야 한다는 것을 의미하지 않는다! useQuery 옵션을 통해 옵티미스틱 UI, 리페칭 및 페이지네이션과 같은 고급 기능도 모두 쉽게 사용할 수 있다.

설정이 전혀 필요없는 캐싱

Apollo Client가 다른 데이터 관리 솔루션과 차별화되는 주요 기능 중 하나는 표준화된 캐시이다. Apollo Client에는 지능형 캐시가 포함되어 있어, 초기 구성이 거의 필요없는 수준이다.

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

const client = new ApolloClient({
  cache: new InMemoryCache()
});

그래프를 캐싱하는 것은 쉬운 일은 아니지만, Apollo Client 팀은 그래프 해결에 2년을 집중했다고 한다. 여러 경로를 통해 동일한 데이터를 얻을 수 있으므로 여러 컴포넌트에 걸쳐 데이터를 일관되게 유지하려면 정규화가 필수적이다. 몇 가지 실제 사례를 살펴보자.

const GET_ALL_DOGS = gql`
  query GetAllDogs {
    dogs {
      id
      breed
      displayImage
    }
  }
`;

const UPDATE_DISPLAY_IMAGE = gql`
  mutation UpdateDisplayImage($id: String!, $displayImage: String!) {
    updateDisplayImage(id: $id, displayImage: $displayImage) {
      id
      displayImage
    }
  }
`;

쿼리 GET_ALL_DOGS는 개의 목록과 개의 displayImage를 가져온다. UPDATE_DISPLAY_IMAGE이라는 뮤테이션은 단일 개의 displayImage를 업데이트한다. 만약 특정 개의 displayImage를 업데이트 한다면, 새로운 데이터를 반영하기 위해 모든 개의 목록에 있는 해당 항목이 필요하다. Apollo Client는 __typenameid 속성이 있는 GraphQL 결과의 각 개체를 Apollo 캐시에서 자체 항목으로 분할한다. 이렇게 하면 id가 있는 뮤테이션에서 값을 반환하면 동일한 id로 개체를 가져오는 모든 쿼리가 자동으로 업데이트된다. 또한 동일한 데이터를 반환하는 두 개의 쿼리가 항상 동기화되도록 한다.

일반적으로 실행이 복잡한 기능은 아폴로 캐시를 사용하여 구축하기에는 매우 간단하다. 개 목록을 표시하는 이전 예제의 GET_ALL_DOGS 쿼리로 돌아가 보자. 특정 개의 세부 페이지로 전환하고 싶다면 어떻게 해야 할까? 이미 각 개의 정보를 가져왔기 때문에 서버에서 동일한 정보를 다시 가져오고 싶지는 않을 것이다. Apollo Client의 캐시 정책 API 덕분에 우리는 두 쿼리 사이에 개를 연결할 수 있으므로 이미 사용 가능한 정보를 가져올 필요가 없다.

개 한 마리에 대한 정보를 가져오는 쿼리는 다음과 같다.

const GET_DOG = gql`
  query GetDog {
    dog(id: "abc") {
      id
      breed
      displayImage
    }
  }
`;

여기서는 캐시된 Dog 데이터에 대한 참조를 반환하는 사용자 지정 FieldPolicy를 정의한다. 이렇게 하면 쿼리에서 해당 데이터를 검색하는 데 사용할 수 있다.

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

const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        dog(_, { args, toReference }) {
          return toReference({
            __typename: 'Dog',
            id: args.id,
          });
        }
      }
    }
  }
});

const client = new ApolloClient({ cache });
반응형