開発環境
この記事の環境の下で行います。
一箇所にまとめて置きたいので下記のように src/apollo/hooks
ディレクトリーを作成します。
※ apollo3-cache-persist
を使うとcache > persist > client 順に実行することが大事で、エラー処理も含めると少し複雑になるので useXXXApolloClient
というカスタムフックを作成し、環境を作っていきます。
// apollo ディレクトリー構造: 現段階では⬇︎のように作成 try🐶everything myproject$ tree src src ├── apollo │ ├── cache.js │ └── hooks │ ├── index.ts │ ├── useCachePersistorApolloClient.tsx │ └── usePersistCachedApolloClient.tsx ...
ApolloClient を追加する
依存関係をインストールする
Apollo Client を使用するアプリには、次の 2 つのトップレベルの依存関係が必要です。
- @apollo/client: この 1 つのパッケージには、Apollo クライアントのセットアップに必要な事実上すべてが含まれています。 これには、メモリ内キャッシュ、ローカル状態管理、エラー処理、および React ベースのビュー レイヤーが含まれます。
- graphql: このパッケージは、GraphQL クエリを解析するためのロジックを提供します。
次のコマンドを実行して、これらのパッケージの両方をインストールします。
try🐶everything myproject$ npm install @apollo/client graphql
ApolloClient を初期化する
依存関係を設定したら、ApolloClient インスタンスを初期化できるようになりました。
app/_layout.tsx
に ApolloProvider をラップします。⬇︎(Line 3、8-11、14、19を追加)
// app/_layout.tsx ... import {ApolloClient, InMemoryCache, ApolloProvider} from '@apollo/client'; // <-- 追加する ... function RootLayoutNav() { const colorScheme = useColorScheme(); const client = new ApolloClient({ // <-- client は 最終的にはカスタムフックに移動される uri: 'https://flyby-router-demo.herokuapp.com/', cache: new InMemoryCache(), }); return ( <ApolloProvider client={client}> <PaperProvider theme={colorScheme === 'dark' ? MD3DarkTheme : _MD3LightTheme}> ... </PaperProvider> </ApolloProvider> ); }
console.log(client)
コマンドなどを実行し、エラーなくメッセージが表示されれば OK です。
persistor を設定する
依存関係をインストールする
try🐶everything myproject$ yarn add apollo3-cache-persist try🐶everything myproject$ yarn add @react-native-async-storage/async-storage try🐶everything myproject$ npx pod-install try🐶everything myproject$ yarn ios && yarn android
※ persistCache & CachePersistor 用 ApolloPersistOptions 詳細
// ApolloPersistOptions: persistCache & CachePersistor export interface ApolloPersistOptions<TSerialized, TSerialize extends boolean = true> { cache: ApolloCache<TSerialized>; storage: StorageType<PersistedData<TSerialized>, TSerialize>; trigger?: 'write' | 'background' | TriggerFunction | false; debounce?: number; key?: string; serialize?: TSerialize; maxSize?: number | false; persistenceMapper?: PersistenceMapperFunction; debug?: boolean; }
persistCache
デフォルトの persistCache
は ApolloPersistOptions
( Apollo キャッシュと基盤となるストレージ プロバイダー) をpersistCache
に渡すだけで開始できます。(例⬇︎)
※ デフォルトでは、Apollo キャッシュの内容はすぐに復元され (非同期)、キャッシュに書き込むたびに (短いデバウンス間隔で) 保持されます。( React Native ではデフォルトでアプリが background 状態に変わると保持される)
cache は実際のプロジェクトでは詳細な typePolicies
設定を行うことがあるので別のファイルに分離しています。(任意)
try🐶everything myproject$ mkdir -p src/apollo/hooks try🐶everything myproject$ touch src/apollo/hooks/useApolloClient.tsx
// src/apollo/cache.js import {InMemoryCache} from '@apollo/client'; export const cache = new InMemoryCache(); // <-- 現在は空のまま
usePersistCachedApolloClient カスタムフックを作成します。
// src/apollo/hooks/usePersistCachedApolloClient.tsx import {useState, useEffect} from 'react'; import { from, ApolloClient, createHttpLink, NormalizedCacheObject, } from '@apollo/client'; import {persistCache, AsyncStorageWrapper} from 'apollo3-cache-persist'; import AsyncStorage from '@react-native-async-storage/async-storage'; import {onError} from '@apollo/client/link/error'; import {cache} from 'apollo/cache'; export const usePersistCachedApolloClient = () => { const [client, setClient] = useState<ApolloClient<NormalizedCacheObject>>( {} as ApolloClient<NormalizedCacheObject>, ); const [loadingCache, setLoadingCache] = useState<boolean>(true); useEffect(() => { async function init() { persistCache({ cache, storage: new AsyncStorageWrapper(AsyncStorage), trigger: 'background', maxSize: false, // Unlimited cache size debug: __DEV__, }).then(() => setLoadingCache(false)); setClient( new ApolloClient({ uri: 'https://flyby-router-demo.herokuapp.com/' cache, connectToDevTools: true, }), ); } init().catch(err => { console.log(err); }); }, []); return { client, loadingCache, }; };
CachePersistor
CachePersistor
は ApolloPersistOptions に加え、persistor.restore()
persistor.purge()
などより細かく Persistor を制御できることができます。
// CachePersistor
// CachePersistor export default class CachePersistor<T> { log: Log<T>; cache: Cache<T>; storage: Storage<T>; persistor: Persistor<T>; trigger: Trigger<T>; constructor(options: ApolloPersistOptions<T>); persist(): Promise<void>; restore(): Promise<void>; purge(): Promise<void>; pause(): void; resume(): void; remove(): void; getLogs(print?: boolean): Array<LogLine> | void; getSize(): Promise<number | null>; }
useCachePersistorApolloClient
カスタムフックを作成します。
// src/apollo/hooks/useCachePersistorApolloClient.tsx import {useState, useEffect, useCallback} from 'react'; import {ApolloClient,NormalizedCacheObject} from '@apollo/client'; import {CachePersistor, AsyncStorageWrapper} from 'apollo3-cache-persist'; import AsyncStorage from '@react-native-async-storage/async-storage'; import {cache} from 'apollo/cache'; import {URL} from 'common/utils'; export const useCachePersistorApolloClient = () => { const [client, setClient] = useState<ApolloClient<NormalizedCacheObject>>( {} as ApolloClient<NormalizedCacheObject>, ); const [persistor, setPersistor] = useState< CachePersistor<NormalizedCacheObject> >({} as CachePersistor<NormalizedCacheObject>); const clearCache = useCallback(() => { if (!persistor) return; persistor.purge(); }, [persistor]); // const store = client?.cache?.data?.data; useEffect(() => { async function init() { let newPersistor = new CachePersistor({ cache, storage: new AsyncStorageWrapper(AsyncStorage), trigger: 'background', maxSize: false, // Unlimited cache size debug: __DEV__, }); await newPersistor.restore(); setPersistor(newPersistor); setClient( new ApolloClient({ uri: 'https://flyby-router-demo.herokuapp.com/', cache, connectToDevTools: true, }), ); } init().catch(err => { console.log(err); }); }, []); return { client, persistor, clearCache, }; };
動作をテストする
Line 4-7、12-33 番のように追加して両方の動作テストをします。⬇︎
usePersistCachedApolloClient()
だけに loadingCache
を使用することに注意しましょう!
// app/_layout.tsx ... import { useCachePersistorApolloClient, // usePersistCachedApolloClient, } from 'apollo/hooks'; ... function RootLayoutNav() { const colorScheme = useColorScheme(); const {client, loadingCache} = usePersistCachedApolloClient(); // const {client} = useCachePersistorApolloClient(); if (loadingCache || !client) { // if (!client) { return <ActivityIndicator size={'large'} />; } client .query({ query: gql` query GetLocations { locations { id name description photo } } `, }) .then(result => console.log(JSON.stringify(result, null, 2)));
TypeError: Cannot read property ‘watchQuery’ of undefined このエラーが発生して解決できない時には、import {isEmpty} from 'lodash';
…if(!client) {...}
を if(isEmpty(client)){...}
に変更してみてください。
※テスト結果:コンソールメッセージ内容 ⬇︎が表示されれば OK です。
{ "data": { "locations": [ { "__typename": "Location", "id": "loc-1", "name": "The Living Ocean of New Lemuria", "description": "Surviving is usually extremely difficult, especially when nutrients are scarce and you have to choose between growing or reproducing. One species on this planet has developed a nifty method to prepare for this. Once full matured, this species will split into 2 versions of itself and attached to each other, so it's essentially reproducing. Once those 2 are fully grown, they newly grown version will either detach itself if enough nutrients are available or it becomes a storage unit for the original, if nutrients are scarce. If nutrients continue to be scarce, the original will use slowly consume the nutrients in the new version in the hope that new nutrients become available again and it can repeat the cycle.", "photo": "https://res.cloudinary.com/apollographql/image/upload/v1644381344/odyssey/federation-course1/FlyBy%20illustrations/Landscape_4_lkmvlw.png" }, ...
おわりに
persistCache
?CachePersistor
?
どちらを採用するかはプロジェクトのニーズによりますが、書き込みが多かった別のアプリの開発時は CachePersistor
を採用し、手動( trigger: false
)で保持するタイミング( persistor.persist()
)を制御したことがありました。
今回のプロジェクトでは、書き込みの少ないアプリを開発するので、デフォルトの persistCache
を採用して様子を見ながらCachePersistor
への移行を判断しようかと思います。
コメント