Webpackを使用した最新のReactプロジェクトのTypeScriptの設定
公開: 2022-03-10ソフトウェア開発のこの時代では、JavaScriptを使用してほぼすべてのタイプのアプリを開発できます。 ただし、JavaScriptが動的に型付けされるという事実は、型チェック機能が緩いため、ほとんどの大企業にとって懸念事項となる可能性があります。
幸い、Ecma技術委員会39が静的型システムをJavaScriptに導入するまで待つ必要はありません。 代わりにTypeScriptを使用できます。
動的に型付けされるJavaScriptは、実行時に変数がインスタンス化されるまで、変数のデータ型を認識しません。 大規模なソフトウェアプログラムを作成する開発者は、以前に宣言された変数を別のタイプの値に再割り当てする傾向があり、警告や問題はまったく発生せず、バグが見過ごされがちです。
このチュートリアルでは、TypeScriptとは何か、およびReactプロジェクトでTypeScriptを操作する方法を学習します。 最後に、TypeScriptと現在のReactのようなフック( useState
、 useEffect
、 useReducer
、 useContext
)を使用して、テレビ番組MoneyHeistのエピソードピッカーアプリで構成されるプロジェクトを構築します。 この知識があれば、自分のプロジェクトでTypeScriptを試すことができます。
この記事はTypeScriptの紹介ではありません。 したがって、TypeScriptとJavaScriptの基本的な構文については説明しません。 ただし、KISSの原則に従おうとするため、これらの言語のいずれかの専門家である必要はありません(シンプルで愚かなことを維持してください)。
TypeScriptとは何ですか?
2019年、TypeScriptはGitHubで7番目に使用されている言語であり、5番目に急成長している言語にランク付けされました。 しかし、TypeScriptとは正確には何ですか?
公式ドキュメントによると、TypeScriptはJavaScriptの型付きスーパーセットであり、プレーンJavaScriptにコンパイルされます。 これは、Microsoftとオープンソースコミュニティによって開発および保守されています。
この文脈での「スーパーセット」とは、言語にJavaScriptのすべての機能と機能が含まれ、次に一部が含まれることを意味します。 TypeScriptは、型付きスクリプト言語です。
開発者は、型アノテーション、クラス、およびインターフェイスを介してコードベースをより細かく制御できるため、開発者はコンソールの厄介なバグを手動で修正する必要がありません。
TypeScriptはJavaScriptを変更するために作成されたものではありません。 代わりに、貴重な新機能を備えたJavaScriptを拡張します。 クロスプラットフォームのモバイルアプリやNode.jsのバックエンドなど、プレーンJavaScriptで記述されたプログラムもTypeScriptで期待どおりに実行されます。
これは、このチュートリアルで行うように、TypeScriptでReactアプリを作成することもできることを意味します。
なぜTypeScriptなのか?
おそらく、TypeScriptの良さを受け入れることに確信が持てないでしょう。 その利点のいくつかを考えてみましょう。
バグが少ない
コード内のすべてのバグを排除することはできませんが、それらを減らすことはできます。 TypeScriptはコンパイル時に型をチェックし、変数の型が変更されるとエラーをスローします。
これらの明白でありながら頻繁なエラーをこの早い段階で見つけることができると、型を使用してコードを管理するのがはるかに簡単になります。
リファクタリングが簡単
多くの場合、かなり多くのことをリファクタリングしたいと思うでしょうが、それらは他の多くのコードや他の多くのファイルに影響を与えるため、それらを変更することには注意が必要です。
TypeScriptでは、統合開発環境(IDE)で「シンボルの名前変更」コマンドをクリックするだけで、このようなことをリファクタリングできることがよくあります。
JavaScriptなどの動的型付け言語では、複数のファイルを同時にリファクタリングする唯一の方法は、正規表現(RegExp)を使用する従来の「検索と置換」機能を使用することです。
TypeScriptのような静的に型付けされた言語では、「検索と置換」はもう必要ありません。 「すべてのオカレンスを検索」や「シンボルの名前を変更」などのIDEコマンドを使用すると、オブジェクトインターフェイスの特定の関数、クラス、またはプロパティのアプリですべてのオカレンスを確認できます。
TypeScriptは、リファクタリングされたビットのすべてのインスタンスを見つけて名前を変更し、リファクタリング後にコードに型の不一致がある場合にコンパイルエラーで警告するのに役立ちます。
TypeScriptには、ここで説明したものよりもさらに多くの利点があります。
TypeScriptのデメリット
上記で強調した有望な機能を考えても、TypeScriptには確かに欠点がないわけではありません。
誤った安心感
TypeScriptのタイプチェック機能は、多くの場合、開発者の間に誤った安心感を生み出します。 型チェックは、コードに問題がある場合に実際に警告します。 ただし、静的タイプは全体的なバグ密度を低下させません。
したがって、型は開発者によって作成され、実行時にチェックされないため、プログラムの強度はTypeScriptの使用法によって異なります。
バグを減らすためにTypeScriptを検討している場合は、代わりにテスト駆動開発を検討してください。
複雑なタイピングシステム
タイピングシステムは、多くの点で優れたツールですが、少し複雑になる場合があります。 この欠点は、JavaScriptと完全に相互運用可能であるため、複雑になる余地がさらに大きくなることに起因します。
ただし、TypeScriptは依然としてJavaScriptであるため、JavaScriptを理解することが重要です。
TypeScriptを使用する場合
次の場合はTypeScriptを使用することをお勧めします。
- 長期間維持されるアプリケーションの構築を検討している場合は、TypeScriptから始めることを強くお勧めします。これは、自己文書化コードを促進し、他の開発者がコードベースに参加するときにコードを簡単に理解できるようにするためです。 。
- ライブラリを作成する必要がある場合は、TypeScriptで作成することを検討してください。 これは、コードエディタがライブラリを使用している開発者に適切なタイプを提案するのに役立ちます。
最後のいくつかのセクションでは、TypeScriptの長所と短所のバランスを取りました。 今日のビジネスに移りましょう:最新のReactプロジェクトでTypeScriptをセットアップします。
入門
ReactプロジェクトでTypeScriptを設定する方法はいくつかあります。 このチュートリアルでは、2つだけを取り上げます。
方法1:React App + TypeScriptを作成する
約2年前、ReactチームはTypeScriptをサポートするCreate React App2.1をリリースしました。 したがって、TypeScriptをプロジェクトに組み込むために、手間のかかる作業を行う必要はありません。
新しいCreateReact Appプロジェクトを開始するには、これを実行できます…
npx create-react-app my-app --folder-name
…またはこれ:
yarn create react-app my-app --folder-name
TypeScriptをCreateReact Appプロジェクトに追加するには、最初にTypeScriptとそれぞれ@types
をインストールします。
npm install --save typescript @types/node @types/react @types/react-dom @types/jest
… また:
yarn add typescript @types/node @types/react @types/react-dom @types/jest
次に、ファイルの名前を変更し(たとえば、 index.js
をindex.tsx
に)、開発サーバーを再起動します。
速かったですね。
方法2:Webpackを使用してTypeScriptを設定する
Webpackは、JavaScriptアプリケーション用の静的モジュールバンドラーです。 アプリケーションからすべてのコードを取得し、Webブラウザーで使用できるようにします。 モジュールは、アプリのJavaScript、 node_modules
、画像、CSSスタイルから構築された再利用可能なコードのチャンクであり、ウェブサイトで簡単に使用できるようにパッケージ化されています。
新しいプロジェクトを作成する
プロジェクトの新しいディレクトリを作成することから始めましょう。
mkdir react-webpack cd react-webpack
npmを使用してプロジェクトを初期化します。
npm init -y
上記のコマンドは、いくつかのデフォルト値を含むpackage.json
ファイルを生成します。 また、webpack、TypeScript、およびReact固有のモジュールの依存関係をいくつか追加しましょう。
パッケージのインストール
最後に、必要なパッケージをインストールする必要があります。 コマンドラインインターフェイス(CLI)を開き、次のコマンドを実行します。
#Installing devDependencies npm install --save-dev @types/react @types/react-dom awesome-typescript-loader css-loader html-webpack-plugin mini-css-extract-plugin source-map-loader typescript webpack webpack-cli webpack-dev-server #installing Dependencies npm install react react-dom
また、 react-webpack
フォルダーの下にいくつかの異なるファイルとフォルダーを手動で追加しましょう。
-
webpack.config.js
を追加して、webpack関連の構成を追加します。 - すべてのTypeScript構成に
tsconfig.json
を追加します。 - 新しいディレクトリ
src
を追加します。 -
src
フォルダに新しいディレクトリcomponents
を作成します。 - 最後に、
components
フォルダーにindex.html
、App.tsx
、およびindex.tsx
を追加します。
プロジェクト構造
したがって、フォルダ構造は次のようになります。
├── package.json ├── package-lock.json ├── tsconfig.json ├── webpack.config.js ├── .gitignore └── src └──components ├── App.tsx ├── index.tsx ├── index.html
コードの追加を開始する
index.html
から始めましょう:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>React-Webpack Setup</title> </head> <body> <div></div> </body> </html>
これにより、 output
IDの空のdiv
を持つHTMLが作成されます。
ReactコンポーネントApp.tsx
にコードを追加しましょう:
import * as React from "react"; export interface HelloWorldProps { userName: string; lang: string; } export const App = (props: HelloWorldProps) => ( <h1> Hi {props.userName} from React! Welcome to {props.lang}! </h1> );
インターフェイスオブジェクトを作成し、 HelloWorldProps
という名前を付けましたuserName
とlang
はstring
型です。
props
をApp
コンポーネントに渡し、エクスポートしました。
それでは、 index.tsx
のコードを更新しましょう:
import * as React from "react"; import * as ReactDOM from "react-dom"; import { App } from "./App"; ReactDOM.render( <App userName="Beveloper" lang="TypeScript" />, document.getElementById("output") );
App
コンポーネントをindex.tsx
にインポートしました。 webpackは、拡張子が.ts
または.tsx
のファイルを検出すると、awesome-typescript-loaderライブラリを使用してそのファイルをトランスパイルします。
TypeScript構成
次に、 tsconfig.json
にいくつかの構成を追加します。
{ "compilerOptions": { "jsx": "react", "module": "commonjs", "noImplicitAny": true, "outDir": "./build/", "preserveConstEnums": true, "removeComments": true, "sourceMap": true, "target": "es5" }, "include": [ "src/components/index.tsx" ] }
tsconfig.json
に追加したさまざまなオプションも見てみましょう。
-
compilerOptions
さまざまなコンパイラオプションを表します。 -
jsx:react
.tsx
ファイルでJSXのサポートを追加します。 -
lib
ライブラリファイルのリストをコンパイルに追加します(たとえば、es2015
を使用すると、ECMAScript 6構文を使用できます)。 -
module
モジュールコードを生成します。 -
noImplicitAny
暗黙any
タイプの宣言に対してエラーを発生させます。 -
outDir
出力ディレクトリを表します。 -
sourceMap
.map
ファイルを生成します。これは、アプリのデバッグに非常に役立ちます。 -
target
コードをトランスパイルするターゲットECMAScriptバージョンを表します(特定のブラウザー要件に基づいてバージョンを追加できます)。 -
include
ファイルリストを指定するために使用されます。
Webpackの構成
webpack構成をwebpack.config.js
に追加しましょう。
const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { entry: "./src/components/index.tsx", target: "web", mode: "development", output: { path: path.resolve(\__dirname, "build"), filename: "bundle.js", }, resolve: { extensions: [".js", ".jsx", ".json", ".ts", ".tsx"], }, module: { rules: [ { test: /\.(ts|tsx)$/, loader: "awesome-typescript-loader", }, { enforce: "pre", test: /\.js$/, loader: "source-map-loader", }, { test: /\.css$/, loader: "css-loader", }, ], }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(\__dirname, "src", "components", "index.html"), }), new MiniCssExtractPlugin({ filename: "./src/yourfile.css", }), ], };
webpack.config.js
に追加したさまざまなオプションを見てみましょう。
-
entry
これは、アプリのエントリポイントを指定します。 ビルドに含める単一のファイルまたはファイルの配列の場合があります。 -
output
これには、出力構成が含まれます。 アプリは、バンドルされたコードをプロジェクトからディスクに出力しようとするときにこれを確認します。 パスは出力先のコードの出力ディレクトリを表し、ファイル名は同じコードのファイル名を表します。 通常、bundle.js
という名前です。 -
resolve
Webpackはこの属性を調べて、ファイルをバンドルするかスキップするかを決定します。 したがって、このプロジェクトでは、webpackは、拡張子が.js
、.jsx
、.json
、.ts
、および.tsx
のファイルをバンドルと見なします。 -
module
ローダーを使用して、アプリから要求されたときにwebpackが特定のファイルをロードできるようにすることができます。 次のことを指定するルールオブジェクトを取ります。- 拡張子が
.tsx
または.ts
で終わるファイルは、ロードするためにawesome-typescript-loader
を使用する必要があります。 - 拡張子が
.js
で終わるファイルは、source-map-loader
を使用してロードする必要があります。 - 拡張子が
.css
で終わるファイルは、css-loader
を使用してロードする必要があります。
- 拡張子が
-
plugins
Webpackには独自の制限があり、それらを克服して機能を拡張するためのプラグインを提供します。 たとえば、html-webpack-plugin
は、。./src/component/index.html
ディレクトリのindex.html
ファイルからブラウザにレンダリングされるテンプレートファイルを作成します。
MiniCssExtractPlugin
は、アプリの親CSS
ファイルをレンダリングします。
package.jsonへのスクリプトの追加
package.json
ファイルにReactアプリをビルドするためのさまざまなスクリプトを追加できます。
"scripts": { "start": "webpack-dev-server --open", "build": "webpack" },
次に、CLIでnpm start
を実行します。 すべてがうまくいった場合は、次のように表示されます。
webpackのコツがある場合は、このセットアップのリポジトリのクローンを作成し、プロジェクト全体で使用してください。
ファイルの作成
src
フォルダーとindex.tsx
ファイルを作成します。 これは、Reactをレンダリングするベースファイルになります。
ここで、 npm start
を実行すると、サーバーが実行され、新しいタブが開きます。 npm run build
を実行すると、本番用のwebpackがビルドされ、ビルドフォルダーが作成されます。
Create React Appとwebpackの設定方法を使用して、TypeScriptを最初から設定する方法を見てきました。
TypeScriptを完全に把握する最も簡単な方法の1つは、既存のバニラReactプロジェクトの1つをTypeScriptに変換することです。 残念ながら、既存のバニラReactプロジェクトでTypeScriptを段階的に採用すると、すべてのファイルを取り出したり名前を変更したりする必要があり、プロジェクトが大規模なチームに属している場合、競合や巨大なプルリクエストが発生するため、ストレスがかかります。
次に、ReactプロジェクトをTypeScriptに簡単に移行する方法を見ていきます。
既存のCreateReactアプリをTypeScriptに移行する
このプロセスをより管理しやすくするために、ステップに分割します。これにより、個々のチャンクに移行できるようになります。 プロジェクトを移行するために実行する手順は次のとおりです。
- TypeScriptとタイプを追加します。
-
tsconfig.json
を追加します。 - 小さく始めます。
- ファイル拡張子の名前を
.tsx
に変更します。
1.プロジェクトにTypeScriptを追加します
まず、TypeScriptをプロジェクトに追加する必要があります。 ReactプロジェクトがCreateReact Appでブートストラップされたと仮定すると、次のように実行できます。
# Using npm npm install --save typescript @types/node @types/react @types/react-dom @types/jest # Using Yarn yarn add typescript @types/node @types/react @types/react-dom @types/jest
TypeScriptにはまだ何も変更していないことに注意してください。 プロジェクトをローカルで開始するコマンド( npm start
またはyarn start
)を実行しても、何も変わりません。 もしそうなら、それなら素晴らしいです! 次のステップの準備が整いました。
tsconfig.json
ファイルを追加します
TypeScriptを利用する前に、 tsconfig.json
ファイルを使用してTypeScriptを構成する必要があります。 開始する最も簡単な方法は、次のコマンドを使用して1つをスキャフォールディングすることです。
npx tsc --init
これにより、コメント付きのコードがたくさん含まれているいくつかの基本がわかります。 ここで、 tsconfig.json
のすべてのコードを次のように置き換えます。
{ "compilerOptions": { "jsx": "react", "module": "commonjs", "noImplicitAny": true, "outDir": "./build/", "preserveConstEnums": true, "removeComments": true, "sourceMap": true, "target": "es5" }, "include": [ "./src/**/**/\*" ] }
TypeScript構成
tsconfig.json
に追加したさまざまなオプションも見てみましょう。
-
compilerOptions
さまざまなコンパイラオプションを表します。-
target
新しいJavaScriptコンストラクトをECMAScript5などの古いバージョンに変換します。 -
lib
ライブラリファイルのリストをコンパイルに追加します(たとえば、es2015を使用すると、ECMAScript 6構文を使用できます)。 -
jsx:react
.tsx
ファイルでJSXのサポートを追加します。 -
lib
ライブラリファイルのリストをコンパイルに追加します(たとえば、es2015を使用すると、ECMAScript 6構文を使用できます)。 -
module
モジュールコードを生成します。 -
noImplicitAny
暗黙any
タイプを含む宣言のエラーを発生させるために使用されます。 -
outDir
出力ディレクトリを表します。 -
sourceMap
.map
ファイルを生成します。これは、アプリのデバッグに非常に役立ちます。 -
include
ファイルリストを指定するために使用されます。
-
構成オプションは、プロジェクトの需要に応じて異なります。 TypeScriptオプションのスプレッドシートをチェックして、プロジェクトに何が適合するかを判断する必要がある場合があります。
私たちは物事を準備するために必要な行動をとっただけです。 次のステップは、ファイルをTypeScriptに移行することです。
3.単純なコンポーネントから始める
TypeScriptの機能を活用して徐々に採用していきます。 自分のペースで一度に1つのファイルに移動します。 あなたとあなたのチームにとって意味のあることをしてください。 一度にすべてに取り組もうとしないでください。
これを適切に変換するには、次の2つのことを行う必要があります。
- ファイル拡張子を
.tsx
に変更します。 - タイプ注釈を追加します(これには、TypeScriptの知識が必要です)。
4.ファイル拡張子の名前を.tsx
に変更します
大規模なコードベースでは、ファイルの名前を個別に変更するのは面倒に思えるかもしれません。
macOSで複数のファイルの名前を変更します
複数のファイルの名前を変更すると、時間が無駄になる可能性があります。 Macでそれを行う方法は次のとおりです。 名前を変更するファイルが含まれているフォルダを右クリック(またはCtrl
+クリックするか、MacBookを使用している場合はトラックパッドを2本の指で同時にクリック)します。 次に、「Finderで表示」をクリックします。 Finderで、名前を変更するすべてのファイルを選択します。 選択したファイルを右クリックし、「Xアイテムの名前を変更…」を選択すると、次のように表示されます。
検索したい文字列と、見つかった文字列を置き換える文字列を挿入し、「名前の変更」をクリックします。 終わり。
Windowsで複数のファイルの名前を変更する
Windowsで複数のファイルの名前を変更することはこのチュートリアルの範囲を超えていますが、完全なガイドが利用可能です。 通常、ファイルの名前を変更するとエラーが発生します。 タイプアノテーションを追加するだけです。 これについては、ドキュメントでブラッシュアップできます。
ReactアプリでTypeScriptを設定する方法について説明しました。 それでは、TypeScriptを使用してMoneyHeist用のエピソードピッカーアプリを作成しましょう。
TypeScriptの基本的なタイプについては説明しません。 このチュートリアルを続行する前に、ドキュメントを確認する必要があります。
構築する時間
このプロセスの煩わしさを軽減するために、これをいくつかのステップに分割します。これにより、アプリを個々のチャンクで構築できるようになります。 MoneyHeistエピソードピッカーを作成するために実行するすべての手順は次のとおりです。
- CreateReactアプリを足場にします。
- エピソードを取得します。
- interface.tsで、エピソードに適切なタイプとインターフェイスを作成し
interface.ts
。 -
store.tsx
でエピソードをフェッチするためのストアを設定します。 -
action.ts
でエピソードをフェッチするためのアクションを作成します。 - フェッチされたエピソードを保持する
EpisodeList.tsx
コンポーネントを作成します。 -
React Lazy and Suspense
を使用して、EpisodesList
コンポーネントをホームページにインポートします。
- interface.tsで、エピソードに適切なタイプとインターフェイスを作成し
- エピソードを追加します。
-
store.tsx
にエピソードを追加するようにストアを設定します。 -
action.ts
にエピソードを追加するためのアクションを作成します。
-
- エピソードを削除します。
-
store.tsx
のエピソードを削除するためのストアを設定します。 -
action.ts
でエピソードを削除するためのアクションを作成します。
-
- 好きなエピソード。
- お気に入りのエピソードの
EpisodesList
コンポーネントをインポートします。 - お気に入りのエピソード内に
EpisodesList
をレンダリングします。
- お気に入りのエピソードの
- ナビゲーションにリーチルーターを使用する。
Reactを設定する
Reactを設定する最も簡単な方法は、Create ReactAppを使用することです。 Create React Appは、単一ページのReactアプリケーションを作成するために公式にサポートされている方法です。 構成なしの最新のビルドセットアップを提供します。
これを利用して、構築するアプリケーションをブートストラップします。 CLIから、以下のコマンドを実行します。
npx create-react-app react-ts-app && cd react-ts-app
インストールが成功したら、npmstartを実行してReactサーバーをnpm start
します。
Typescriptのインターフェースとタイプを理解する
TypeScriptのインターフェイスは、オブジェクトのプロパティに型を指定する必要がある場合に使用されます。 したがって、インターフェイスを使用して型を定義します。
interface Employee { name: string, role: string salary: number } const bestEmployee: Employee= { name: 'John Doe', role: 'IOS Developer', salary: '$8500' //notice we are using a string }
上記のコードをコンパイルすると、次のエラーが表示されます。「プロパティsalary
のタイプに互換性がありません。 タイプstring
をタイプnumber
に割り当てることはできません。」
このようなエラーは、プロパティまたは変数に定義された型以外の型が割り当てられている場合にTypeScriptで発生します。 具体的には、上記のスニペットは、 salary
プロパティにnumber
型ではなくstring
型が割り当てられたことを意味します。
src
フォルダーにinterface.ts
ファイルを作成しましょう。 このコードをコピーして貼り付けます。
/** |-------------------------------------------------- | All the interfaces! |-------------------------------------------------- */ export interface IEpisode { airdate: string airstamp: string airtime: string id: number image: { medium: string; original: string } name: string number: number runtime: number season: number summary: string url: string } export interface IState { episodes: Array<IEpisode> favourites: Array<IEpisode> } export interface IAction { type: string payload: Array<IEpisode> | any } export type Dispatch = React.Dispatch<IAction> export type FavAction = ( state: IState, dispatch: Dispatch, episode: IEpisode ) => IAction export interface IEpisodeProps { episodes: Array<IEpisode> store: { state: IState; dispatch: Dispatch } toggleFavAction: FavAction favourites: Array<IEpisode> } export interface IProps { episodes: Array<IEpisode> store: { state: IState; dispatch: Dispatch } toggleFavAction: FavAction favourites: Array<IEpisode> }
インターフェイスの名前に「I」を追加することをお勧めします。 コードを読みやすくします。 ただし、除外することもできます。
IEpisodeインターフェース
APIは、 airdate
、 airstamp
、 airtime
、 id
、 image
、 name
、 number
、 runtime
、 season
、 summary
、 url
などの一連のプロパティを返します。 したがって、 IEpisode
インターフェイスを定義し、適切なデータ型をオブジェクトプロパティに設定しました。
IStateインターフェース
IState
インターフェースには、それぞれepisodes
とfavorites
プロパティ、およびArray<IEpisode>
インターフェースがあります。
IAction
IAction
インターフェイスのプロパティは、 payload
とtype
です。 type
プロパティには文字列型があり、ペイロードにはArray | any
の型があります。 Array | any
。
Array | any
Array | any
は、エピソードインターフェイスまたは任意のタイプの配列を意味します。
Dispatch
タイプはReact.Dispatch
および<IAction>
インターフェースに設定されています。 @types/react
コードベースによると、 React.Dispatch
はdispatch
関数の標準タイプであり、 <IAction>
はインターフェイスアクションの配列であることに注意してください。
また、Visual StudioCodeにはTypeScriptチェッカーがあります。 したがって、コードを強調表示するかホバーするだけで、適切なタイプを提案するのに十分賢いです。
つまり、アプリ全体でインターフェースを利用するには、それをエクスポートする必要があります。 これまでのところ、オブジェクトのタイプを保持するストアとインターフェイスがあります。 それでは、ストアを作成しましょう。 他のインターフェースは、説明されているものと同じ規則に従うことに注意してください。
エピソードを取得する
ストアの作成
エピソードを取得するには、データの初期状態を保持し、レデューサー関数を定義するストアが必要です。
useReducer
フックを使用して設定します。 src
フォルダーにstore.tsx
ファイルを作成します。 次のコードをコピーして貼り付けます。
import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }
import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }
import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }
import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }
ストアを作成するために行った手順は次のとおりです。
- ストアを定義するには、
useReducer
フックとReactのcreateContext
APIが必要です。これが、ストアをインポートした理由です。 - ./types/interfacesから
IState
とIAction
をインポートし./types/interfaces
た。 - タイプが
IState
のinitialState
オブジェクトを宣言し、エピソードとお気に入りのプロパティをそれぞれ空の配列に設定しました。 - 次に、
createContext
メソッドを保持し、initialState
が渡されるStore
変数を作成しました。
createContext
メソッドタイプは<IState | any>
<IState | any>
。これは、 <IState>
またはany
のタイプである可能性があることを意味します。 この記事で頻繁に使用されるタイプを確認しany
。
- 次に、
reducer
関数を宣言し、state
とaction
をパラメーターとして渡しました。reducer
関数には、action.type
の値をチェックするswitchステートメントがあります。 値がFETCH_DATA
の場合、状態(...state)
とアクションペイロードを保持するエピソード状態のコピーを持つオブジェクトを返します。 - switchステートメントでは、
default
の状態を返します。
レデューサー関数のstate
パラメーターとaction
パラメーターには、それぞれIState
タイプとIAction
タイプがあることに注意してください。 また、 reducer
関数のタイプはIState
。
- 最後に、
StoreProvider
関数を宣言しました。 これにより、アプリ内のすべてのコンポーネントがストアにアクセスできるようになります。 - この関数は
children
を小道具として受け取り、StorePrivder
関数内でuseReducer
フックを宣言しました。 -
state
を解体してdispatch
ます。 - ストアをすべてのコンポーネントにアクセスできるようにするために、
state
とdispatch
を含むオブジェクト値を渡しました。
エピソードとお気に入りのstate
を含む状態は、他のコンポーネントからアクセスできるようになりますが、 dispatch
は状態を変更する機能です。
-
Store
とStoreProvider
をエクスポートして、アプリケーション全体で使用できるようにします。
Action.tsを作成する
ユーザーに表示されるエピソードを取得するには、APIにリクエストを送信する必要があります。 これは、アクションファイルで実行されます。 Action.ts
ファイルを作成し、次のコードを貼り付けます。
import { Dispatch } from './interface/interfaces' export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON.\_embedded.episodes }) }
まず、このファイルで使用できるように、インターフェイスをインポートする必要があります。 アクションを作成するために、次の手順が実行されました。
-
fetchDataAction
関数は、dispatch
小道具をパラメーターとして受け取ります。 - この関数は非同期であるため、
async
とawait
を使用します。 - APIエンドポイントを保持する変数(
URL
)を作成します。 - APIからの応答を保持する
data
という名前の別の変数があります。 - 次に、
data.json()
を呼び出してJSON形式で応答を取得した後、JSON応答をdataJSON
に格納します。 - 最後に、
type
のプロパティとFETCH_DATA
の文字列を持つディスパッチ関数を返します。 また、payload()
もあります。_embedded.episodes
は、endpoint
からのエピソードオブジェクトの配列です。
fetchDataAction
関数は、エンドポイントをフェッチし、それをJSON
オブジェクトに変換して、ストアで以前に宣言された状態を更新するディスパッチ関数を返すことに注意してください。
エクスポートされたディスパッチタイプはReact.Dispatch
に設定されます。 React.Dispatch
は、 @types/react
コードベースに従ったディスパッチ関数の標準タイプであり、 <IAction>
はインターフェイスアクションの配列であることに注意してください。
EpisodesListコンポーネント
アプリの再利用性を維持するために、フェッチされたすべてのエピソードを別のファイルに保存してから、そのファイルをhomePage
コンポーネントにインポートします。
components
フォルダで、 EpisodesList.tsx
ファイルを作成し、次のコードをコピーして貼り付けます。
import React from 'react' import { IEpisode, IProps } from '../types/interfaces' const EpisodesList = (props: IProps): Array<JSX.Element> => { const { episodes } = props return episodes.map((episode: IEpisode) => { return ( <section key={episode.id} className='episode-box'> <img src={!!episode.image ? episode.image.medium : ''} alt={`Money Heist ${episode.name}`} /> <div>{episode.name}</div> <section style={{ display: 'flex', justifyContent: 'space-between' }}> <div> Season: {episode.season} Number: {episode.number} </div> <button type='button' > Fav </button> </section> </section> ) }) } export default EpisodesList
-
IEpisode
とIProps
をinterfaces.tsx
からインポートします。 - 次に、小道具を
EpisodesList
関数を作成します。 小道具のタイプはIProps
で、関数のタイプはArray<JSX.Element>
です。
Visual Studio Codeは、関数型をJSX.Element[]
として記述することを提案しています。
Array<JSX.Element>
はJSX.Element[]
と同じですが、 Array<JSX.Element>
は総称IDと呼ばれます。 したがって、この記事では一般的なパターンが頻繁に使用されます。
- 関数内では、タイプとして
IEpisode
を持つprops
からepisodes
を分解します。
一般的なアイデンティティについて読んでください。この知識は、進むにつれて必要になります。
-
episodes
の小道具を返し、それを介してマッピングして、いくつかのHTMLタグを返しました。 - 最初のセクションには、
episode.id
であるkey
と、後で作成されるepisode-box
のclassName
が保持されます。 私たちのエピソードには画像が含まれていることを私たちは知っています。 したがって、画像タグ。 - 画像には、
episode.image
またはepisode.image.medium
のいずれかがあるかどうかをチェックする三項演算子があります。 それ以外の場合、画像が見つからない場合は空の文字列を表示します。 また、episode.name
をdivに含めました。
section
では、エピソードが属するシーズンとその番号を示します。 Fav
というテキストのボタンがあります。 EpisodesList
コンポーネントをエクスポートして、アプリ全体で使用できるようにしました。
ホームページコンポーネント
ホームページでAPI呼び出しをトリガーし、作成したEpisodesList
コンポーネントを使用してエピソードを表示する必要があります。 components
フォルダ内にHomePage
コンポーネントを作成し、次のコードをコピーして貼り付けます。
import React, { useContext, useEffect, lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces' import { fetchDataAction } from '../Actions' const EpisodesList = lazy<any>(() => import('./EpisodesList')) const HomePage = (): JSX.Element => { const { state, dispatch } = useContext(Store) useEffect(() => { state.episodes.length === 0 && fetchDataAction(dispatch) }) const props: IEpisodeProps = { episodes: state.episodes, store: { state, dispatch } } return ( <App> <Suspense fallback={<div>loading...</div>}> <section className='episode-layout'> <EpisodesList {...props} /> </section> </Suspense> </App> ) } export default HomePage
-
useContext
、useEffect
、lazy
、Suspense
をReactからインポートします。 インポートされたアプリコンポーネントは、他のすべてのコンポーネントがストアの価値を受け取らなければならない基盤です。 - また、
Store
、IEpisodeProps
、およびFetchDataAction
をそれぞれのファイルからインポートします。 - React16.6で利用可能な
React.lazy
機能を使用してEpisodesList
コンポーネントをインポートします。
Reactの遅延読み込みは、コード分割の規則をサポートしています。 したがって、 EpisodesList
コンポーネントは一度に読み込まれるのではなく動的に読み込まれるため、アプリのパフォーマンスが向上します。
-
state
を分解し、Store
から小道具としてdispatch
します。 -
useEffect
フックのアンパサンド(&&)は、エピソードの状態がempty
(または0に等しい)かどうかをチェックします。 それ以外の場合は、fetchDataAction
関数を返します。 - 最後に、
App
コンポーネントを返します。 その中で、Suspense
ラッパーを使用し、loading
テキストを使用してfallback
をdivに設定します。 これは、APIからの応答を待つ間、ユーザーに表示されます。 -
EpisodesList
コンポーネントは、データが利用可能になるとマウントされ、episodes
を含むデータがデータに拡散されます。
Index.txsを設定する
Homepage
コンポーネントは、 StoreProvider
の子である必要があります。 index
ファイルでそれを行う必要があります。 index.js
の名前をindex.tsx
に変更し、次のコードを貼り付けます。
import React from 'react' import ReactDOM from 'react-dom' import './index.css' import { StoreProvider } from './Store' import HomePage from './components/HomePage' ReactDOM.render( <StoreProvider> <HomePage /> </StoreProvider>, document.getElementById('root') )
StoreProvider
、 HomePage
、およびindex.css
をそれぞれのファイルからインポートします。 HomePage
コンポーネントをStoreProvider
でラップします。 これにより、前のセクションで見たように、 Homepage
コンポーネントがストアにアクセスできるようになります。
私たちは長い道のりを歩んできました。 CSSを使用せずに、アプリがどのように表示されるかを確認してみましょう。
Create Index.css
Delete the code in the index.css
file and replace it with this:
html { font-size: 14px; } body { margin: 0; padding: 0; font-size: 10px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .episode-layout { display: flex; flex-wrap: wrap; min-width: 100vh; } .episode-box { padding: .5rem; } .header { display: flex; justify-content: space-between; background: white; border-bottom: 1px solid black; padding: .5rem; position: sticky; top: 0; }
Our app now has a look and feel. Here's how it looks with CSS.
Now we see that our episodes can finally be fetched and displayed, because we've adopted TypeScript all the way. いいですね。
Add Favorite Episodes Feature
Let's add functionality that adds favorite episodes and that links it to a separate page. Let's go back to our Store component and add a few lines of code:
Note that the highlighted code is newly added:
import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext<IState | any>(initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload }
case 'ADD_FAV': return { ...state, favourites: [...state.favourites, action.payload] }
default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return <Store.Provider value={{ state, dispatch }}>{children}</Store.Provider> }
To implement the “Add favorite” feature to our app, the ADD_FAV
case is added. It returns an object that holds a copy of our previous state, as well as an array with a copy of the favorite state
, with the payload
.
We need an action that will be called each time a user clicks on the FAV
button. Let's add the highlighted code to index.tx
:
import {
IAction, IEpisode, Dispatch } from './types/interfaces'
export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON._embedded.episodes }) }
export const toggleFavAction = (dispatch: any, episode: IEpisode | any): IAction => { let dispatchObj = { type: 'ADD_FAV', payload: episode } return dispatch(dispatchObj) }
export const toggleFavAction = (dispatch: any, episode: IEpisode | any): IAction => { let dispatchObj = { type: 'ADD_FAV', payload: episode } return dispatch(dispatchObj) }
We create a toggleFavAction
function that takes dispatch
and episodes
as parameters, and any
and IEpisode|any
as their respective types, with IAction
as our function type. We have an object whose type
is ADD_FAV
and that has episode
as its payload. Lastly, we just return and dispatch the object.
EpisodeList.tsx
にさらにスニペットを追加します。 ハイライトされたコードをコピーして貼り付けます。
import React from 'react' import { IEpisode, IProps } from '../types/interfaces' const EpisodesList = (props: IProps): Array<JSX.Element> => {
const { episodes, toggleFavAction, favourites, store } = props const { state, dispatch } = store
return episodes.map((episode: IEpisode) => { return ( <section key={episode.id} className='episode-box'> <img src={!!episode.image ? episode.image.medium : ''} alt={`Money Heist - ${episode.name}`} /> <div>{episode.name}</div> <section style={{ display: 'flex', justifyContent: 'space-between' }}> <div> Seasion: {episode.season} Number: {episode.number} </div> <button type='button'
onClick={() => toggleFavAction(state, dispatch, episode)} > {favourites.find((fav: IEpisode) => fav.id === episode.id) ? 'Unfav' : 'Fav'}
</button> </section> </section> ) }) } export default EpisodesList
togglefavaction
、 favorites
、およびstore
を小道具として含め、 state
、ストアからのdispatch
を非構造化します。 お気に入りのエピソードを選択するために、 onClick
イベントにtoggleFavAction
メソッドを含め、関数の引数としてstate
、 dispatch
、 episode
の小道具を渡します。
最後に、 favorite
の状態をループして、 fav.id
(お気に入りのID)がepisode.id
と一致するかどうかを確認します。 含まれている場合は、 Unfav
テキストとFav
テキストを切り替えます。 これは、ユーザーがそのエピソードをお気に入りに追加したかどうかを知るのに役立ちます。
終わりに近づいています。 ただし、ユーザーがホームページのエピソードから選択したときに、お気に入りのエピソードをリンクできるページが必要です。
ここまで進んだら、背中を軽くたたいてください。
Favpageコンポーネント
components
フォルダに、 FavPage.tsx
ファイルを作成します。 次のコードをコピーして貼り付けます。
import React, { lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces' import { toggleFavAction } from '../Actions' const EpisodesList = lazy<any>(() => import('./EpisodesList')) export default function FavPage(): JSX.Element { const { state, dispatch } = React.useContext(Store) const props: IEpisodeProps = { episodes: state.favourites, store: { state, dispatch }, toggleFavAction, favourites: state.favourites } return ( <App> <Suspense fallback={<div>loading...</div>}> <div className='episode-layout'> <EpisodesList {...props} /> </div> </Suspense> </App> ) }
お気に入りのエピソードを選択する背後にあるロジックを作成するために、小さなコードを作成しました。 Reactからlazy
とSuspense
をインポートします。 また、 Store
、 IEpisodeProps
、 toggleFavAction
をそれぞれのファイルからインポートします。
React.lazy
機能を使用してEpisodesList
コンポーネントをインポートします。 最後に、 App
コンポーネントを返します。 その中で、 Suspense
ラッパーを使用し、ロードテキストを使用してフォールバックをdivに設定します。
これは、 Homepage
コンポーネントと同様に機能します。 このコンポーネントはストアにアクセスして、ユーザーがお気に入りのエピソードを取得します。 次に、エピソードのリストがEpisodesList
コンポーネントに渡されます。
HomePage.tsx
ファイルにさらにいくつかのスニペットを追加しましょう。
toggleFavAction
から../Actions
を含めます。 また、小道具としてtoggleFavAction
メソッドを含めます。
import React, { useContext, useEffect, lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces'
import { fetchDataAction, toggleFavAction } from '../Actions'
const EpisodesList = lazy<any>(() => import('./EpisodesList')) const HomePage = (): JSX.Element => { const { state, dispatch } = useContext(Store) useEffect(() => { state.episodes.length === 0 && fetchDataAction(dispatch) }) const props: IEpisodeProps = { episodes: state.episodes, store: { state, dispatch },
toggleFavAction, favourites: state.favourites
} return ( <App> <Suspense fallback={<div>loading...</div>}> <section className='episode-layout'> <EpisodesList {...props} /> </section> </Suspense> </App> ) } export default HomePage
FavPage
をリンクする必要があるため、 App.tsx
のヘッダーにリンクが必要です。 これを実現するために、ReactRouterに似たライブラリであるReachRouterを使用します。 William Leは、ReachRouterとReactRouterの違いについて説明しています。
CLIで、 npm install @reach/router @types/reach__router
を実行します。 ReachRouterライブラリとreach-router
タイプの両方をインストールしています。
インストールが正常に完了したら、 @reach/router
からLink
をインポートします。
import React, { useContext, Fragment } from 'react' import { Store } from './tsx'
import { Link } from '@reach/router'
const App = ({ children }: { children: JSX.Element }): JSX.Element => {
const { state } = useContext(Store)
return ( <Fragment> <header className='header'> <div> <h1>Money Heist</h1> <p>Pick your favourite episode</p> </div>
<div> <Link to='/'>Home</Link> <Link to='/faves'>Favourite(s): {state.favourites.length}</Link> </div>
</header> {children} </Fragment> ) } export default App
useContext
からストアを分解します。 最後に、私たちの家には/
へのLink
とパスがあり、お気に入りには/faves
へのパスがあります。
{state.favourites.length}
は、お気に入りの状態のエピソードの数をチェックして表示します。
最後に、 index.tsx
ファイルで、 FavPage
コンポーネントとHomePage
コンポーネントをそれぞれインポートし、 Router
でラップします。
強調表示されたコードを既存のコードにコピーします。
import React from 'react' import ReactDOM from 'react-dom' import './index.css' import { StoreProvider } from './Store'
import { Router, RouteComponentProps } from '@reach/router' import HomePage from './components/HomePage' import FavPage from './components/FavPage' const RouterPage = ( props: { pageComponent: JSX.Element } & RouteComponentProps ) => props.pageComponent
ReactDOM.render( <StoreProvider>
<Router> <RouterPage pageComponent={<HomePage />} path='/' /> <RouterPage pageComponent={<FavPage />} path='/faves' /> </Router>
</StoreProvider>, document.getElementById('root') )
次に、実装されたADD_FAV
がどのように機能するかを見てみましょう。
お気に入りの機能を削除する
最後に、「エピソードの削除機能」を追加して、ボタンがクリックされたときに、お気に入りのエピソードの追加と削除を切り替えます。 追加または削除されたエピソードの数がヘッダーに表示されます。
お店
「お気に入りのエピソードを削除」機能を作成するために、ストアに別のケースを追加します。 したがって、 Store.tsx
に移動し、強調表示されたコードを追加します。
import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext<IState | any>(initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } case 'ADD_FAV': return { ...state, favourites: [...state.favourites, action.payload] }
case 'REMOVE_FAV': return { ...state, favourites: action.payload }
default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }
default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }
default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }
REMOVE_FAV
という名前のさらに別のケースを追加し、 initialState
のコピーを含むオブジェクトを返します。 また、 favorites
の状態にはアクションペイロードが含まれています。
アクション
次の強調表示されたコードをコピーして、 action.ts
に貼り付けます。
import
{ IAction, IEpisode, IState, Dispatch } from './types/interfaces'
export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON.\_embedded.episodes }) } //Add IState withits type
export const toggleFavAction = (state: IState, dispatch: any, episode: IEpisode | any): IAction => { const episodeInFav = state.favourites.includes(episode)
let dispatchObj = { type: 'ADD_FAV', payload: episode }
if (episodeInFav) { const favWithoutEpisode = state.favourites.filter( (fav: IEpisode) => fav.id !== episode.id ) dispatchObj = { type: 'REMOVE_FAV', payload: favWithoutEpisode }
} return dispatch(dispatchObj) }
IState
インターフェースを./types/interfaces
からインポートします。これは、IStateインターフェースをtypeとしてstate
関数のtoggleFavAction
に渡す必要があるためです。
エピソードがfavorites
の状態で存在するかどうかを確認するために、 episodeInFav
変数が作成されます。
お気に入りの状態をフィルタリングして、お気に入りのIDがエピソードIDと等しくないかどうかを確認します。 したがって、 dispatchObj
には、タイプREMOVE_FAV
とペイロードfavWithoutEpisode
が再割り当てされます。
アプリの結果をプレビューしてみましょう。
結論
この記事では、ReactプロジェクトでTypeScriptをセットアップする方法と、プロジェクトをバニラReactからTypeScriptに移行する方法を見てきました。
また、TypeScriptとReactを使用してアプリを作成し、ReactプロジェクトでTypeScriptがどのように使用されるかを確認しました。 あなたはいくつかのことを学ぶことができたと思います。
以下のコメントセクションで、TypeScriptに関するフィードバックと経験を共有してください。 あなたが思いついたものを見てみたいです!
この記事のサポートリポジトリはGitHubで入手できます。
参考文献
- 「ReactアプリをTypeScriptに移行する方法」JoePrevite
- 「ReactアプリでTypeScriptを使用する理由と方法は?」MaheshHaldar