React、Redux、Sanity.ioを使用したWebアプリの構築

公開: 2022-03-10
クイックサマリー↬ヘッドレスCMSは、コンテンツを管理し、APIにアクセスするための強力で簡単な方法です。 React上に構築されたSanity.ioは、柔軟なコンテンツ管理のためのシームレスなツールです。 単純なアプリケーションから複雑なアプリケーションをゼロから構築するために使用できます。 この記事では、IfeanyiがSanity.ioとReactを使用して簡単なリストアプリを作成する方法を説明します。 グローバル状態はReduxで管理され、アプリケーションはstyled-componentsでスタイル設定されます。

デジタルプラットフォームの急速な進化により、Wordpressのような従来のCMSに深刻な制限が課せられました。 これらのプラットフォームは結合されており、柔軟性がなく、製品ではなくプロジェクトに重点を置いています。 ありがたいことに、これらの課題やその他多くの課題に取り組むために、いくつかのヘッドレスCMSが開発されました。

従来のCMSとは異なり、サービスとしてのソフトウェア(SaaS)として説明できるヘッドレスCMSは、Webサイト、モバイルアプリ、デジタルディスプレイなどの開発に使用できます。 それらは無制限のプラットフォームで使用できます。 プラットフォームに依存せず、開発者優先で、クロスプラットフォームサポートを提供するCMSを探している場合は、ヘッドレスCMSから遠くを探す必要はありません。

ヘッドレスCMSは、単にヘッドのないCMSです。 ここでのheadはフロントエンドまたはプレゼンテーション層を指し、 bodyはバックエンドまたはコンテンツリポジトリを指します。 これには多くの興味深い利点があります。 たとえば、開発者は自分で選択したフロントエンドを選択できます。また、必要に応じてプレゼンテーション層を設計することもできます。

ヘッドレスCMSはたくさんありますが、最も人気のあるものには、Strapi、Contentful、Contentstack、Sanity、Butter CMS、Prismic、Storyblok、Directusなどがあります。これらのヘッドレスCMSはAPIベースであり、それぞれに長所があります。 たとえば、Sanity、Strapi、Contentful、StoryblokなどのCMSは、小規模なプロジェクトでは無料です。

これらのヘッドレスCMSは、さまざまな技術スタックにも基づいています。 Sanity.ioはReact.jsに基づいていますが、StoryblokはVue.jsに基づいています。 React開発者として、これが私がすぐにSanityに興味を持った主な理由です。 ただし、ヘッドレスCMSであるため、これらの各プラットフォームは、Angular、Vue、Reactのいずれのフロントエンドにも接続できます。

これらのヘッドレスCMSにはそれぞれ、大幅な価格上昇を表す無料プランと有料プランの両方があります。 これらの有料プランはより多くの機能を提供しますが、中小規模のプロジェクトにはそれほど多くの費用をかけたくないでしょう。 Sanityは、従量課金制のオプションを導入することで、この問題を解決しようとしています。 これらのオプションを使用すると、使用した分だけ支払うことができ、価格の急上昇を回避できます。

私がSanity.ioを選んだもう1つの理由は、GROQ言語です。 私にとって、Sanityはこのツールを提供することで群衆から際立っています。 Graphical-Relational Object Queries(GROQ)は、開発時間を短縮し、必要なコンテンツを必要な形式で取得するのに役立ちます。また、開発者がコードを変更せずに新しいコンテンツモデルでドキュメントを作成するのに役立ちます。

さらに、開発者はGROQ言語に制約されません。 GraphQLまたは従来のaxiosを使用して、Reactアプリでfetchしてバックエンドにクエリを実行することもできます。 他のほとんどのヘッドレスCMSと同様に、Sanityには、プラットフォーム上で構築するための役立つヒントを含む包括的なドキュメントがあります。

注:この記事では、React、Redux、CSSの基本を理解している必要があります。

ジャンプした後もっと! 以下を読み続けてください↓

Sanity.io入門

マシンでSanityを使用するには、SanityCLIツールをインストールする必要があります。 これはプロジェクトにローカルにインストールできますが、将来のアプリケーションからアクセスできるように、グローバルにインストールすることをお勧めします。

これを行うには、端末で次のコマンドを入力します。

 npm install -g @sanity/cli

上記のコマンドの-gフラグは、グローバルインストールを有効にします。

次に、アプリケーションでSanityを初期化する必要があります。 これは別のプロジェクトとしてインストールできますが、通常はフロントエンドアプリ(この場合はReact)内にインストールすることをお勧めします。

彼女のブログで、KapeheはSanityをReactと統合する方法を詳細に説明しました。 このチュートリアルを続行する前に、記事を確認しておくと役に立ちます。

次のコマンドを入力して、ReactアプリでSanityを初期化します。

 sanity init

Sanity CLIツールをインストールすると、 sanityコマンドを使用できるようになります。 ターミナルでsanityまたはsanity helpと入力すると、使用可能なSanityコマンドのリストを表示できます。

プロジェクトを設定または初期化するときは、プロンプトに従ってカスタマイズする必要があります。 また、データセットを作成する必要があり、データが入力されたカスタムデータセットを選択することもできます。 このリスティングアプリでは、SanityのカスタムSF映画データセットを使用します。 これにより、自分でデータを入力する必要がなくなります。

データセットを表示および編集するには、ターミナルのSanityサブディレクトリにcdして、sanitystartと入力しsanity start 。 これは通常https://localhost:3333/で実行されます。 インターフェイスにアクセスするにはログインが必要な場合があります(プロジェクトの初期化時に使用したのと同じアカウントでログインするようにしてください)。 環境のスクリーンショットを以下に示します。

Sanityサーバーの概要
SF映画データセットの健全性サーバーの概要。 (大プレビュー)

Sanity-React双方向コミュニケーション

SanityとReactは、完全に機能するアプリケーションのために相互に通信する必要があります。

SanityManagerでのCORSオリジン設定

まず、ReactアプリをSanityに接続します。 これを行うには、 https://manage.sanity.io/ //manage.sanity.io/にログインし、 Settings ]タブの[ API SettingsCORS originsを見つけます。 ここでは、フロントエンドのオリジンをSanityバックエンドにフックする必要があります。 Reactアプリはデフォルトでhttps://localhost:3000/で実行されるため、CORSに追加する必要があります。

これを下の図に示します。

CORSオリジン設定
Sanity.ioManagerでCORSオリジンを設定します。 (大プレビュー)

正気を反応につなぐ

Sanityは、作成するすべてのプロジェクトにproject IDを関連付けます。 このIDは、フロントエンドアプリケーションに接続するときに必要です。 プロジェクトIDは、 SanityManagerで確認できます。

バックエンドは、sanityclientと呼ばれるライブラリを使用してReactと通信しsanity client 。 次のコマンドを入力して、このライブラリをSanityプロジェクトにインストールする必要があります。

 npm install @sanity/client

プロジェクトのsrcフォルダーにsanitySetup.jsファイル(ファイル名は関係ありません)を作成し、次のReactコードを入力して、SanityとReact間の接続を設定します。

 import sanityClient from "@sanity/client" export default sanityClient({ projectId: PROJECT_ID, dataset: DATASET_NAME, useCdn: true });

projectIddataset name 、ブール値のuseCdnを、 @sanity/clientからインポートされたsanityクライアントのインスタンスに渡しました。 これは魔法のように機能し、アプリをバックエンドに接続します。

双方向接続が完了したので、すぐにプロジェクトを構築しましょう。

Reduxをセットアップしてアプリに接続する

ReactアプリでReduxを操作するには、いくつかの依存関係が必要です。 React環境でターミナルを開き、次のbashコマンドを入力します。

 npm install redux react-redux redux-thunk

Reduxは、ほとんどのフロントエンドフレームワークやReactなどのライブラリで使用できるグローバルな状態管理ライブラリです。 ただし、 ReduxストアとReactアプリケーション間の通信を可能にするには、中間ツールreact-reduxが必要です。 Reduxサンクは、Reduxからアクションオブジェクトの代わりに関数を返すのに役立ちます。

Reduxワークフロー全体を1つのファイルに書き込むこともできますが、多くの場合、懸念事項を分離する方が適切です。 このために、ワークフローをactionsreducersstoreの3つのファイルに分割します。 ただし、 action typesを保存するための別のファイル( constantsとも呼ばれます)も必要です。

ストアの設定

ストアはReduxで最も重要なファイルです。 状態を整理してパッケージ化し、Reactアプリケーションに送信します。

Reduxワークフローを接続するために必要なReduxストアの初期設定は次のとおりです。

 import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/"; export default createStore( reducers, applyMiddleware(thunk) );

このファイルのcreateStore関数は、 reducer (必須)、初期状態、エンハンサー(通常はミドルウェア、この場合はapplyMiddlewareを介して提供されるthunk )の3つのパラメーターを取ります。 レデューサーはreducersフォルダーに保存され、それらを組み合わせて、 reducersフォルダーのindex.jsファイルにエクスポートします。 これは、上記のコードでインポートしたファイルです。 このファイルは後で再検討します。

SanityのGROQ言語の紹介

Sanityは、GROQを導入することで、JSONデータのクエリをさらに一歩進めます。 GROQは、Graph-RelationalObjectQueriesの略です。 Sanity.ioによると、GROQは、主にスキーマのないJSONドキュメントのコレクションをクエリするように設計された宣言型クエリ言語です。

Sanityは、開発者が言語に慣れるためのGROQPlaygroundも提供しています。 ただし、遊び場にアクセスするには、 sanityvisionをインストールする必要があります。 ターミナルでsanity install @sanity/visionを実行してインストールします。

GROQの構文はGraphQLに似ていますが、より凝縮されて読みやすくなっています。 さらに、GraphQLとは異なり、GROQを使用してJSONデータをクエリできます。

たとえば、ムービードキュメント内のすべてのアイテムを取得するには、次のGROQ構文を使用します。

 *[_type == "movie"]

ただし、ムービードキュメントの_idscrewMembersのみを取得する場合。 これらのフィールドを次のように指定する必要があります。

 `*[_type == 'movie']{ _id, crewMembers }

ここでは、 *を使用して、 _typeムービーのすべてのドキュメントが必要であることをGROQに通知しました。 _typeは、ムービーコレクションの下の属性です。 次のように、 _idおよびcrewMembersを実行したようにタイプを返すこともできます。

 *[_type == 'movie']{ _id, _type, crewMembers }

ReduxアクションにGROQを実装することで、GROQの詳細に取り組みますが、GROQのSanity.ioのドキュメントで、GROQの詳細を確認できます。 GROQクエリのチートシートには、クエリ言語を習得するのに役立つ多くの例が記載されています。

定数の設定

Reduxワークフローのすべての段階でアクションタイプを追跡するための定数が必要です。 定数は、各時点でディスパッチされるアクションのタイプを判別するのに役立ちます。 たとえば、APIが読み込まれているとき、完全に読み込まれているとき、エラーが発生したときを追跡できます。

必ずしも別のファイルで定数を定義する必要はありませんが、単純さと明確さのために、これは通常、Reduxのベストプラクティスです。

慣例により、Javascriptの定数は大文字で定義されます。 ここでは、定数を定義するためのベストプラクティスに従います。 これは、動画のフェッチを移動するリクエストを示す定数の例です。

 export const MOVIE_FETCH_REQUEST = "MOVIE_FETCH_REQUEST";

ここでは、 MOVIE_FETCH_REQUESTのアクションタイプを示す定数MOVIE_FETCH_REQUESTを作成しました。 これにより、 stringsを使用せずにこのアクションタイプを簡単に呼び出して、バグを回避できます。 また、定数をエクスポートして、プロジェクトのどこでも使用できるようにしました。

同様に、リクエストが成功または失敗したことを示すアクションタイプをフェッチするための他の定数を作成できます。 movieConstants.jsの完全なコードは、以下のコードに示されています。

 export const MOVIE_FETCH_REQUEST = "MOVIE_FETCH_REQUEST"; export const MOVIE_FETCH_SUCCESS = "MOVIE_FETCH_SUCCESS"; export const MOVIE_FETCH_FAIL = "MOVIE_FETCH_FAIL"; export const MOVIES_FETCH_REQUEST = "MOVIES_FETCH_REQUEST"; export const MOVIES_FETCH_SUCCESS = "MOVIES_FETCH_SUCCESS"; export const MOVIES_FETCH_FAIL = "MOVIES_FETCH_FAIL"; export const MOVIES_FETCH_RESET = "MOVIES_FETCH_RESET"; export const MOVIES_REF_FETCH_REQUEST = "MOVIES_REF_FETCH_REQUEST"; export const MOVIES_REF_FETCH_SUCCESS = "MOVIES_REF_FETCH_SUCCESS"; export const MOVIES_REF_FETCH_FAIL = "MOVIES_REF_FETCH_FAIL"; export const MOVIES_SORT_REQUEST = "MOVIES_SORT_REQUEST"; export const MOVIES_SORT_SUCCESS = "MOVIES_SORT_SUCCESS"; export const MOVIES_SORT_FAIL = "MOVIES_SORT_FAIL"; export const MOVIES_MOST_POPULAR_REQUEST = "MOVIES_MOST_POPULAR_REQUEST"; export const MOVIES_MOST_POPULAR_SUCCESS = "MOVIES_MOST_POPULAR_SUCCESS"; export const MOVIES_MOST_POPULAR_FAIL = "MOVIES_MOST_POPULAR_FAIL";

ここでは、映画または映画のリストを取得し、最も人気のある映画を並べ替えて取得するためのいくつかの定数を定義しました。 リクエストがいつloadingされ、 successfulfailedたかを判断するために定数を設定していることに注意してください。

同様に、 personConstants.jsファイルを以下に示します。

 export const PERSONS_FETCH_REQUEST = "PERSONS_FETCH_REQUEST"; export const PERSONS_FETCH_SUCCESS = "PERSONS_FETCH_SUCCESS"; export const PERSONS_FETCH_FAIL = "PERSONS_FETCH_FAIL"; export const PERSON_FETCH_REQUEST = "PERSON_FETCH_REQUEST"; export const PERSON_FETCH_SUCCESS = "PERSON_FETCH_SUCCESS"; export const PERSON_FETCH_FAIL = "PERSON_FETCH_FAIL"; export const PERSONS_COUNT = "PERSONS_COUNT";

movieConstants.jsと同様に、1人または複数の人をフェッチするための定数のリストを設定します。 また、人を数えるための定数を設定します。 定数は、 movieConstants.jsで説明されている規則に従い、アプリケーションの他の部分からアクセスできるようにエクスポートしました。

最後に、アプリにライトモードとダークモードを実装するため、別の定数ファイルglobalConstants.jsがあります。 それを見てみましょう。

 export const SET_LIGHT_THEME = "SET_LIGHT_THEME"; export const SET_DARK_THEME = "SET_DARK_THEME";

ここでは、ライトモードまたはダークモードがいつディスパッチされるかを決定する定数を設定します。 SET_LIGHT_THEMEは、ユーザーが明るいテーマに切り替えるタイミングを決定し、 SET_DARK_THEMEは、暗いテーマが選択されるタイミングを決定します。 図のように定数もエクスポートしました。

アクションの設定

慣例により、アクションは別のフォルダーに保存されます。 アクションは、タイプに従ってグループ化されます。 たとえば、映画のアクションはmovieActions.jsに保存され、個人のアクションはpersonActions.jsファイルに保存されます。

また、テーマをライトモードからダークモードに切り替えるためのglobalActions.jsもあります。

moviesActions.js内のすべての映画を取得しましょう。

 import sanityAPI from "../../sanitySetup"; import { MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS } from "../constants/movieConstants"; const fetchAllMovies = () => async (dispatch) => { try { dispatch({ type: MOVIES_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster": poster.asset->url, } ` ); dispatch({ type: MOVIES_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_FETCH_FAIL, payload: error.message }); } };

ReactをSanityバックエンドに接続するためにsanitySetup.jsファイルを作成したときのことを覚えていますか? ここでは、GROQを使用して健全性バックエンドをクエリできるようにセットアップをインポートしました。 また、 constantsフォルダーのmovieConstants.jsファイルからエクスポートされたいくつかの定数をインポートしました。

次に、コレクション内のすべての映画をフェッチするためのfetchAllMoviesアクション関数を作成しました。 ほとんどの従来のReactアプリケーションは、 axiosまたはfetchを使用してバックエンドからデータをフェッチします。 ここではこれらのいずれかを使用できますが、SanityのGROQを使用しています。 GROQモードに入るには、上記のコードに示すようにsanityAPI.fetch()関数を呼び出す必要があります。 ここで、 sanityAPIは、前に設定したReact-Sanity接続です。 これはPromiseを返すため、非同期で呼び出す必要があります。 ここではasync-await構文を使用しましたが、 .then構文を使用することもできます。

アプリケーションでthunkを使用しているため、アクションオブジェクトの代わりに関数を返すことができます。 ただし、returnステートメントを1行で渡すことを選択しました。

 const fetchAllMovies = () => async (dispatch) => { ... }

このように関数を書くこともできることに注意してください。

 const fetchAllMovies = () => { return async (dispatch)=>{ ... } }

一般に、すべての映画をフェッチするために、最初に、リクエストがまだロードされているときに追跡するアクションタイプをディスパッチしました。 次に、SanityのGROQ構文を使用して、映画ドキュメントを非同期的にクエリしました。 映画データの_idとposterurlを取得しました。 次に、APIから取得したデータを含むペイロードを返しました。

同様に、 _idで映画を取得し、映画を並べ替えて、最も人気のある映画を取得できます。

特定の人物の参照に一致する映画を取得することもできます。 これは、 fetchMoviesByRef関数で行いました。

 const fetchMoviesByRef = (ref) => async (dispatch) => { try { dispatch({ type: MOVIES_REF_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie' && (castMembers[person._ref match '${ref}'] || crewMembers[person._ref match '${ref}']) ]{ _id, "poster" : poster.asset->url, title } ` ); dispatch({ type: MOVIES_REF_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_REF_FETCH_FAIL, payload: error.message }); } };

この関数は引数を取り、 person._refまたはcrewMemberscastMembersが渡された引数と一致するかどうかをチェックします。 映画の_idposter urltitle一緒に返します。 また、タイプMOVIES_REF_FETCH_SUCCESSのアクションをディスパッチして、返されたデータのペイロードを添付します。エラーが発生した場合は、 try-catchラッパーのおかげで、タイプMOVIE_REF_FETCH_FAILのアクションをディスパッチして、エラーメッセージのペイロードを添付します。

fetchMovieById関数では、 GROQを使用して、関数に渡された特定のidに一致する映画を取得しました。

関数のGROQ構文を以下に示します。

 const data = await sanityAPI.fetch( `*[_type == 'movie' && _id == '${id}']{ _id, "cast" : castMembers[]{ "ref": person._ref, characterName, "name": person->name, "image": person->image.asset->url } , "crew" : crewMembers[]{ "ref": person._ref, department, job, "name": person->name, "image": person->image.asset->url } , "overview": { "text": overview[0].children[0].text }, popularity, "poster" : poster.asset->url, releaseDate, title }[0]` );

fetchAllMoviesアクションと同様に、最初はmovieタイプのすべてのドキュメントを選択しましたが、さらに関数に指定されたIDを持つドキュメントのみを選択しました。 映画の詳細をたくさん表示するつもりなので、取得する属性の束を指定しました。

映画idと、 castMembers配列内のいくつかの属性、つまりrefcharacterName 、人物の名前、および人物の画像を取得しました。 また、エイリアスをcastMembersからcastに変更しました。

castMembersと同様に、 crewMembers配列からいくつかの属性、つまりrefdepartmentjob 、人物の名前、人物の画像を選択しました。 また、エイリアスをcrewMembersからcrewに変更しました。

同様に、概要テキスト、人気、映画のポスターURL、映画のリリース日、タイトルを選択しました。

SanityのGROQ言語を使用すると、ドキュメントを並べ替えることもできます。 アイテムを並べ替えるには、パイプ演算子の横に注文を渡します。

たとえば、映画をreleaseDateで昇順で並べ替える場合は、次のようにすることができます。

 const data = await sanityAPI.fetch( `*[_type == 'movie']{ ... } | order(releaseDate, asc)` );

この概念をsortMoviesBy関数で使用して、昇順または降順で並べ替えました。

以下のこの関数を見てみましょう。

 const sortMoviesBy = (item, type) => async (dispatch) => { try { dispatch({ type: MOVIES_SORT_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster" : poster.asset->url, title } | order( ${item} ${type})` ); dispatch({ type: MOVIES_SORT_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_SORT_FAIL, payload: error.message }); } };

リクエストがいつ読み込まれるかを判断するために、タイプMOVIES_SORT_REQUESTのアクションをディスパッチすることから始めました。 次に、 GROQ構文を使用して、 movieコレクションからデータを並べ替えてフェッチしました。 並べ替える項目は変数itemで提供され、並べ替えのモード(昇順または降順)は変数typeで提供されます。 その結果、 id 、poster url、およびtitleを返しました。 データが返されると、タイプMOVIES_SORT_SUCCESSのアクションをディスパッチし、失敗した場合は、タイプMOVIES_SORT_FAILのアクションをディスパッチします。

同様のGROQの概念がgetMostPopular関数に適用されます。 GROQの構文を以下に示します。

 const data = await sanityAPI.fetch( ` *[_type == 'movie']{ _id, "overview": { "text": overview[0].children[0].text }, "poster" : poster.asset->url, title }| order(popularity desc) [0..2]` );

ここでの唯一の違いは、人気順に映画を並べ替えてから、最初の3つだけを選択したことです。 アイテムはゼロベースのインデックスで返されるため、最初の3つのアイテムはアイテム0、1、2です。最初の10個のアイテムを取得する場合は、 [0..9]を関数に渡すことができます。

これは、 movieActions.jsファイル内のムービーアクションの完全なコードです。

 import sanityAPI from "../../sanitySetup"; import { MOVIE_FETCH_FAIL, MOVIE_FETCH_REQUEST, MOVIE_FETCH_SUCCESS, MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS, MOVIES_SORT_REQUEST, MOVIES_SORT_SUCCESS, MOVIES_SORT_FAIL, MOVIES_MOST_POPULAR_REQUEST, MOVIES_MOST_POPULAR_SUCCESS, MOVIES_MOST_POPULAR_FAIL, MOVIES_REF_FETCH_SUCCESS, MOVIES_REF_FETCH_FAIL, MOVIES_REF_FETCH_REQUEST } from "../constants/movieConstants"; const fetchAllMovies = () => async (dispatch) => { try { dispatch({ type: MOVIES_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster" : poster.asset->url, } ` ); dispatch({ type: MOVIES_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_FETCH_FAIL, payload: error.message }); } }; const fetchMoviesByRef = (ref) => async (dispatch) => { try { dispatch({ type: MOVIES_REF_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie' && (castMembers[person._ref match '${ref}'] || crewMembers[person._ref match '${ref}']) ]{ _id, "poster" : poster.asset->url, title }` ); dispatch({ type: MOVIES_REF_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_REF_FETCH_FAIL, payload: error.message }); } }; const fetchMovieById = (id) => async (dispatch) => { try { dispatch({ type: MOVIE_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie' && _id == '${id}']{ _id, "cast" : castMembers[]{ "ref": person._ref, characterName, "name": person->name, "image": person->image.asset->url } , "crew" : crewMembers[]{ "ref": person._ref, department, job, "name": person->name, "image": person->image.asset->url } , "overview": { "text": overview[0].children[0].text }, popularity, "poster" : poster.asset->url, releaseDate, title }[0]` ); dispatch({ type: MOVIE_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIE_FETCH_FAIL, payload: error.message }); } }; const sortMoviesBy = (item, type) => async (dispatch) => { try { dispatch({ type: MOVIES_MOST_POPULAR_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster" : poster.asset->url, title } | order( ${item} ${type})` ); dispatch({ type: MOVIES_SORT_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_SORT_FAIL, payload: error.message }); } }; const getMostPopular = () => async (dispatch) => { try { dispatch({ type: MOVIES_SORT_REQUEST }); const data = await sanityAPI.fetch( ` *[_type == 'movie']{ _id, "overview": { "text": overview[0].children[0].text }, "poster" : poster.asset->url, title }| order(popularity desc) [0..2]` ); dispatch({ type: MOVIES_MOST_POPULAR_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_MOST_POPULAR_FAIL, payload: error.message }); } }; export { fetchAllMovies, fetchMovieById, sortMoviesBy, getMostPopular, fetchMoviesByRef };

レデューサーの設定

レデューサーはReduxの最も重要な概念の1つです。 それらは前の状態を取り、状態の変化を決定します。

通常、switchステートメントを使用して、各アクションタイプの条件を実行します。 たとえば、アクションタイプが読み込みを示している場合はloadingを返し、成功またはエラーを示している場合はペイロードを返すことができます。 initial stateactionを引数として取ることが期待されます。

movieReducers.jsファイルには、 movieActions.jsファイルで定義されたアクションに一致するさまざまなレデューサーが含まれています。 ただし、各レデューサーの構文と構造は似ています。 唯一の違いは、それらが呼び出すconstantsとそれらが返す値です。

まず、 movieReducers.jsファイルのfetchAllMoviesReducerを見てみましょう。

 import { MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS, } from "../constants/movieConstants"; const fetchAllMoviesReducer = (state = {}, action) => { switch (action.type) { case MOVIES_FETCH_REQUEST: return { loading: true }; case MOVIES_FETCH_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_FETCH_FAIL: return { loading: false, error: action.payload }; case MOVIES_FETCH_RESET: return {}; default: return state; } };

すべてのレデューサーと同様に、 fetchAllMoviesReducerは、初期状態オブジェクト( state )とactionオブジェクトを引数として受け取ります。 switchステートメントを使用して、各時点でのアクションタイプを確認しました。 MOVIES_FETCH_REQUESTに対応する場合、ユーザーに読み込みインジケーターを表示できるように、loadingをtrueとして返します。

MOVIES_FETCH_SUCCESSに対応する場合は、読み込みインジケーターをオフにしてから、アクションペイロードを可変moviesで返します。 ただし、 MOVIES_FETCH_FAILの場合は、読み込みをオフにしてからエラーを返します。 また、ムービーをリセットするオプションも必要です。 これにより、必要なときに状態をクリアできます。

他のレデューサーについても同じ構造です。 完全なmovieReducers.jsを以下に示します。

 import { MOVIE_FETCH_FAIL, MOVIE_FETCH_REQUEST, MOVIE_FETCH_SUCCESS, MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS, MOVIES_SORT_REQUEST, MOVIES_SORT_SUCCESS, MOVIES_SORT_FAIL, MOVIES_MOST_POPULAR_REQUEST, MOVIES_MOST_POPULAR_SUCCESS, MOVIES_MOST_POPULAR_FAIL, MOVIES_FETCH_RESET, MOVIES_REF_FETCH_REQUEST, MOVIES_REF_FETCH_SUCCESS, MOVIES_REF_FETCH_FAIL } from "../constants/movieConstants"; const fetchAllMoviesReducer = (state = {}, action) => { switch (action.type) { case MOVIES_FETCH_REQUEST: return { loading: true }; case MOVIES_FETCH_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_FETCH_FAIL: return { loading: false, error: action.payload }; case MOVIES_FETCH_RESET: return {}; default: return state; } }; const fetchMoviesByRefReducer = (state = {}, action) => { switch (action.type) { case MOVIES_REF_FETCH_REQUEST: return { loading: true }; case MOVIES_REF_FETCH_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_REF_FETCH_FAIL: return { loading: false, error: action.payload }; default: return state; } }; const fetchMovieByIdReducer = (state = {}, action) => { switch (action.type) { case MOVIE_FETCH_REQUEST: return { loading: true }; case MOVIE_FETCH_SUCCESS: return { loading: false, movie: action.payload }; case MOVIE_FETCH_FAIL: return { loading: false, error: action.payload }; default: return state; } }; const sortMoviesByReducer = (state = {}, action) => { switch (action.type) { case MOVIES_SORT_REQUEST: return { loading: true }; case MOVIES_SORT_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_SORT_FAIL: return { loading: false, error: action.payload }; default: return state; } }; const getMostPopularReducer = (state = {}, action) => { switch (action.type) { case MOVIES_MOST_POPULAR_REQUEST: return { loading: true }; case MOVIES_MOST_POPULAR_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_MOST_POPULAR_FAIL: return { loading: false, error: action.payload }; default: return state; } }; export { fetchAllMoviesReducer, fetchMovieByIdReducer, sortMoviesByReducer, getMostPopularReducer, fetchMoviesByRefReducer };

また、 personReducers.jsについてもまったく同じ構造に従いました。 たとえば、 fetchAllPersonsReducer関数は、データベース内のすべての人をフェッチするための状態を定義します。

これは以下のコードで与えられます。

 import { PERSONS_FETCH_FAIL, PERSONS_FETCH_REQUEST, PERSONS_FETCH_SUCCESS, } from "../constants/personConstants"; const fetchAllPersonsReducer = (state = {}, action) => { switch (action.type) { case PERSONS_FETCH_REQUEST: return { loading: true }; case PERSONS_FETCH_SUCCESS: return { loading: false, persons: action.payload }; case PERSONS_FETCH_FAIL: return { loading: false, error: action.payload }; default: return state; } };

fetchAllMoviesReducerと同様に、 stateactionを引数としてfetchAllPersonsReducerを定義しました。 これらはReduxレデューサーの標準設定です。 次に、switchステートメントを使用してアクションタイプをチェックし、タイプがPERSONS_FETCH_REQUESTの場合、loadingをtrueとして返します。 PERSONS_FETCH_SUCCESSの場合は、読み込みをオフにしてペイロードを返し、 PERSONS_FETCH_FAILの場合は、エラーを返します。

レデューサーの組み合わせ

ReduxのcombineReducers関数を使用すると、複数のレデューサーを組み合わせてストアに渡すことができます。 映画と人物のレデューサーを、 reducersフォルダー内のindex.jsファイルに結合します。

それを見てみましょう。

 import { combineReducers } from "redux"; import { fetchAllMoviesReducer, fetchMovieByIdReducer, sortMoviesByReducer, getMostPopularReducer, fetchMoviesByRefReducer } from "./movieReducers"; import { fetchAllPersonsReducer, fetchPersonByIdReducer, countPersonsReducer } from "./personReducers"; import { toggleTheme } from "./globalReducers"; export default combineReducers({ fetchAllMoviesReducer, fetchMovieByIdReducer, fetchAllPersonsReducer, fetchPersonByIdReducer, sortMoviesByReducer, getMostPopularReducer, countPersonsReducer, fetchMoviesByRefReducer, toggleTheme });

ここでは、映画、人物、グローバルレデューサーファイルからすべてのレデューサーをインポートし、 combineReducers関数に渡しました。 combineReducers関数は、すべてのレデューサーを渡すことを可能にするオブジェクトを取ります。 プロセスの引数にエイリアスを追加することもできます。

後でglobalReducersに取り組みます。

これで、 store.jsファイルでレデューサーを渡すことができます。 これを以下に示します。

 import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/index"; export default createStore(reducers, initialState, applyMiddleware(thunk));

Reduxワークフローを設定したら、Reactアプリケーションを設定しましょう。

Reactアプリケーションのセットアップ

私たちのreactアプリケーションは、映画とそれに対応するキャストとクルーメンバーを一覧表示します。 ルーティングにreact-router-domを使用し、アプリのスタイリングにはstyled-componentsを使用します。 アイコンと一部のUIコンポーネントにもマテリアルUIを使用します。

次のbashコマンドを入力して、依存関係をインストールします。

 npm install react-router-dom @material-ui/core @material-ui/icons query-string

これが私たちが構築するものです:

ReduxをReactアプリに接続する

React-reduxには、アプリケーションをReduxストアに接続できるプロバイダー機能が付属しています。 これを行うには、ストアのインスタンスをプロバイダーに渡す必要があります。 これは、 index.jsファイルまたはApp.jsファイルのいずれかで実行できます。

これがindex.jsファイルです。

 import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; import { Provider } from "react-redux"; import store from "./redux/store"; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById("root") );

ここでは、 react-reduxからProviderをインポートし、Redux storeからstoreをインポートしました。 次に、コンポーネントツリー全体をプロバイダーでラップし、ストアをプロバイダーに渡します。

次に、Reactアプリケーションでルーティングするためreact-router-domが必要です。 react-router-domには、パスとルートを定義するために使用できるBrowserRouterSwitch 、およびRouteが付属しています。

これは、 App.jsファイルで行います。 これを以下に示します。

 import React from "react"; import Header from "./components/Header"; import Footer from "./components/Footer"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import MoviesList from "./pages/MoviesListPage"; import PersonsList from "./pages/PersonsListPage"; function App() { return ( <Router> <main className="contentwrap"> <Header /> <Switch> <Route path="/persons/"> <PersonsList /> </Route> <Route path="/" exact> <MoviesList /> </Route> </Switch> </main> <Footer /> </Router> ); } export default App;

これは、react-router-domを使用したルーティングの標準設定です。 あなたは彼らのドキュメントでそれをチェックすることができます。 コンポーネントHeaderFooterPersonsListMovieListをインポートしました。 次に、すべてをRouterSwitchでラップして、 react-router-domを設定します。

ページで同じヘッダーとフッターを共有する必要があるため、構造体をSwitchでラップする前に、 <Header /> >コンポーネントと<Footer />コンポーネントを渡す必要がありました。 アプリケーション全体をラップしたいので、 main要素でも同様のことを行いました。

各コンポーネントを、 react-router-domからのRouteを使用してルートに渡しました。

ページとコンポーネントの定義

私たちのアプリケーションは構造化された方法で編成されています。 再利用可能なコンポーネントはcomponentsフォルダーに保存され、Pagesはpagesフォルダーに保存されます。

私たちのpagesは、 movieListPage.jsmoviePage.jsPersonListPage.jsPersonPage.jsで構成されています。 MovieListPage.jsは、Sanity.ioバックエンド内のすべての映画と最も人気のある映画を一覧表示します。

すべての映画を一覧表示するには、 movieAction.jsファイルで定義されているfetchAllMoviesアクションをdispatchするだけです。 ページが読み込まれるとすぐにリストをフェッチする必要があるため、 useEffectでリストを定義する必要があります。 これを以下に示します。

 import React, { useEffect } from "react"; import { fetchAllMovies } from "../redux/actions/movieActions"; import { useDispatch, useSelector } from "react-redux"; const MoviesListPage = () => { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchAllMovies()); }, [dispatch]); const { loading, error, movies } = useSelector( (state) => state.fetchAllMoviesReducer ); return ( ... ) }; export default MoviesListPage;

useDispatchuseSelectorフックのおかげで、Reduxアクションをディスパッチし、Reduxストアから適切な状態を選択できます。 loadingerrormoviesの状態はレデューサー関数で定義されており、ここではReactReduxのuseSelectorフックを使用してそれらを選択していることに注意してください。 これらの状態、つまりloadingerror 、およびmoviesは、 fetchAllMovies()アクションをディスパッチするとすぐに使用可能になります。

映画のリストを取得したら、 map機能を使用して、または任意の方法でアプリケーションに表示できます。

これがmoviesListPage.jsファイルの完全なコードです。

import React, {useState, useEffect} from 'react' import {fetchAllMovies, getMostPopular, sortMoviesBy} from "../redux/actions/movieActions" import {useDispatch, useSelector} from "react-redux" import Loader from "../components/BackdropLoader" import {MovieListContainer} from "../styles/MovieStyles.js" import SortIcon from '@material-ui/icons/Sort'; import SortModal from "../components/Modal" import {useLocation, Link} from "react-router-dom" import queryString from "query-string" import {MOVIES_FETCH_RESET} from "../redux/constants/movieConstants" const MoviesListPage = () => { const location = useLocation() const dispatch = useDispatch() const [openSort, setOpenSort] = useState(false) useEffect(()=>{ dispatch(getMostPopular()) const {order, type} = queryString.parse(location.search) if(order && type){ dispatch({ type: MOVIES_FETCH_RESET }) dispatch(sortMoviesBy(order, type)) }else{ dispatch(fetchAllMovies()) } }, [dispatch, location.search]) const {loading: popularLoading, error: popularError, movies: popularMovies } = useSelector(state => state.getMostPopularReducer) const { loading: moviesLoading, error: moviesError, movies } = useSelector(state => state.fetchAllMoviesReducer) const { loading: sortLoading, error: sortError, movies: sortMovies } = useSelector(state => state.sortMoviesByReducer) return ( <MovieListContainer> <div className="mostpopular"> { popularLoading ? <Loader /> : popularError ? popularError : popularMovies && popularMovies.map(movie => ( <Link to={`/movie?id=${movie._id}`} className="popular" key={movie._id} style={{backgroundImage: `url(${movie.poster})`}}> <div className="content"> <h2>{movie.title}</h2> <p>{movie.overview.text.substring(0, 50)}…</p> </div> </Link> )) } </div> <div className="moviespanel"> <div className="top"> <h2>All Movies</h2> <SortIcon onClick={()=> setOpenSort(true)} /> </div> <div className="movieslist"> { moviesLoading ? <Loader /> : moviesError ? moviesError : movies && movies.map(movie =>( <Link to={`/movie?id=${movie._id}`} key={movie._id}> <img className="movie" src={movie.poster} alt={movie.title} /> </Link> )) } { ( sortLoading ? !movies && <Loader /> : sortError ? sortError : sortMovies && sortMovies.map(movie =>( <Link to={`/movie?id=${movie._id}`} key={movie._id}> <img className="movie" src={movie.poster} alt={movie.title} /> </Link> )) ) } </div> </div> <SortModal open={openSort} setOpen={setOpenSort} /> </MovieListContainer> ) } export default MoviesListPage

useEffectフックでgetMostPopular映画アクション(このアクションは最も人気のある映画を選択します)をディスパッチすることから始めました。 これにより、ページが読み込まれるとすぐに最も人気のある映画を取得できます。 さらに、ユーザーがreleaseDate日とpopularityで映画を並べ替えることができるようにしました。 これは、上記のコードでディスパッチされたsortMoviesByアクションによって処理されます。 さらに、クエリパラメータに応じてfetchAllMoviesをディスパッチしました。

また、 useSelectorフックを使用して、これらの各アクションに対応するレデューサーを選択しました。 各レデューサーのloadingerrormoviesの状態を選択しました。

レデューサーからmoviesを取得したら、ユーザーに表示できるようになります。 ここでは、ES6 map関数を使用してこれを行いました。 ムービーの各状態が読み込まれているときは常にローダーを最初に表示し、エラーが発生した場合はエラーメッセージを表示します。 最後に、映画を入手した場合、 map機能を使用して映画の画像をユーザーに表示します。 コンポーネント全体をMovieListContainerコンポーネントでラップしました。

<MovieListContainer> … </MovieListContainer>タグは、スタイル付きコンポーネントを使用して定義されたdivです。 それについてはすぐに簡単に見ていきます。

スタイル付きコンポーネントを使用したアプリのスタイリング

スタイル付きコンポーネントを使用すると、ページとコンポーネントを個別にスタイル設定できます。 また、 inheritanceThemingpassing of propsなど、いくつかの興味深い機能も提供します。

私たちは常にページを個別にスタイリングしたいのですが、グローバルなスタイリングが望ましい場合もあります。 興味深いことに、styled-componentsは、 createGlobalStyle関数のおかげでそれを行う方法を提供します。

アプリケーションでスタイル付きコンポーネントを使用するには、それをインストールする必要があります。 反応プロジェクトでターミナルを開き、次のbashコマンドを入力します。

 npm install styled-components

styled-componentsをインストールしたら、グローバルスタイルを始めましょう。

stylesという名前のsrcディレクトリに別のフォルダを作成しましょう。 これにより、すべてのスタイルが保存されます。 また、stylesフォルダー内にglobalStyles.jsファイルを作成しましょう。 styled-componentsでグローバルスタイルを作成するには、 createGlobalStyleをインポートする必要があります。

 import { createGlobalStyle } from "styled-components";

次に、スタイルを次のように定義できます。

 export const GlobalStyle = createGlobalStyle` ... `

スタイル付きコンポーネントは、テンプレートリテラルを使用して小道具を定義します。 このリテラル内で、従来のCSSコードを記述できます。

また、 definition.jsという名前のファイルで定義されたdeviceWidthをインポートしました。 deviceWidthは、メディアクエリを設定するためのブレークポイントの定義を保持します。

 import { deviceWidth } from "./definition";

アプリケーションのフローを制御するために、オーバーフローを非表示に設定しました。

 html, body{ overflow-x: hidden; }

また、 .headerスタイルセレクターを使用してヘッダースタイルを定義しました。

 .header{ z-index: 5; background-color: ${(props)=>props.theme.midDarkBlue}; display:flex; align-items:center; padding: 0 20px; height:50px; justify-content:space-between; position:fixed; top:0; width:100%; @media ${deviceWidth.laptop_lg} { width:97%; } ... }

ここでは、背景色、z-index、パディング、およびその他の多くの従来のCSSプロパティなどのさまざまなスタイルが定義されています。

styled-componentsのpropsを使用して背景色を設定しました。 これにより、コンポーネントから渡すことができる動的変数を設定できます。 さらに、テーマの切り替えを最大限に活用できるように、テーマの変数も渡しました。

ここでは、アプリケーション全体をstyled-componentsのThemeProviderでラップしているため、テーマ設定が可能です。 これについては後で説明します。 さらに、 CSS flexboxを使用してヘッダーのスタイルを適切に設定し、位置をfixedに設定して、ブラウザーに対して固定されたままになるようにしました。 また、ヘッダーをモバイルフレンドリーにするためにブレークポイントを定義しました。

これがglobalStyles.jsファイルの完全なコードです。

 import { createGlobalStyle } from "styled-components"; import { deviceWidth } from "./definition"; export const GlobalStyle = createGlobalStyle` html{ overflow-x: hidden; } body{ background-color: ${(props) => props.theme.lighter}; overflow-x: hidden; min-height: 100vh; display: grid; grid-template-rows: auto 1fr auto; } #root{ display: grid; flex-direction: column; } h1,h2,h3, label{ font-family: 'Aclonica', sans-serif; } h1, h2, h3, p, span:not(.MuiIconButton-label), div:not(.PrivateRadioButtonIcon-root-8), div:not(.tryingthis){ color: ${(props) => props.theme.bodyText} } p, span, div, input{ font-family: 'Jost', sans-serif; } .paginate button{ color: ${(props) => props.theme.bodyText} } .header{ z-index: 5; background-color: ${(props) => props.theme.midDarkBlue}; display: flex; align-items: center; padding: 0 20px; height: 50px; justify-content: space-between; position: fixed; top: 0; width: 100%; @media ${deviceWidth.laptop_lg}{ width: 97%; } @media ${deviceWidth.tablet}{ width: 100%; justify-content: space-around; } a{ text-decoration: none; } label{ cursor: pointer; color: ${(props) => props.theme.goldish}; font-size: 1.5rem; } .hamburger{ cursor: pointer; color: ${(props) => props.theme.white}; @media ${deviceWidth.desktop}{ display: none; } @media ${deviceWidth.tablet}{ display: block; } } } .mobileHeader{ z-index: 5; background-color: ${(props) => props.theme.darkBlue}; color: ${(props) => props.theme.white}; display: grid; place-items: center; width: 100%; @media ${deviceWidth.tablet}{ width: 100%; } height: calc(100% - 50px); transition: all 0.5s ease-in-out; position: fixed; right: 0; top: 50px; .menuitems{ display: flex; box-shadow: 0 0 5px ${(props) => props.theme.lightshadowtheme}; flex-direction: column; align-items: center; justify-content: space-around; height: 60%; width: 40%; a{ display: flex; flex-direction: column; align-items:center; cursor: pointer; color: ${(props) => props.theme.white}; text-decoration: none; &:hover{ border-bottom: 2px solid ${(props) => props.theme.goldish}; .MuiSvgIcon-root{ color: ${(props) => props.theme.lightred} } } } } } footer{ min-height: 30px; margin-top: auto; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.875rem; background-color: ${(props) => props.theme.midDarkBlue}; color: ${(props) => props.theme.white}; } `;

リテラル内に純粋なCSSコードを記述しましたが、いくつかの例外があることに注意してください。 スタイル付きコンポーネントを使用すると、小道具を渡すことができます。 これについて詳しくは、ドキュメントをご覧ください。

グローバルスタイルの定義とは別に、個々のページのスタイルを定義できます。

たとえば、 stylesフォルダーのPersonListPage.jsで定義されているPersonListPage.jsのスタイルは次のPersonStyle.jsです。

 import styled from "styled-components"; import { deviceWidth, colors } from "./definition"; export const PersonsListContainer = styled.div` margin: 50px 80px; @media ${deviceWidth.tablet} { margin: 50px 10px; } a { text-decoration: none; } .top { display: flex; justify-content: flex-end; padding: 5px; .MuiSvgIcon-root { cursor: pointer; &:hover { color: ${colors.darkred}; } } } .personslist { margin-top: 20px; display: grid; place-items: center; grid-template-columns: repeat(5, 1fr); @media ${deviceWidth.laptop} { grid-template-columns: repeat(4, 1fr); } @media ${deviceWidth.tablet} { grid-template-columns: repeat(3, 1fr); } @media ${deviceWidth.tablet_md} { grid-template-columns: repeat(2, 1fr); } @media ${deviceWidth.mobile_lg} { grid-template-columns: repeat(1, 1fr); } grid-gap: 30px; .person { width: 200px; position: relative; img { width: 100%; } .content { position: absolute; bottom: 0; left: 8px; border-right: 2px solid ${colors.goldish}; border-left: 2px solid ${colors.goldish}; border-radius: 10px; width: 80%; margin: 20px auto; padding: 8px 10px; background-color: ${colors.transparentWhite}; color: ${colors.darkBlue}; h2 { font-size: 1.2rem; } } } } `;

最初に、 styled-componentsからstyledをインポートし、 definitionファイルからdeviceWidthをインポートしました。 次に、 PersonsListContainerをスタイルを保持するdivとして定義しました。 メディアクエリと確立されたブレークポイントを使用して、さまざまなブレークポイントを設定することにより、ページをモバイルフレンドリーにしました。

ここでは、小さい画面、大きい画面、および非常に大きい画面に対して、標準のブラウザーブレークポイントのみを使用しました。 また、CSSフレックスボックスとグリッドを最大限に活用して、ページのコンテンツを適切にスタイル設定して表示しました。

PersonListPage.jsファイルでこのスタイルを使用するには、次のようにインポートしてページに追加しました。

 import React from "react"; const PersonsListPage = () => { return ( <PersonsListContainer> ... </PersonsListContainer> ); }; export default PersonsListPage;

スタイルでdivとして定義したため、ラッパーはdivを出力します。

テーマを追加してまとめる

アプリケーションにテーマを追加することは、常に優れた機能です。 このためには、次のものが必要です。

  • 別のファイル(この場合はdefinition.jsファイル)で定義されたカスタムテーマ。
  • Reduxアクションとレデューサーで定義されたロジック。
  • アプリケーションでテーマを呼び出し、それをコンポーネントツリーに渡します。

これをチェックしてみましょう。

これがdefinition.jsファイルのthemeオブジェクトです。

 export const theme = { light: { dark: "#0B0C10", darkBlue: "#253858", midDarkBlue: "#42526e", lightBlue: "#0065ff", normal: "#dcdcdd", lighter: "#F4F5F7", white: "#FFFFFF", darkred: "#E85A4F", lightred: "#E98074", goldish: "#FFC400", bodyText: "#0B0C10", lightshadowtheme: "rgba(0, 0, 0, 0.1)" }, dark: { dark: "white", darkBlue: "#06090F", midDarkBlue: "#161B22", normal: "#dcdcdd", lighter: "#06090F", white: "white", darkred: "#E85A4F", lightred: "#E98074", goldish: "#FFC400", bodyText: "white", lightshadowtheme: "rgba(255, 255, 255, 0.9)" } };

明るいテーマと暗いテーマにさまざまな色のプロパティを追加しました。 ライトモードとダークモードの両方で視認できるように、色は慎重に選択されています。 テーマは自由に定義できます。 これは厳格なルールではありません。

次に、Reduxに機能を追加しましょう。

ReduxアクションフォルダーにglobalActions.jsを作成し、次のコードを追加しました。

 import { SET_DARK_THEME, SET_LIGHT_THEME } from "../constants/globalConstants"; import { theme } from "../../styles/definition"; export const switchToLightTheme = () => (dispatch) => { dispatch({ type: SET_LIGHT_THEME, payload: theme.light }); localStorage.setItem("theme", JSON.stringify(theme.light)); localStorage.setItem("light", JSON.stringify(true)); }; export const switchToDarkTheme = () => (dispatch) => { dispatch({ type: SET_DARK_THEME, payload: theme.dark }); localStorage.setItem("theme", JSON.stringify(theme.dark)); localStorage.setItem("light", JSON.stringify(false)); };

ここでは、定義したテーマをインポートしただけです。 対応するアクションをディスパッチし、必要なテーマのペイロードを渡しました。 ペイロードの結果は、明るいテーマと暗いテーマの両方で同じキーを使用してローカルストレージに保存されます。 これにより、ブラウザで状態を保持できます。

また、テーマのレデューサーを定義する必要があります。

 import { SET_DARK_THEME, SET_LIGHT_THEME } from "../constants/globalConstants"; export const toggleTheme = (state = {}, action) => { switch (action.type) { case SET_LIGHT_THEME: return { theme: action.payload, light: true }; case SET_DARK_THEME: return { theme: action.payload, light: false }; default: return state; } };

これは私たちがやってきたことと非常に似ています。 switchステートメントを使用してアクションのタイプを確認してから、適切なpayloadを返しました。 また、ユーザーが明るいテーマと暗いテーマのどちらを選択したかを決定する状態lightを返しました。 これをコンポーネントで使用します。

また、ルートレデューサーとストアに追加する必要があります。 これがstore.jsの完全なコードです。

 import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import { theme as initialTheme } from "../styles/definition"; import reducers from "./reducers/index"; const theme = localStorage.getItem("theme") ? JSON.parse(localStorage.getItem("theme")) : initialTheme.light; const light = localStorage.getItem("light") ? JSON.parse(localStorage.getItem("light")) : true; const initialState = { toggleTheme: { light, theme } }; export default createStore(reducers, initialState, applyMiddleware(thunk));

ユーザーが更新するときにテーマを永続化する必要があるため、 localStorage.getItem()を使用してローカルストレージからテーマを取得し、初期状態に渡す必要がありました。

Reactアプリケーションに機能を追加する

スタイル付きコンポーネントは、アプリケーションを介してテーマを渡すことができるThemeProviderを提供します。 App.jsファイルを変更して、この機能を追加できます。

それを見てみましょう。

 import React from "react"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import { useSelector } from "react-redux"; import { ThemeProvider } from "styled-components"; function App() { const { theme } = useSelector((state) => state.toggleTheme); let Theme = theme ? theme : {}; return ( <ThemeProvider theme={Theme}> <Router> ... </Router> </ThemeProvider> ); } export default App;

テーマをThemeProviderに渡すことで、スタイルでテーマの小道具を簡単に使用できます。

たとえば、次のように、色をbodyTextカスタム色に設定できます。

 color: ${(props) => props.theme.bodyText};

アプリケーションで色が必要な場所ならどこでもカスタムテーマを使用できます。

たとえば、 border-bottomを定義するには、次のようにします。

 border-bottom: 2px solid ${(props) => props.theme.goldish};

結論

まず、Sanity.ioを詳しく調べてセットアップし、Reactアプリケーションに接続しました。 次に、Reduxをセットアップし、GROQ言語を使用してAPIをクエリしました。 私たちは、 react-reduxを使用してReactアプリにReduxを接続して使用する方法、styled-componentsとテーマを使用する方法を見ました。

ただし、これらのテクノロジーで可能なことのほんの一部にすぎません。 私のGitHubリポジトリのコードサンプルを確認し、これらのテクノロジーを使用してまったく異なるプロジェクトを試して、それらを学習および習得することをお勧めします。

資力

  • 正気のドキュメント
  • KapeheによるSanity.ioでブログを構築する方法
  • Reduxドキュメント
  • スタイル付きコンポーネントのドキュメント
  • GROQチートシート
  • マテリアルUIドキュメント
  • ReduxミドルウェアとSideEffects
  • ReduxThunkドキュメント