React ApolloでReduxコードの使用を減らす

みなさんこんにちは! Peggy RayzisによるReact ApolloによるReduxコードの削減という興味深い記事の翻訳を共有したいと思います。 著者と彼女のチームがどのように彼らのプロジェクトでGraphQLテクノロジーを実装したかについての記事。 翻訳は著者の許可を得て公開されています。







React ApolloでReduxコードの使用を減らす

Higher Football Leagueでの宣言型アプローチへの切り替え







私は、最高のコードはコードの欠如だと強く信じています。 コードが多いほど、バグが発生する可能性が高くなり、そのようなコードを維持するためにより多くの時間が費やされます。 ハイアーフットボールリーグでは、チームが非常に小さいため、この原則を重視しています。 コードの再利用を増やすか、単にコードの特定の部分のサービスを停止することにより、可能な限りすべてを最適化しようとします。







この記事では、データの受信に対する制御をApolloに転送し、約5,000行のコードを削除する方法について学習します 。 さらに、Apolloに切り替えた後、アプリケーションは必要なデータのみを要求するようになったため、アプリケーションのボリュームが非常に小さくなっただけでなく、より宣言的になりました。







宣言とはどういう意味ですか、なぜそれがとてもクールなのですか? 宣言型プログラミングは究極の目標に焦点を当てていますが、命令型プログラミングはそれを達成するために必要なステップに焦点を当てています。 React自体は宣言的です。







Reduxを使用したデータの取得



簡単なArticleコンポーネントを見てみましょう。







import React from 'react';
import { View, Text } from 'react-native';
export default ({ title, body }) => (
<View>
<Text>{title}</Text>
<Text>{body}</Text>
</View>
);
view raw article.js hosted with ❤ by GitHub


接続された<MatchDetail/>



ビューで<Article/>



をレンダリングし、一致IDを小道具として受け取るとします。 GraphQLクライアントなしでこれを行う場合、 <Article/>



レンダリングに必要なデータを取得するプロセスは次のようになります。







  1. <MatchDetail/>



    がマウントされたら、アクション作成者を呼び出してIDで一致データを取得します。 アクション作成者はアクションをディスパッチして、データ収集プロセスの開始をReduxに通知します。
  2. 目的地に到着し、データを返送しています。 便利な構造でデータを正規化します。
  3. データが正規化された後、データ取得プロセスの完了についてReduxに通知する別のアクションをディスパッチします。
  4. Reduxはリデューサーでアクションを処理し、アプリケーションの状態を更新します。
  5. <MatchDetail/>



    は、小道具を介して必要なすべての一致データを受け取り、それらをフィルタリングして記事をレンダリングします。


<Article/>



データを取得するだけの多くのステップ! GraphQLクライアントがないと、データを取得する方法に専念する必要があるため、コードがはるかに不可欠になります。 しかし、 <Article/>



単純なレンダリングのためにすべての一致データを転送したくない場合はどうでしょうか? 別のエンドポイントを構築し、そこからデータを受信するアクションクリエーターの別のセットを作成できますが、このオプションは非常に簡単にサポートされなくなる可能性があります。







GraphQLで同じことができる方法を比較してみましょう。







  1. <MatchDetail/>



    、次のリクエストを実行する高次コンポーネントに接続されています。


 query Article($id: Float!) { match(id: $id) { article { title body } } }
      
      





...そしてそれだけです! クライアントがデータを受信するとすぐに、それらを小道具に渡し、さらに<Article/>



送信できます。 これは、コンポーネントをレンダリングするために必要データのみに焦点を合わせるため、はるかに宣言的です。







これは、RelayでもApolloでも、GraphQLデータの受信をクライアントに委任することの利点です。 「 GraphQLの概念 」を考え始めると、このデータを取得する方法を心配するのではなく、コンポーネントがレンダリングする必要がある小道具だけを気にすることになります。







ある時点でこの「 方法 」について考える必要がありますが、これはすでにサーバー側の懸念事項であり、したがって、フロントエンドの複雑さが大幅に軽減されます。 GraphQLサーバーアーキテクチャを初めて使用する場合は、Apolloライブラリであるgraphql-tools



試してください。これにより、スキーマをよりgraphql-tools



的に構成できます。 簡潔にするために、今日はフロントエンド部分のみに焦点を当てます。







この投稿ではReduxコードの使用を削減する方法について説明していますが、それを完全になくすことはできません。 Apolloはボンネットの下でReduxを使用しているので、不変性の恩恵を受けることができ、タイムトラベルデバッグなどのRedux Dev Toolsのすべてのクールな機能も機能します。 構成中に ApolloをReduxの既存のストアに接続して、単一の「真実の源」をサポートできます。 ストアを構成したら、アプリケーション全体をラップする<ApolloProvider/>



コンポーネントにストアを渡します。 おなじみの音? このコンポーネントは、クライアントプロパティを介してApolloClient



インスタンスを渡す必要があることを除き、Reduxの既存の<Provider/>



を完全に置き換えます。







Reduxコードのカットを開始する前に、GraphQLの最も機能的な機能の1つであるインクリメンタル採用に名前を付けます 。 アプリケーション全体を一度にリファクタリングする必要はありません。 Apolloを既存のReduxストアと統合した後、レデューサーから徐々に切り替えることができます。 サーバー側にも同じことが当てはまります。大規模なアプリケーションで作業している場合、完全な移行の準備ができるまで、現在のREST APIとGraphQLを併用できます。 公正な警告:GraphQLを試すとすぐに、このテクノロジーに夢中になり、アプリケーション全体を作り直したいと思うかもしれません。







私たちの要件



ReduxからApolloに移行する前に、Apolloがニーズを満たしているかどうかを慎重に検討しました。 決定する前に気づいたのは次のとおりです。









Apolloは現在のすべての要件を満たしているだけでなく、特にパーソナライズがロードマップに含まれていることを考慮して、将来のニーズの一部もカバーしたと言わなければなりません。 現在、サーバーは読み取り専用ですが、ユーザーにお気に入りのチームを保存するために、将来的に突然変異を導入する必要がある場合があります。 ポーリングでは解決できないリアルタイムのコメントやファンとのやり取りを追加する場合、Apolloはサブスクリプションをサポートします







Reduxからアポロへ



みんなが待っていた瞬間! 最初は、この記事を書くことを考えていたとき、前後のコード例を紹介するだけでしたが、これら2つのアプローチを直接比較することは難しいと思います(特にApolloの初心者向け)。 代わりに、リモートコード全体の量を計算し、Apolloを使用してコンテナコンポーネントを作成するときに適用できるおなじみのReduxの概念をガイドします。







削除したもの





接続()→graphql()



connect



使用方法を知っている場合、Apolloの高次graphql



は非常に馴染みのあるものになります。 connect



がコンポーネントを受け入れてReduxストアに接続する関数を返すように、 graphql



コンポーネントを受け入れてそれをApolloクライアントに「接続」する関数を返します。 実際に見てみましょう!







import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import { MatchSummary, NoDataSummary } from '@mls-digital/react-components';
import MatchSummaryQuery from './match-summary.graphql';
// here we're using the graphql HOC as a decorator, but you can use it as a function too!
@graphql(MatchSummaryQuery, {
options: ({ id, season, shouldPoll }) => {
return {
variables: {
id,
season,
},
pollInterval: shouldPoll ? 1000 * 60 : undefined,
};
};
})
class MatchSummaryContainer extends Component {
render() {
const { data: { loading, match } } = this.props;
if (loading && !match) {
return <NoDataSummary />;
}
return <MatchSummary {...match} />;
}
}
export default MatchSummaryContainer;
view raw match-summary.js hosted with ❤ by GitHub


graphql



渡される最初の引数はMatchSummaryQuery



です。 これは、サーバーから受信するデータです。 Webpackローダーを使用してGraphQL ASTでクエリを解析しますが、Webpackを使用しない場合は、クエリをテンプレート文字列でラップし、Apolloからエクスポートされたgql



関数に渡す必要があります。 コンポーネントに必要なデータのリクエストの例を次に示します。







query MatchSummary($id: String!, $season: String) {
match(id: $id) {
stats {
scores {
home {
score
isWinner: is_winner
}
away {
score
isWinner: is_winner
}
}
}
home {
id: opta_id
record(season: $season)
}
away {
id: opta_id
record(season: $season)
}
}
}


素晴らしい、リクエストがあります! 正しく実行するには、2つの変数$id



$season



に渡す必要があります。 しかし、これらの変数はどこで取得できますか? ここで、 graphql



関数の2番目の引数が機能し、構成オブジェクトとして提示されます。







このオブジェクトには、高次コンポーネント(HOC)の動作を構成するために指定できるいくつかのプロパティがあります。 最も重要なプロパティの1つはoptions



で、コンテナの小道具を受け取る関数を受け取ります。 この関数は、変数をリクエストに渡すことができるタイプvariables



プロパティと、コンポーネントのポーリング動作をカスタマイズできるpollInterval



持つオブジェクトを返します。 id



season



MatchSummaryQuery



に渡すためにコンテナの小道具をどのように使用するかに注目してMatchSummaryQuery



。 この関数が長くなりすぎてデコレータに直接書き込むことができない場合、 mapPropsToOptions



と呼ばれる別の関数にmapPropsToOptions



ます。







mapStateToProps()→mapResultsToProps()



ReduxコンテナーでmapStateToProps



関数を使用し、この関数を渡してconnect



して、状態アプリケーションからこのコンテナーの小道具にデータを転送しました。 Apolloでは、同様の関数を定義できます。 graphql



関数に以前に渡した構成オブジェクトを覚えていますか? このオブジェクトにはもう1つのプロパティがありますprops



、propsを入力として受け取り、コンテナに渡す前にそれらを処理する関数を受け取ります。 もちろん、 graphql



で直接定義できますが、別のmapResultsToProps



関数として定義しmapResultsToProps



です。







なぜ小道具を再定義する必要があるのですか? GraphQLクエリの結果は、常にdata



プロパティに割り当てられます。 このデータをコンポーネントに送信する前に調整する必要がある場合があります。 次に例を示します。







import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import { MatchSummary, NoDataSummary } from '@mls-digital/react-components';
import MatchSummaryQuery from './match-summary.graphql';
const mapResultsToProps = ({ data }) => {
if (!data.match)
return {
loading: data.loading,
};
const { stats, home, away } = data.match;
return {
loading: data.loading,
home: {
...home,
results: stats.scores.home,
},
away: {
...away,
results: stats.scores.away,
},
};
};
const mapPropsToOptions = ({ id, season, shouldPoll }) => {
return {
variables: {
id,
season,
},
pollInterval: shouldPoll ? 1000 * 60 : undefined,
};
};
@graphql(MatchSummaryQuery, {
props: mapResultsToProps,
options: mapPropsToOptions,
})
class MatchSummaryContainer extends Component {
render() {
const { loading, ...matchSummaryProps } = this.props;
if (loading && !matchSummaryProps.home) {
return <NoDataSummary />;
}
return <MatchSummary {...matchSummaryProps} />;
}
}
export default MatchSummaryContainer;
view raw match-summary.js hosted with ❤ by GitHub


これで、データオブジェクトには、リクエストの結果だけでなく、 data.loading



タイプのプロパティも含まれ、リクエストがまだレスポンスを返していないことがわかります。 これは、 <NoDataSummary/>



行ったように、同様の状況で別のコンポーネントをユーザーに表示する場合に役立ちます。







作成()



ComposeはReduxで使用される機能だけではありませんが、Apolloに含まれているという事実に引き続き注意を向けたいと思います。 1つのコンテナで使用するために複数のgraphql



関数を構築したい場合に非常に便利です。 その中で、 graphql



とともにReduxのconnect



機能を使用することもできます! さまざまな一致状態を表示するために、 compose



を使用compose



方法は次のとおりです。







import React, { Component } from 'react';
import { compose, graphql } from 'react-apollo';
import { NoDataExtension } from '@mls-digital/react-components';
import PostGameExtension from './post-game';
import PreGameExtension from './pre-game';
import PostGameQuery from './post-game.graphql';
import PreGameQuery from './pre-game.graphql';
@compose(
graphql(PreGameQuery, {
skip: ({ gameStatus }) => gameStatus !== 'pre',
props: ({ data }) => ({
preGameLoading: data.loading,
preGameProps: data.match,
}),
}),
graphql(PostGameQuery, {
skip: ({ gameStatus }) => gameStatus !== 'post',
props: ({ data }) => ({
postGameLoading: data.loading,
postGameProps: data.match,
}),
}),
)
export default class MatchExtensionContainer extends Component {
render() {
const {
preGameLoading,
postGameLoading,
gameStatus,
preGameProps,
postGameProps,
...rest
} = this.props;
if (preGameLoading || postGameLoading)
return <NoDataExtension gameStatus={gameStatus} />;
return gameStatus === 'post'
? <PostGameExtension {...postGameProps} {...rest} />;
: <PreGameExtension {...preGameProps} {...rest} />;
}
}
view raw match-extension.js hosted with ❤ by GitHub


コンテナに複数の状態が含まれる場合、 compose



うまく機能します。 しかし、状態に応じてのみ別のリクエストを実行する必要がある場合はどうでしょうか? ここでskip



は、上記の構成オブジェクトで確認できるように役立ちます。 skip



プロパティは、propsを受け取る関数を受け入れ、必要な基準を満たさない場合はクエリの実行をスキップできます。







これらすべての例は、Reduxを知っていれば、すぐにApolloの開発に参加できることを示しています! そのAPIにはReduxの概念の多くが組み込まれていますが、同じ結果を達成するために記述する必要があるコードの量は削減されています。










メジャーリーグサッカーをアポロに移した経験があなたのお役に立てば幸いです! さまざまなライブラリに関する決定と同様に、アプリケーションでのデータの受信を制御する最適な決定は、プロジェクトの特定の要件によって異なります。 私たちの経験に関して質問がある場合は、 ここにコメントを残すか、 Twitterでフォローしてください!







読んでくれてありがとう!








All Articles