Apollo Client의 캐싱(Caching)
캐싱(Caching)이란 무엇일까?
캐싱은 캐시(Cache)라는 임시 저장소에 데이터를 저장해놓아, 동일한 데이터를 반복해서 요청할 경우 굳이 네트워크를 통하지 않고 데이터를 불러올 수 있게 하는 방법이다. 데이터의 양이 많아질수록 캐시를 어떻게 활용하느냐에 따라 서비스의 메모리 부하에 큰 차이가 생긴다.
GraphQL 통신 라이브러리인 Apollo Client는 자체적으로 캐싱을 지원한다.
예를 들어서 "id가 5인 Book의 정보를 API 읽어와줘!" 라는 요청을 백엔드에 보낸다고 가정해보자.
Apollo client는 먼저 InMemoryCache라는 캐시 저장공간 안에 id가 5인 Book의 정보가 있는지 찾는다. 그리고 캐시 안에 정보가 있을 경우에는 캐시 안에서 데이터를 불러오고, 캐시 안에서 정보를 찾을 수 없는 경우에는 GraphQL 서버로 query 요청을 보낸다.
이 때, 이 캐시 저장공간은 다음과 같은 형태로 만들 수 있다. (리액트의 경우)
// index.js
// 출처: Apollo/client 공식 문서
import React from 'react';
import * as ReactDOM from 'react-dom/client';
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
import App from './App';
const client = new ApolloClient({
uri: 'GraphQL API endpoint',
cache: new InMemoryCache(),
});
// Supported in React 18+
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
);
데이터 통신이 이루어질 영역을 ApolloProvider로 감싸준 다음, API의 uri와 cache 정보를 담은 ApolloClient 인스턴스를 새로 만들어서 ApolloProvider와 바인딩해준다. 이렇게 하면 new InMemoryCache()를 이용해 생성한 저장공간에 데이터를 캐싱할 수 있다.
Next.js에서는
문제는 Next.js에서 발생했다.
데이터 query시 캐시에서 우선 데이터를 찾도록 설정해두었음에도 불구하고 내가 의도하는대로 작동하지 않았다.
- fetchPolicy는 cache-first로 설정해두었음
- cache에 데이터가 잘 들어오는 것도 육안으로 확인했음 (with Apollo dev tools)
- 하지만 동일한 useQuery 요청 시 데이터를 캐시에서 받아오지 않음! 네트워크에 새로운 요청이 찍힌다.
리액트와 넥스트의 렌더링 구조 차이때문에 발생하는 문제였다.
React의 경우, 라우터 이동시 App 컴포넌트 자체는 다시 렌더링되지 않는다. Route 경로가 지정되어있는 컴포넌트들이 교체될 뿐이다.
// React 환경
// App.js
import { Route, Routes, Link } from 'react-router-dom';
import Hello from './Hello';
import Home from './Home';
import About from './About';
function App() {
console.log("app 컴포넌트 렌더링!")
return (
<div>
<Routes>
<Route path="/" element={<Hello />}/>
<Route path="/home" element={<Home />}/>
<Route path="/about" element={<About />}/>
</Routes>
<div>
<Link to="/home">홈으로</Link>
<Link to="/about">About으로</Link>
<Link to="/">메인으로</Link>
</div>
</div>
);
}
export default App;
콘솔을 찍어보면 리액트의 app 컴포넌트는 서비스 최초 진입시에만 렌더링되는 것을 확인할 수 있다.
반면 Next.js의 경우, 라우터 이동시에도 App컴포넌트가 리렌더링된다.
// Next.js 환경
// _app.js
import Layout from "../src/components/commons/layout";
import { useRouter } from "next/router";
function MyApp({ Component, pageProps }) {
const router = useRouter();
console.log("app 컴포넌트 렌더링!!", "현재 위치는", router.asPath);
return (
<>
<Layout>
<Component {...pageProps} />
</Layout>
</>
);
}
export default MyApp;
라우터 이동시마다 app 컴포넌트가 리렌더링 되는 것을 볼 수 있다.
그런데 처음에는 이걸 모르고 app 컴포넌트 내부에서 new InMemoryCache()를 선언했다.
즉, 라우터 이동시마다 캐시를 재생성하고 있었던것.
new InMemoryCache()를 app 컴포넌트 바깥에서 해주면 해당 문제를 해결할 수 있다.
변경 전
// Next.js 환경
// _app.js
import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";
import Layout from "../src/components/commons/layout";
import { useRouter } from "next/router";
function MyApp({ Component, pageProps }) {
const router = useRouter();
console.log("app 컴포넌트 렌더링!!", "현재 위치는", router.asPath);
const client = new ApolloClient({
uri: "GraphQL API endpoint",
cache: new InMemoryCache(),
})
return (
<>
<ApolloProvider client={client}>
<Layout>
<Component {...pageProps} />
</Layout>
</ApolloProvider>
</>
);
}
export default MyApp;
변경 후
// Next.js 환경
// _app.js
import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";
import Layout from "../src/components/commons/layout";
import { useRouter } from "next/router";
const APOLLO_CACHE = new InMemoryCache();
function MyApp({ Component, pageProps }) {
const router = useRouter();
console.log("app 컴포넌트 렌더링!!", "현재 위치는", router.asPath);
const client = new ApolloClient({
uri: "GraphQL API endpoint",
cache: APOLLO_CACHE,
})
return (
<>
<ApolloProvider client={client}>
<Layout>
<Component {...pageProps} />
</Layout>
</ApolloProvider>
</>
);
}
export default MyApp;
References
https://www.apollographql.com/docs/react/caching/overview/
https://www.apollographql.com/docs/react/get-started#step-4-connect-your-client-to-react
그리고 우리 God 코드캠프 노원두 팀장님 👍👍👍👍
'React, Next.js' 카테고리의 다른 글
[React] setState - prevState (prev 인자) (0) | 2022.02.16 |
---|---|
[React] 컴포넌트 생명주기 - Component Life Cycle (with useEffect) (0) | 2022.02.09 |
[React] 클래스형 컴포넌트 vs 함수형 컴포넌트 (0) | 2022.02.09 |
[React] 자주 쓰는 React 기반 라이브러리, 프레임워크 모음 (0) | 2022.02.05 |
[React/Typescript] Eslint / prettier 설치 및 기본 세팅 (0) | 2022.01.24 |