おもしろいですが、インターネットが世界に普及するにつれて、ネットワークに接続せずに動作するWebアプリケーションの需要が高まっています。 ユーザー(および顧客)は、オンライン、オフライン、および通信が不安定な地域で機能するインターネットアプリケーションを求めています。
そして、これは...簡単ではありません。
ReactおよびApollo Clientを使用したGraphQLデータレイヤーで、ネットワーク接続なしで機能する効果的なソリューションを作成する方法を見てみましょう。 この記事は2つの部分に分かれています。 今週はオフラインクエリを分析します。 来週、突然変異に取りかかりましょう。
Redux PersistとRedux Offline
Apollo Clientの背後には同じReduxがあります。 そしてそれは、多数のツールとライブラリを備えたReduxエコシステム全体がApolloアプリケーションで利用できることを意味します。
オフラインサポートでは、ReduxにはRedux PersistとRedux Offlineの2つの主要なプレーヤーがあります。
Redux Persistは、 reduxストレージをlocalStorage
して復元するための優れた基本ツールです(他のストレージの場所がサポートされています)。 受け入れられている用語によると、回復は再水和とも呼ばれます。
Redux Offlineは、機能とユーティリティの追加レイヤーでRedux Persistの機能を拡張します。 Redux Offlineは、切断され復元された通信について自動的に学習します。中断すると、アクションと操作をキューに入れ、回復すると、これらのアクションを自動的に再現します。
Redux Offlineは、ネットワーク接続なしで作業するための「有料」オプションです。
オフラインリクエスト
Apollo Client自体は、ネットワーク接続の短期的な中断にうまく対処します。 要求が行われると、その結果はApolloの独自のリポジトリに配置されます。
同じリクエストが繰り返される場合、fetchPolicyパラメータがnetwork-onlyでない限り、結果はクライアント上のリポジトリからすぐに取得され、リクエスト元のコンポーネントに返されます 。 これは、ネットワーク接続がない場合、またはサーバーが利用できない場合、リクエストを繰り返すと最後に保存された結果が返されることを意味します。
ただし、ユーザーがアプリケーションを閉じると、ストレージは消えます。 アプリケーションの再起動の間にクライアントのApolloストレージを失わないようにするには?
Redux Offlineが助けになります。
Apolloは、データをReduxリポジトリ( apollo
キー)に保持します。 ストレージ全体をlocalStorage
書き込み、次にアプリケーションを起動したときに復元することで、インターネットに接続していない場合でも、アプリケーションとのセッション間で過去のリクエストの結果を転送できます 。
Redux OfflineとApollo Clientの使用には微妙な違いがあります。 両方のライブラリを連携させる方法を見てみましょう。
手動ストレージ作成
通常、Apolloクライアントは非常に簡単に作成されます。
export const client = new ApolloClient({ networkInterface });
ApolloClient
コンストラクターは、Apolloリポジトリー(および間接的にReduxリポジトリー)を自動的に作成します。 結果のclient
インスタンスはApolloProvider
コンポーネントにApolloProvider
ます。
ReactDOM.render( <ApolloProvider client={client}> <App /> </ApolloProvider>, document.getElementById('root') );
Redux Offlineを使用する場合、Reduxリポジトリを手動で作成する必要があります。 これにより、ストレージにRedux Offlineから中間プロセッサ(ミドルウェア)に接続できます。 開始するには、単にApolloが行うことを繰り返します。
export const store = createStore( combineReducers({ apollo: client.reducer() }), undefined, applyMiddleware(client.middleware()) );
ここでは、 store
はApolloインスタンス( client
変数)のリデューサーとミドルウェアを使用し、初期状態はundefined
です。
これで、 store
をApolloProvider
コンポーネントに送信できます。
<ApolloProvider client={client} store={store}> <App /> </ApolloProvider>
素晴らしい。 Reduxリポジトリの作成は制御下にあり、Reduxオフラインで続行できます。
永続クエリストレージの基本
最も単純な場合、Redux Offlineの追加は、別の中間ハンドラーをリポジトリに追加することです。
import { offline } from 'redux-offline'; import config from 'redux-offline/lib/defaults'; export const store = createStore( ... compose( applyMiddleware(client.middleware()), offline(config) ) );
追加の構成を行わないと、 offline
ハンドラーは自動的にReduxストレージのlocalStorage
への書き込みを開始しlocalStorage
。
信じられない?
コンソールを開き、 localStorage
からこのエントリを取得しlocalStorage
。
localStorage.getItem("reduxPersist:apollo");
Apolloアプリケーションの完全な現在の状態を表す大きなJSONオブジェクトが表示されます。
いいね!
Redux Offlineは、Reduxのストレージのスナップショットを自動的にlocalStorage
し、 localStorage
書き込みlocalStorage
。 アプリケーションが起動するたびに、保存された状態がlocalStorage
から自動的に取得され、Reduxストレージに復元されます。
結果がこのリポジトリにあるすべてのクエリでは、サーバーへの接続がない場合でもデータが返されます。
ストレージリカバリコンペティション
残念ながら、ストレージの復旧はすぐには行われません。 Redux Offlineがストレージを復元している間にアプリケーションがリクエストを行うと、Strange Things(tm)が発生する場合があります。
Redux OfflineでautoRehydrate
モードのロギングを有効にすると(それ自体が緊張します)、アプリケーションの初期ロード中に、次のようなエラーが表示されます。
21 actions were fired before rehydration completed. This can be a symptom of a race condition where the rehydrate action may overwrite the previously affected state. Consider running these actions after rehydration: … 21 . , - . : …
Redux Persistの開発者は問題を認識し、リカバリ後のアプリケーションの遅延レンダリングのレシピを提案しました。 残念ながら、彼のソリューションはpersistStore
手動呼び出しに基づいています。 ただし、Redux Offlineはこの呼び出しを自動的に行います。
別のソリューションを見てみましょう。
REHYDRATE_STORE
と呼ばれるReduxアクションと、Reduxリポジトリのrehydrated
属性の値をtrueに設定する対応するリデューサーを作成します。
export const REHYDRATE_STORE = 'REHYDRATE_STORE'; export default (state = false, action) => { switch (action.type) { case REHYDRATE_STORE: return true; default: return state; } };
作成されたレデューサーをリポジトリに接続し、リカバリの終了時に新しいアクションが実行されるようにReduxオフラインを構成します。
export const store = createStore( combineReducers({ rehydrate: RehydrateReducer, apollo: client.reducer() }), ..., compose( ... offline({ ...config, persistCallback: () => { store.dispatch({ type: REHYDRATE_STORE }); }, persistOptions: { blacklist: ['rehydrate'] } }) ) );
いいね! Redux Offlineは、リポジトリを復元するときにpersistCallback
関数を呼び出します。これにより、 REHYDRATE_STORE
アクションがトリガーされ、最終的にリポジトリにrehydrate
が設定されます。
blacklist
配列にrehydrate
を追加すると、ストレージのこの部分がlocalStorage
書き込まれたり復元されたりすることがlocalStorage
。
リポジトリにはリカバリの完了に関する情報があるため、 rehydrate
フィールドの変更に応答し、 rehydrate
がtrue
場合にのみ子コンポーネントを視覚化するコンポーネントを開発しtrue
。
class Rehydrated extends Component { render() { return ( <div className="rehydrated"> {this.props.rehydrated ? this.props.children : <Loader />} </div> ); } } export default connect(state => { return { rehydrate: state.rehydrate }; })(Rehydrate);
最後に、 <App />
コンポーネントを<Rehydrated />
<App />
コンポーネント内に配置して、水分補給が完了するまでアプリケーションが表示されないようにします。
<ApolloProvider client={client} store={store}> <Rehydrated> <App /> </Rehydrated> </ApolloProvider>
うわ
Redux OfflineがlocalStorage
からストアを復元する間、アプリケーションはさりげなく待機し、レンダリングを続行し、その後のすべてのGraphQLクエリまたは変更を行います。
奇妙な説明
ApolloクライアントでRedux Offlineを使用する場合、いくつかの奇妙な点と明確なことがあります。
まず、この記事の例ではapollo-client
パッケージのバージョン1.9.0-0
を使用していることに注意してください。 Apollo Client バージョン1.9では、 Redux Offlineを使用する際の奇妙な症状の修正が発表されました。
このペアのもう1つの奇妙な点は、Redux OfflineがApollo Client Devtoolsとうまく連携していないように見えることです。 インストールされたDevtoolsでRedux Offlineを使用しようとすると、予期せず一見矛盾したエラーが発生する場合があります。
このようなエラーは、Apollo client
作成時にDevtoolsに接続しなければ簡単に解消できclient
。
export const client = new ApolloClient({ networkInterface, connectToDevTools: false });
連絡を取り合う
Redux Offlineは、Apolloアプリケーションに、サーバーから切断した後にアプリケーションが再起動した場合でも、リクエストを実行するための基本的なメカニズムを提供します。
1週間後、Redux Offlineを使用したオフライン突然変異の処理に飛び込みます。
連絡を取り合いましょう!
記事の翻訳。 ピート・コーリーによるオリジナル。