Webpackを使用した最新のReactプロジェクトのTypeScriptの設定

公開: 2022-03-10
簡単な要約↬この記事では、JavaScriptの上付き文字であるTypescriptを紹介します。これは、開発者コードとして一般的なエラーを見つけるための静的型機能を示し、パフォーマンスを向上させ、堅牢なエンタープライズアプリケーションを実現します。 また、Money Heistエピソードピッカーアプリを構築し、TypeScript、useReducer、useContext、Reach RouterなどのReactフックを調べながら、ReactプロジェクトでTypeScriptを効率的に設定する方法についても学びます。

ソフトウェア開発のこの時代では、JavaScriptを使用してほぼすべてのタイプのアプリを開発できます。 ただし、JavaScriptが動的に型付けされるという事実は、型チェック機能が緩いため、ほとんどの大企業にとって懸念事項となる可能性があります。

幸い、Ecma技術委員会39が静的型システムをJavaScriptに導入するまで待つ必要はありません。 代わりにTypeScriptを使用できます。

動的に型付けされるJavaScriptは、実行時に変数がインスタンス化されるまで、変数のデータ型を認識しません。 大規模なソフトウェアプログラムを作成する開発者は、以前に宣言された変数を別のタイプの値に再割り当てする傾向があり、警告や問題はまったく発生せず、バグが見過ごされがちです。

このチュートリアルでは、TypeScriptとは何か、およびReactプロジェクトでTypeScriptを操作する方法を学習します。 最後に、TypeScriptと現在のReactのようなフック( useStateuseEffectuseReduceruseContext )を使用して、テレビ番組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)で「シンボルの名前変更」コマンドをクリックするだけで、このようなことをリファクタリングできることがよくあります。

アプリの名前をexpAppに変更(大プレビュー)

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をプロジェクトに組み込むために、手間のかかる作業を行う必要はありません。

Create React Appでの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.jsindex.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フォルダーの下にいくつかの異なるファイルとフォルダーを手動で追加しましょう。

  1. webpack.config.jsを追加して、webpack関連の構成を追加します。
  2. すべてのTypeScript構成にtsconfig.jsonを追加します。
  3. 新しいディレクトリsrcを追加します。
  4. srcフォルダに新しいディレクトリcomponentsを作成します。
  5. 最後に、 componentsフォルダーにindex.htmlApp.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という名前を付けましたuserNamelangstring型です。

propsAppコンポーネントに渡し、エクスポートしました。

それでは、 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を実行します。 すべてがうまくいった場合は、次のように表示されます。

React-Webpackセットアップ出力(大プレビュー)

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に移行する

このプロセスをより管理しやすくするために、ステップに分割します。これにより、個々のチャンクに移行できるようになります。 プロジェクトを移行するために実行する手順は次のとおりです。

  1. TypeScriptとタイプを追加します。
  2. tsconfig.jsonを追加します。
  3. 小さく始めます。
  4. ファイル拡張子の名前を.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つのことを行う必要があります。

  1. ファイル拡張子を.tsxに変更します。
  2. タイプ注釈を追加します(これには、TypeScriptの知識が必要です)。

4.ファイル拡張子の名前を.tsxに変更します

大規模なコードベースでは、ファイルの名前を個別に変更するのは面倒に思えるかもしれません。

macOSで複数のファイルの名前を変更します

複数のファイルの名前を変更すると、時間が無駄になる可能性があります。 Macでそれを行う方法は次のとおりです。 名前を変更するファイルが含まれているフォルダを右クリック(またはCtrl +クリックするか、MacBookを使用している場合はトラックパッドを2本の指で同時にクリック)します。 次に、「Finderで表示」をクリックします。 Finderで、名前を変更するすべてのファイルを選択します。 選択したファイルを右クリックし、「Xアイテムの名前を変更…」を選択すると、次のように表示されます。

Macでファイルの名前を変更する(大プレビュー)

検索したい文字列と、見つかった文字列を置き換える文字列を挿入し、「名前の変更」をクリックします。 終わり。

Windowsで複数のファイルの名前を変更する

Windowsで複数のファイルの名前を変更することはこのチュートリアルの範囲を超えていますが、完全なガイドが利用可能です。 通常、ファイルの名前を変更するとエラーが発生します。 タイプアノテーションを追加するだけです。 これについては、ドキュメントでブラッシュアップできます。

ReactアプリでTypeScriptを設定する方法について説明しました。 それでは、TypeScriptを使用してMoneyHeist用のエピソードピッカーアプリを作成しましょう。

TypeScriptの基本的なタイプについては説明しません。 このチュートリアルを続行する前に、ドキュメントを確認する必要があります。

構築する時間

このプロセスの煩わしさを軽減するために、これをいくつかのステップに分割します。これにより、アプリを個々のチャンクで構築できるようになります。 MoneyHeistエピソードピッカーを作成するために実行するすべての手順は次のとおりです。

  • CreateReactアプリを足場にします。
  • エピソードを取得します。
    • interface.tsで、エピソードに適切なタイプとインターフェイスを作成しinterface.ts
    • store.tsxでエピソードをフェッチするためのストアを設定します。
    • action.tsでエピソードをフェッチするためのアクションを作成します。
    • フェッチされたエピソードを保持するEpisodeList.tsxコンポーネントを作成します。
    • React Lazy and Suspenseを使用して、 EpisodesListコンポーネントをホームページにインポートします。
  • エピソードを追加します。
    • 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します。

Reactスターターページ(大きなプレビュー)

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は、 airdateairstampairtimeidimagenamenumberruntimeseasonsummaryurlなどの一連のプロパティを返します。 したがって、 IEpisodeインターフェイスを定義し、適切なデータ型をオブジェクトプロパティに設定しました。

IStateインターフェース

IStateインターフェースには、それぞれepisodesfavoritesプロパティ、およびArray<IEpisode>インターフェースがあります。

IAction

IActionインターフェイスのプロパティは、 payloadtypeです。 typeプロパティには文字列型があり、ペイロードにはArray | anyの型があります。 Array | any

Array | any Array | anyは、エピソードインターフェイスまたは任意のタイプの配列を意味します。

DispatchタイプはReact.Dispatchおよび<IAction>インターフェースに設定されています。 @types/reactコードベースによると、 React.Dispatchdispatch関数の標準タイプであり、 <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からIStateIActionをインポートし./types/interfacesた。
  • タイプがIStateinitialStateオブジェクトを宣言し、エピソードとお気に入りのプロパティをそれぞれ空の配列に設定しました。
  • 次に、 createContextメソッドを保持し、 initialStateが渡されるStore変数を作成しました。

createContextメソッドタイプは<IState | any> <IState | any> 。これは、 <IState>またはanyのタイプである可能性があることを意味します。 この記事で頻繁に使用されるタイプを確認しany

  • 次に、 reducer関数を宣言し、 stateactionをパラメーターとして渡しました。 reducer関数には、 action.typeの値をチェックするswitchステートメントがあります。 値がFETCH_DATAの場合、状態(...state)とアクションペイロードを保持するエピソード状態のコピーを持つオブジェクトを返します。
  • switchステートメントでは、 defaultの状態を返します。

レデューサー関数のstateパラメーターとactionパラメーターには、それぞれIStateタイプとIActionタイプがあることに注意してください。 また、 reducer関数のタイプはIState

  • 最後に、 StoreProvider関数を宣言しました。 これにより、アプリ内のすべてのコンポーネントがストアにアクセスできるようになります。
  • この関数はchildrenを小道具として受け取り、 StorePrivder関数内でuseReducerフックを宣言しました。
  • stateを解体してdispatchます。
  • ストアをすべてのコンポーネントにアクセスできるようにするために、 statedispatchを含むオブジェクト値を渡しました。

エピソードとお気に入りのstateを含む状態は、他のコンポーネントからアクセスできるようになりますが、 dispatchは状態を変更する機能です。

  • StoreStoreProviderをエクスポートして、アプリケーション全体で使用できるようにします。

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小道具をパラメーターとして受け取ります。
  • この関数は非同期であるため、 asyncawaitを使用します。
  • 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
  • IEpisodeIPropsinterfaces.tsxからインポートします。
  • 次に、小道具をEpisodesList関数を作成します。 小道具のタイプはIPropsで、関数のタイプはArray<JSX.Element>です。

Visual Studio Codeは、関数型をJSX.Element[]として記述することを提案しています。

Visual Studio Codeはタイプを提案します(大プレビュー)

Array<JSX.Element>JSX.Element[]と同じですが、 Array<JSX.Element>は総称IDと呼ばれます。 したがって、この記事では一般的なパターンが頻繁に使用されます。

  • 関数内では、タイプとしてIEpisodeを持つpropsからepisodesを分解します。

一般的なアイデンティティについて読んでください。この知識は、進むにつれて必要になります。

  • episodesの小道具を返し、それを介してマッピングして、いくつかのHTMLタグを返しました。
  • 最初のセクションには、 episode.idであるkeyと、後で作成されるepisode-boxclassNameが保持されます。 私たちのエピソードには画像が含まれていることを私たちは知っています。 したがって、画像タグ。
  • 画像には、 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
  • useContextuseEffectlazySuspenseをReactからインポートします。 インポートされたアプリコンポーネントは、他のすべてのコンポーネントがストアの価値を受け取らなければならない基盤です。
  • また、 StoreIEpisodeProps 、および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') )

StoreProviderHomePage 、およびindex.cssをそれぞれのファイルからインポートします。 HomePageコンポーネントをStoreProviderでラップします。 これにより、前のセクションで見たように、 Homepageコンポーネントがストアにアクセスできるようになります。

私たちは長い道のりを歩んできました。 CSSを使用せずに、アプリがどのように表示されるかを確認してみましょう。

App without CSS (Large preview)

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

togglefavactionfavorites 、およびstoreを小道具として含め、 state 、ストアからのdispatchを非構造化します。 お気に入りのエピソードを選択するために、 onClickイベントにtoggleFavActionメソッドを含め、関数の引数としてstatedispatchepisodeの小道具を渡します。

最後に、 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からlazySuspenseをインポートします。 また、 StoreIEpisodePropstoggleFavActionをそれぞれのファイルからインポートします。

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で入手できます。

参考文献

  1. 「ReactアプリをTypeScriptに移行する方法」JoePrevite
  2. 「ReactアプリでTypeScriptを使用する理由と方法は?」MaheshHaldar