import { InMemoryCache, ApolloClient } from '@apollo/client';
const client = new ApolloClient({
// ...other arguments...
cache: new InMemoryCache(options)
});
type TypePolicy = {
keyFields?: KeySpecifier | KeyFieldsFunction | false;
queryType?: true;
mutationType?: true;
subscriptionType?: true;
fields?: {
[fieldName: string]: FieldPolicy<any> | FieldReadFunction<any>;
};
};
type FieldPolicy<TExisting = any, TIncoming = TExisting, TReadResult = TExisting> = {
keyArgs?: KeySpecifier | KeyArgsFunction | false;
read?: FieldReadFunction<TExisting, TReadResult>;
merge?: FieldMergeFunction<TExisting, TIncoming> | boolean;
};
keyArgs : Query 자료형의 하위 필드에서 자주 쓰이는 항목으로 필드의 인수를 추가 분류 기준으로 설정할 수 있다. keyFields는 자료형을 구분하는 기준이고, keyArgs는 필드를 구분하는 기준으로 사용한다.
read(), merge() : 이 함수를 통해 필드를 읽거나 필드에 새로운 값을 쓰는 과정을 세부적으로 설정할 수 있다.
client = new ApolloClient({
link: authLink.concat(httpLik),
cache: new InMemoryCache({
typePolicies: {
HLBrand: {
keyFields: ["ID"],
merge: true,
fields: {
links: {
keyArgs: ["input", ["filter", "sort"]],
merge(
existing: Links_brand_links,
incoming: Links_brand_links,
) {
if (!existing) return incoming;
return {
...incoming,
list: [...existing.list, ...incoming.list],
};
},
},
},
},
},
}),
});
typePolicies: {
Query: {
fields: {
feed: {
keyArgs: ["type"],
merge(existing, incoming, {
args: { cursor },
readField,
}) {
const merged = existing ? existing.slice(0) : [];
let offset = offsetFromCursor(merged, cursor, readField);.
if (offset < 0) offset = merged.length;
for (let i = 0; i < incoming.length; ++i) {
merged[offset + i] = incoming[i];
}
return merged;
},
read(existing, {
args: { cursor, limit = existing.length },
readField,
}) {
if (existing) {
let offset = offsetFromCursor(existing, cursor, readField);
if (offset < 0) offset = 0;
return existing.slice(offset, offset + limit);
}
},
},
},
},
},
});
const [addBrandTagToLinkMutation] = useMutation<
AddBrandTagToLinkVariables,
AddBrandTagToLink
>(addBrandTagToLinkMutationDoc, {
update(cache, { data: result }) {
if (result?.addBrandTagToLink) {
cache.modify({
id: cache.identify({ __typename: "HLLink", ID: linkID }),
fields: {
brandTags(existing: Links_brand_links_list_brandTags[]) {
return [...existing, { ...result?.addBrandTagToLink }];
},
},
});
}
},
});
캐시에는 기본적으로 ROOT_QUERY 공간이 존재하는데 따로 설정하지 않을시 이곳에 쿼리 결과가 캐싱된다.
ROOT_QUERY 안에 __ref 항목처럼 캐시 레퍼런스로 저장된 데이터는 직접 읽을 수 없고 apollo client에서 제공하는 readField 함수를 사용해야한다
apollo 캐시는 기본적으로 typename 필드를 가진 객체마다 UID를 자동으로 생성해주는데 만약 해당 객체에 id 나 _id 필드가 존재하면 UID는 typename:id 로 설정되고 그런 필드가 없으면서 keyField도 정의되지 않는다면 캐시에 따로 저장되지 않고 ROOT_QUERT에 값으로 저장된다. 반환된 데이터의 효율적인 캐싱을 위해 요청할 때 클라이언트측에서 각 객체 필드에 id 필드를 넣어주는 것이 좋다.
만약 서버에서 각 자료형에 대해 id 필드를 지원하지 않거나 테이터 분류 기준을 세부적으로 설정하고 싶으면 keyFields를 이용할 수 있다.
동일한 key 값을 가지는 객체는 서로 병합되며 keyFields는 문자열 배열 문자열 배열을 반환하는 함수, false로 설정할 수 있다.
const cache = new InMemoryCache({
typePolicies: {
Person: {
fields: {
name: {
read(name) {
// Return the cached `name`, transformed to upper case
return name.toUpperCase();
},
},
},
},
},
});
import { useMemo } from "react";
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
import merge from "deepmerge";
let apolloClient: ApolloClient<NormalizedCacheObject> = null;
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: new HttpLink({
uri: "<https://nextjs-graphql-with-prisma-simple.vercel.app/api>", // 서버 URL (상대 주소가 아닌 절대 주소를 써야한다.)
credentials: "same-origin", // `credentials`나 `headers`같은 추가적 fetch() 옵션
}),
cache: new InMemoryCache(),
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
// Next.js에서 Apollo Client를 이용해 데이터를 가져오는 함수가 있다면, 초기 상태값이 여기에서 합쳐진다.
if (initialState) {
// 클라이언트에서의 받은 데이터인 현재 캐시 데이터를 가져온다.
const existingCache = _apolloClient.extract();
// 현재 캐시와 SSR 메소드인 getStaticProps/getServerSideProps로 부터 받은 데이터를 합친다.
const data = merge(initialState, existingCache);
// 합쳐진 데이터를 저장한다.
_apolloClient.cache.restore(data);
}
// SSG(Server Side Generation)와 SSR(Server Side Rendering)은 항상 새로운 Apollo Client를 생성한다.
if (typeof window === "undefined") return _apolloClient;
// 클라이언트의 Apollo Client는 한 번만 생성한다.
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}
import { ALL_POSTS_QUERY } from "@/~";
import { allPostsQueryVars } from "@/~";
import { initializeApollo } from "../lib/apolloClient";
const Home = () => {
// SSR에서 사용한 query와 같은 query(ALL_POSTS_QUERY)를 사용
const { loading, error, data } = useQuery(ALL_POSTS_QUERY, {
variables: allPostsQueryVars,
});
};
export const getServerSideProps: GetServerSideProps<{}, {}> = async (ctx) => {
/*
1. 투표 등 실시간성 데이터가 계속 업데이트 되어야하는 경우 getStaticProps와 revalidate를 이용
2. 일반적인 경우 getServerSideProps를 이용
*/
// browser단의 context(headers)를 SSR에 넘기는 과정
const apolloClient = initializeApollo(null, ctx);
await apolloClient.query({
query: ALL_POSTS_QUERY,
variables: allPostsQueryVars,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
},
};
};
export default Home;