TypeScriptおよびReact Webアプリケーションを開発するための環境:「hello world」から最新のSPAまで。 パート1

この記事の目的は、最新のWebアプリケーションを開発し、必要なツールとライブラリを順次追加およびカスタマイズするための環境を読者と一緒に書くことです。 多数のスターターキット/定型リポジトリとの類推によってですが、私たちのものです。



また、著者は自分の考え、知識、実践的な経験を構築するためにこの記事を書き、開発の新しい側面を研究する良いモチベーションを受け取ります。



著者は現在の記事の修正と修正に全力で取り組んでおり、最終的な資料を最新の便利な参考書に変えたいと考えています。



画像



この記事では、詳細なTypeScript構文とReactの操作の基本については考慮していません。読者が上記のテクノロジーを使用した経験がない場合は、研究を分けることをお勧めします。



記事の第2部へのリンク



使用されているテクノロジーについて少し:



TypeScriptでプロジェクトを作成することは、特に最初に言語に慣れるときに多くの困難を伴います。 著者の見解では、ストロングタイピングの利点は努力する価値があります。



言語自体の機能に加えて、TypeScriptコンパイラーは標準のすべてのバージョンのJavaScriptコードを生成し、プロジェクトでBabelの使用を放棄できるようにします(作成者はこの素晴らしいツールに対して何もありませんが、TSとBabelの同時使用は最初に少し混乱をもたらします)。



Reactは、巨大なコミュニティとインフラストラクチャを備えた、Webインターフェイスを作成するための実績のあるライブラリです。



最近、多くの改善と改訂されたドキュメントを含むライブラリの新しいバージョンがリリースされました。



フロントエンド開発者の親友であるWebpackを使用してプロジェクトをビルドします。 このツールの基本設定は非常に簡単に習得して使用できます。 真剣に。



ツールとライブラリの使用済みバージョン
NodeJs v6。*。*

Npm v5 *。*

TypeScript v2。*。*

Webpack v3。*。*

React v16 *。*



さあ始めましょう!

プロジェクトリポジトリには、各ステップの個別のブランチにコードが含まれています。



ステップ1-プロジェクトにTypeScriptを追加します。



結果のコードを表示するには:



git checkout step-1







依存関係のインストール:



npm i webpack typescript awesome-typescript-loader --save-dev





awesome-typescript-loader -webpack用のTypeScript ローダー 。主要なライバルであるts-loaderよりも高速と見なされています。



プロジェクトのソース用に、 src



フォルダーを作成します。

アセンブリ結果をdist



送信します。



TypeScriptコンパイラの基本設定-プロジェクトルートディレクトリのtsconfig.json



ファイル



tsconfig.json
 { "compilerOptions": { "target": "es5", //  ts   js   ES5 "module": "esnext" //      } }
      
      







コレクターの基本設定は、プロジェクトルートディレクトリのwebpack.config.js



ファイルです。



webpack.config.js
 const path = require('path'); const webpack = require('webpack'); const paths = { src: path.resolve(__dirname, 'src'), dist: path.resolve(__dirname, 'dist') }; module.exports = { context: paths.src, //        entry: { app: './index' //    ,  src/index.ts ,       - app }, output: { path: paths.dist, //     filename: '[name].bundle.js' //   ,  dist/app.bundle.js }, resolve: { extensions: ['.ts'] //   ,  webpack  ,     (    index,      index.ts) }, devtool: 'inline-source-map', //      ,      TypeScript  source-map-loader    tsconfig - "sourceMap": true module: { rules: [ { test: /\.ts$/, loader: 'awesome-typescript-loader' } //       .ts ] } };
      
      







src



内でsrc



TypeScript構文を使用するコードでindex.ts



ファイルを作成します。次に例を示します。



index.ts
 interface Props { world: string; } function hello(props: Props) { alert(`Hello, ${props.world}`); } hello({ world: 'TypeScript!' });
      
      







コードをコンパイルしてビルドするコマンド:

webpack



プロジェクトの1回限りのビルド



webpackモジュール内の最後のdist/app.bundle.js



には、選択した標準のバージョンのわかりやすく読みやすいJavaScriptコードが表示されます。



作成した環境は、ライブラリを使用して簡単に拡張でき、プロトタイピング(Favorite Technology + TypeScript)に使用するのに便利です。



続けましょう!



ステップ2-Tiny Reactアプリケーションを作成する



結果のコードを表示するには:



git checkout step-2







依存関係のインストール:



npm i webpack react react-dom --save





npm i webpack @types/react @types/react-dom html-webpack-plugin clean-webpack-plugin --save-dev







html-webpack-plugin-接続されたアセンブリ結果を含むhtmlファイルを生成するためのプラグイン。

clean-webpack-plugin-アセンブリの結果でディレクトリをクリーンアップします。

@ types / reactおよび@ types / react-dom対応するJSライブラリの宣言を含むパッケージであり、エクスポートされたすべてのモジュールのタイプに関するTSコンパイラー情報を提供します。



人気のあるJSライブラリのほとんどには宣言があり、時にはプロジェクトのソースファイルに、時には素晴らしいDefinitelyTypedリポジトリにあり、コミュニティのおかげで積極的に開発されています。既存の宣言にエラーがなければ、これらの問題の修正に簡単に貢献できます。



src



内で、ルート反応コンポーネントをマウントするための要素を含むindex.html



ファイルを作成します。



index.html
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>React and Typescript</title> </head> <body> <div id="root"></div> </body> </html>
      
      







Webpack設定の更新:



webpack.config.js
 const path = require('path'); const webpack = require('webpack'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const paths = { src: path.resolve(__dirname, 'src'), dist: path.resolve(__dirname, 'dist') }; const config = { context: paths.src, entry: { app: './index' }, output: { path: paths.dist, filename: '[name].bundle.js' }, resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'] //   tsx    react  }, devtool: 'inline-source-map', module: { rules: [ { test: /\.tsx?$/, //   tsx    react  loader: 'awesome-typescript-loader' } ] }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ template: './index.html' }) //  html-     ] }; module.exports = config;
      
      







TypeScriptコンパイラー設定を更新します。



tsconfig.json
 { "compilerOptions": { "target": "es5", "module": "esnext", "jsx": "react" //     JSX } }
      
      







コンポーネントに移りましょう。



拡張子をindex.ts



からindex.ts



に変更する必要があります。 コンポーネントのコードを記述し、ページに表示します。



index.tsx
 //  react  TS    import React from 'react' -     TS import * as React from 'react'; import * as ReactDOM from 'react-dom'; //     props  state  interface IAppProps { title: string; } //   const App = (props: IAppProps) => <h1>{props.title}</h1>; ReactDOM.render( <App title="Hello, React!" />, document.getElementById('root') );
      
      







コマンドを追加して、コードをコンパイルおよびビルドします。



webpack-dev-server



アプリケーションでサーバーを上げwebpack-dev-server



http://localhost:8080/



で利用できます。

また、ソースファイルが変更されると、webpackは自動的にプロジェクトを再構築します。



この段階で、アセンブリのサイズに関して疑問が生じる場合があります。作成者は、次のステップで、アセンブリの生産と開発の区分に特別な注意を払います。 最初の段階では、プロセスを完全に理解するために、最低限必要な設定とライブラリに重点が置かれています。



ステップ3-ReactおよびTypeScriptレシピ



結果のコードを表示するには:



git checkout step-3







このステップの依存関係は変わりません。

この段階での一般化(ジェネリック(ジェネリック))を理解することをお勧めします。



この記事から標準のReactパターンの詳細を学ぶことができます。



1) プロパティと状態を持つ標準コンポーネント

制御された入力フィールドを出力するsimple.tsx



コンポーネントを作成します。



simple.tsx
 import * as React from 'react'; /** *     . *    React.HTMLProps     *      . */ interface Props extends React.HTMLProps<HTMLInputElement> { customProperty: string; } //    interface State { value: string; } //   React.Component      class Simple extends React.Component<Props, State> { //    ,     state: State = { value: '' } /* *    onChange   ,    *  onChange  JSX  input. *      - MouseEvent, FocusEvent, KeyboardEvent */ handleChange = (event: React.FormEvent<HTMLInputElement>) => { const value = event.currentTarget.value; this.setState(() => ({ value })); } render() { /* *      HTMLProps  HTMLInputElement,   *    ,     ,  *  ,   . */ const { customProperty, ...inputProps } = this.props; const { value } = this.state; /* * <input {...inputProps} /> -         *  JSX  (  placeholder={inputProps.placeholder} *       ) *   value  onChange    {...inputProps},  *    ,   inputProps */ return ( <div> <h4>{customProperty}</h4> <input {...inputProps} value={value} onChange={this.handleChange} /> </div> ); } } export default Simple;
      
      







2) 高次コンポーネント

公式のReactドキュメントでの高次コンポーネントの説明- ここをクリック

TypeScriptでの高次コンポーネントの記述の詳細な記事(この記事の例は、著者によって部分的に借用されています )- ここをクリック



要するに、高次コンポーネント(以下、ホックと呼びます)は、コンポーネントを引数として(およびオプションで)オプションとして、古いコンポーネントをrender



メソッドで表示する新しいコンポーネントを返し、そのプロパティと状態を渡す関数です。



署名は次のようになります。 (Component) => WrapComponent => Component







TypeScriptはコンポーネントに渡すプロパティを厳密に監視するため、これらのプロパティのインターフェイスを決定する必要があります。

OriginProps-コンポーネントの固有のプロパティ、hocはそれらについて何も知らず、コンポーネントに渡すだけです。

ExternalProps-一意の一時的なプロパティ。

InjectedProps -hocからコンポーネントに渡すプロパティは、ExternalPropsとStateに基づいて計算されます。

状態 -一時的な状態のインターフェイス。 hoc状態全体をコンポーネントに渡すため、StateはInjectedPropsと異なるプロパティを持つことはできません(または、拡張演算子を使用せずに使用可能なプロパティを渡す必要があります)。



コードに移り、簡単なボタンクリックカウンターを記述しましょう。

hoc



フォルダーを作成します。その中にコンポーネントdisplayCount.tsx



およびhoc withCount.tsx



作成します



コンポーネントコードdisplayCount.tsx
 import * as React from 'react'; import { InjectedProps } from './withCount'; //    interface OriginProps { title: string; } /* *     ,      *   InjectedProps,   withCount */ const DisplayCount = (props: OriginProps & InjectedProps) => ( <div> <h4>{props.title}</h4> <div>Count: {props.count}</div> </div> ); export default DisplayCount;
      
      







コンポーネントコードwithCount.tsx
 import * as React from 'react'; // ,  hoc   export interface InjectedProps { count: number; } // ,    hoc interface ExternalProps { increment: number; } //  hoc,     InjectedProps,          interface State { count: number; } /** *  ,    ,  , *      OriginProps -   . *  React.ComponentType -  ComponentClass  StatelessComponent, *   ,   ,   . *  ,         , *     hoc - OriginProps & InjectedProps */ function withCount<OriginProps>(Component: React.ComponentType<OriginProps & InjectedProps>) { //     type ResultProps = OriginProps & ExternalProps; return class extends React.Component<ResultProps, State> { /** *      name  displayName, *    ,     React DevTools */ static displayName = `WithCount(${Component.displayName || Component.name})`; state: State = { count: 0 } increment = () => { const { increment } = this.props; this.setState((prevState: State) => ({ count: prevState.count + increment })); } render() { // {...this.props}  {...this.state} -      . return ( <div> <Component {...this.props} {...this.state} /> <button type="button" onClick={this.increment} > + </button> </div> ) } } } export default withCount;
      
      







次に、高次コンポーネントの使用について説明します。



 const Counter = withCount(DisplayCount); /* * title -     DisplayCount * increment -       */ const App = () => <Counter title="High Order Component" increment={1} /> ;
      
      





概要ツリー:







WithCount(DisplayCount)のプロパティと状態:







DisplayCountのプロパティとステータス:



ここで、追加のincrement



プロパティが表示されます。必要に応じて、lodashのexclude メソッドなどを使用して削除できます。



3) 遅延コンポーネントのロード:

コンポーネントをオンデマンドでロードするには、モジュールの動的インポートの構文を使用します。

TypeScriptでは、この構文はバージョン2.4で登場しました。

動的インポートに遭遇したWebpackは、インポート条件に該当するモジュール用に個別のバンドル作成します。

インポートする最も簡単な式は次のとおりです。

 import('module.ts').then((module) => { // ,      ,    default const defaultExport = module.default; //  ,  export function foo() {} -    , //  module.foo const otherExport = module.OtherExport; });
      
      





次に、インポートを返す関数を受け取り、結果のコンポーネントを表示するコンポーネントを作成します。

lazyComponent.tsx



およびlazyLoad.tsx



コンポーネントのlazy



フォルダーを作成します



LazyComponentはシンプルな機能コンポーネントです。実際のアプリケーションでは、個別のページまたはスタンドアロンのウィジェットにすることができます。



lazyComponent.tsx
 import * as React from 'react'; const LazyComponent = () => <h3>I'm so lazy!</h3>; export default LazyComponent;
      
      







LazyLoadは、動的コンポーネントのロードと出力のための汎用コンポーネントです。

プロパティを動的コンポーネントにスローする必要がある場合、LazyLoadを高次コンポーネントに書き換えることができます。



lazyLoad.tsx
 import * as React from 'react'; /* *  load   : * () => import('path/to/module') *         import(),   *        default. *           - * [key: string]: React.ComponentType */ interface LazyLoadProps { load: () => Promise<{ default: React.ComponentType }>; } //       interface LazyLoadState { Component: React.ComponentType; } class LazyLoad extends React.Component<LazyLoadProps, LazyLoadState> { // null     ,     state: LazyLoadState = { Component: null } //  async await   ,     async componentDidMount() { const { load } = this.props; try { //    -  const module = await load(); //      default const Component = module.default; //  state    this.setState({ Component }); } catch (e) { //     } } render() { const { Component } = this.state; //  ,       . //   ,     LazyLoad return ( <div> <h4>Lazy load component</h4> {Component ? <Component /> : '...'} </div> ); } } export default LazyLoad;
      
      







それでも、バンドルに名前を付けることができるように、webpackの設定を更新します。



webpack.config.js
 const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const paths = { src: path.resolve(__dirname, 'src'), dist: path.resolve(__dirname, 'dist') }; const config = { context: paths.src, entry: { app: './index' }, output: { path: paths.dist, filename: '[name].bundle.js', chunkFilename: '[name].bundle.js' //     chunk' }, resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'] }, devtool: 'inline-source-map', module: { rules: [ { test: /\.tsx?$/, loader: 'awesome-typescript-loader' } ] }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ template: './index.html' }) ] }; module.exports = config;
      
      







そして、tsconfig.json設定を更新します-コンパイル時にTypeScriptが使用するライブラリを手動で指定します。 具体的には「es2015.promise」が必要ですが、便宜上、ES標準の完全なリスト、そしてもちろんDOMを追加します。



tsconfig.json
 { "compilerOptions": { "lib": [ "es5", "es6", "es7", "dom" ], "target": "es5", "module": "esnext", "jsx": "react" } }
      
      







コンポーネントの使用:

 // webpackChunkName -        // chunkFilename: '[name].bundle.js'   lazy-component.bundle.js const load = () => import(/* webpackChunkName: 'lazy-component' */'./lazy/lazyComponent'); const App = ({title}: IAppProps) => <LazyLoad load={load} />;
      
      







4) 小道具のレンダリング

公式のReactドキュメントでのrenderプロパティを持つコンポーネントの説明- ここ



このようなコンポーネントを使いやすくするために、通常、いくつかのレンダリング方法が提供されています。

2つの主なものを検討してください: renderプロパティとchildrenプロパティ。



renderProps



フォルダーを作成します。その中にdisplaySize.tsx



コンポーネントとwindowQueries.tsx



コンポーネント



コンポーネントコードdisplaySize.tsx
 import * as React from 'react'; import { IRenderProps } from './windowQueries'; //    IRenderProps,    ,    //        . interface IProps extends IRenderProps { title: string; } const DisplaySize = ({ title, width, height }: IProps) => ( <div> <h4>{title}</h4> <p>Width: {width}px</p> <p>Height: {height}px</p> </div> ); export default DisplaySize;
      
      







windowQueries.tsxコンポーネントコード
 import * as React from 'react'; // React.ReactNode -       . interface IProps { children?: ((props: IRenderProps) => React.ReactNode); render?: ((props: IRenderProps) => React.ReactNode); } interface IState { width: number; height: number; } export interface IRenderProps { width?: number; height?: number; } /** *        . */ class WindowQueries extends React.Component<IProps, IState> { state: IState = { width: window.innerWidth, height: window.innerHeight, } componentDidMount() { window.addEventListener('resize', this.handleWindowResize); } componentWillUnmount() { window.removeEventListener('resize', this.handleWindowResize); } handleWindowResize = () => { this.setState({ width: window.innerWidth, height: window.innerHeight, }) } gerRenderProps = (): IRenderProps => { const { width, height } = this.state; return { width, height }; } //       ,  render  children //  ,   . //          . render() { const { children, render } = this.props; if (render) { return render(this.gerRenderProps()) } if (children) { return children(this.gerRenderProps()) } return null; } } export default WindowQueries;
      
      







次に、コンポーネントの使用について説明します。



 <WindowQueries> {({ width, height }) => <DisplaySize title="render children" width={width} height={height} />} </WindowQueries> <WindowQueries render={ ({ width, height }) => <DisplaySize title="render property" width={width} height={height} /> } />
      
      







5) ニュアンス:



子にアクセスするためのchildrenプロパティの説明(オプション):

 interface Props { children: React.ReactNode; }
      
      





JSX要素を使用したプロパティの説明は、マークアップコンポーネントに使用できます。

 interface Props { header: JSX.Element, body: JSX.Element } <Component header={<h1></h1>} body={<div></div>} />
      
      





おわりに

最小限必要な設定でReactとTypeScriptの開発環境を作成し、いくつかの簡単なコンポーネントを作成しました。



TypeScriptを使用すると、 PropTypesの使用を放棄し、開発およびコンパイル中にコンポーネントのプロパティを確認できます(PropTypesは、アプリケーションの実行中にのみエラーを返します)。



自動補完としての強い型付けのこのような利点はJSXにまで及び、Reactライブラリ宣言ファイルでは、組み込みJSX要素のすべての可能なプロパティをすばやく確認できます。



複雑なプロジェクトでは、TypeScriptを使用することで完全に正当化されます。これは、Reduxを使用する(ストアのインターフェイスのおかげです)、外部APIを操作するなどの瞬間に見られます。



記事2では、次のことを考慮しています。



1)Reduxを接続する

2)標準React、Redux、およびTypeScriptレシピ

3)APIを使用する

4)生産開発プロジェクトの組み立て



その後の記事では、著者は次のことを説明する予定です。プログレッシブWebアプリケーション(PWA)の作成、サーバーレンダリング、Jestでのテスト、そして最終的にアプリケーションの最適化



著者は、記事のデザインの失敗について謝罪し、この記事の認識と読みやすさを改善するための提案を再度提出するよう求めます。



ご清聴ありがとうございました!



更新10.22.2017:遅延ロードコンポーネントのレシピを追加



アップデート02.17.2018:レンダリングプロパティを含むコンポーネントレシピを追加し、依存関係を更新(ReactNodeタイプのエラーを修正)



All Articles