この記事の目的は、最新のWebアプリケーションを開発し、必要なツールとライブラリを順次追加およびカスタマイズするための環境を読者と一緒に書くことです。 多数のスターターキット/定型リポジトリとの類推によってですが、私たちのものです。
この記事は完全に改訂と修正のために公開されており、おそらく最終的な資料は、専門家と新しい技術を試してみたい人の両方にとって興味深い最新の便利な参考書になります。

この記事では、詳細なTypeScript構文とReactの操作の基本については考慮していません。上記のテクノロジーを使用した経験がない場合は、それらの研究を分離することをお勧めします。
記事の最初の部分へのリンクプロジェクトリポジトリには、各ステップの個別のブランチにコードが含まれています。
パート2の主なテーマは、 Redux状態マネージャーの接続と使用です。
Reduxを使用する理由と、Fluxパターンの他の実装との比較-個々の記事のトピック、この情報は簡単に見つけて調べることができます。
いくつかの利点-大規模なコミュニティとエコシステム、アプリケーションの動作を完全に制御できる可能性、テストの容易さ、関数型プログラミングの学習に向けた特定の手順に注目します。
React-reduxはいくつかのReactコンポーネントを提供する小さなライブラリです
-ProviderはReduxストレージを
コンテキストに転送し、
connectはポイント単位の転送とストレージからラップされたコンポーネントのプロパティへのデータの更新のための高次コンポーネントです。
コードを見てみましょう!
ステップ4-プロジェクトにReduxを追加します(ベースHello World)
結果のコードを表示するには:
git checkout step-4
srcフォルダーで、コンポーネントを削除します-ステップ3の例では、
index.htmlと
index.tsxのみ
が残ります。
依存関係のインストール (reduxにはソースに宣言ファイルが含まれています):
npm install redux react-redux -S npm install @types/react-redux -D
プロジェクト設定を変更します。tsconfig.jsonにmoduleResolution:nodeプロパティを追加して、コンパイラーがpackage.jsonライブラリー(この場合はredux)で定義された宣言を見つけるようにします。
tsconfig.json { "compilerOptions": { "lib": [ "es5", "es6", "es7", "dom" ], "target": "es5", "module": "esnext", "jsx": "react", "moduleResolution": "node" } }
アヒルモジュールの方法論を使用して、将来のリポジトリの単純なアクションとレデューサーを作成しましょう。ソースフォルダーに、アヒルモジュールを保存する
reduxフォルダーを作成します。 内部で、
field.tsファイルを作成します。
field.ts export interface FieldState { value: string; focus: boolean; } const initialState: FieldState = { value: '', focus: false } const SET = 'field/SET'; type SET = typeof SET; const FOCUS = 'field/FOCUS'; type FOCUS = typeof FOCUS; const BLUR = 'field/BLUR'; type BLUR = typeof BLUR; export interface SetAction { type: SET; payload: string; } export interface FocusAction { type: FOCUS; } export interface BlurAction { type: BLUR; } type FieldAction = SetAction | FocusAction | BlurAction; export default function reducer(state: FieldState = initialState, action: FieldAction): FieldState { switch (action.type) { case SET: return { ...state, value: action.payload } case FOCUS: return { ...state, focus: true } case BLUR: return { ...state, focus: false } default: return state; } } export const set = (payload: string): SetAction => ({ type: SET, payload }); export const focus = (): FocusAction => ({ type: FOCUS }); export const blur = (): BlurAction => ({ type: BLUR });
index.tsファイルを
reduxフォルダーに追加します。これをルートレデューサー(rootReducer)としてリポジトリにインポートします。
redux / index.ts import { combineReducers } from 'redux'; import fieldReducer from './field'; export default combineReducers({ field: fieldReducer })
さらに、Redux-
Redux DevToolsで開発ツールを使用します。
ソースフォルダーで、
index.tsファイル内に
ストアフォルダーを作成します。
ストア/index.ts import { createStore } from 'redux'; import rootReducer from '../redux'; import { FieldState } from '../redux/field'; export interface IStore { field: FieldState } const configureStore = (initialState?: IStore) => { return createStore( rootReducer, initialState, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ) }; export default configureStore;
TypeScriptコンパイラは、グローバルウィンドウオブジェクトの__REDUX_DEVTOOLS_EXTENSION__プロパティについて何も認識していないため、宣言を追加します。
さらにこれらの宣言では、__ DEV__や__PRODUCTION__など、Webpackを介して送信するグローバルフラグを追加します。
ルートフォルダーで、
windows.d.tsファイル内に、
typingsフォルダーを作成します。
window.d.ts interface Window { __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any; __REDUX_DEVTOOLS_EXTENSION__: any; }
次に、ストアからデータを受信して更新するコンポーネントを作成します。 簡素化するために、コンポーネントとコンテナーに分離することはありません。 ソースフォルダーで、
Field.tsxファイル内に
componentsフォルダーを作成します。
Field.tsx import * as React from 'react'; import { connect, Dispatch, DispatchProp } from 'react-redux'; import { IStore } from '../store'; import { set, focus, blur } from '../redux/field'; interface FieldProps extends DispatchProp<IStore>, React.HTMLProps<HTMLInputElement> { value?: string; } class Field extends React.Component<FieldProps, {}> { handleChange = (event: React.FormEvent<HTMLInputElement>) => { const { dispatch } = this.props; const value = event.currentTarget.value; dispatch(set(value)); } handleFocus = () => { const { dispatch } = this.props; dispatch(focus()); } handleBlur = () => { const { dispatch } = this.props; dispatch(blur()); } render() { const { value, dispatch, ...inputProps } = this.props; return ( <input {...inputProps} type="text" value={value} onChange={this.handleChange} onFocus={this.handleFocus} onBlur={this.handleBlur} /> ); } } /** * mapStateToProps, ( ) * */ const mapStateToProps = (state: IStore, ownProps: FieldProps) => ({ value: state.field.value }); export default connect<{}, {}, FieldProps>(mapStateToProps)(Field);
そして最後に、エントリポイント
-src / index.tsxでアプリケーションのすべてを収集します。
src / index.tsx import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import configureStore from './store'; import Field from './components/Field'; const store = configureStore(); const App = () => ( <Provider store={store}> <div> <h1>Hello, Redux!</h1> <Field placeholder='I like dev tools!' /> </div> </Provider> ); ReactDOM.render(<App />, document.getElementById('root'));
ステップ5-いくつかのRedux Typescriptレシピ
結果のコードを表示するには:
git checkout step-5
最初のレシピはミドルウェアです。ソースフォルダーで、
logger.tsファイル内に
ミドルウェアフォルダーを作成します(コードは
公式ドキュメントから取得され
ます )。
ミドルウェア/ logger.ts import { Middleware, MiddlewareAPI, Dispatch, Action } from 'redux'; import { IStore } from '../store'; const logger: Middleware = <S>(store: MiddlewareAPI<S & IStore>) => (next: Dispatch<S>) => // - <A extends Action>(action: A), . (action: any) => { // store.getState().field.value; console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; } export default logger;
コードを更新してリポジトリを作成します。
ストア/index.ts import { createStore, compose, applyMiddleware } from 'redux'; import rootReducer from '../redux'; import { FieldState } from '../redux/field'; import logger from '../middlewares/logger'; export interface IStore { field: FieldState } let composeEnhancers = compose;
2番目のレシピは、高次のリデューサーです。reduxフォルダーで、ファイル
createNamedReducer.tsを作成します(コードは
公式ドキュメントから取得され
ます )。
createNamedReducer.ts import { Reducer, Action } from 'redux'; import { IStore } from '../store'; export interface namedAction extends Action { name: string; } function createNamedReducer<S>(reducer: Reducer<S>, reducerName: string): Reducer<S> { return (state: S, action: namedAction) => { const { name } = action; const isInitializationCall = state === undefined; if (name !== reducerName && !isInitializationCall) { return state; } return reducer(state, action); } } export default createNamedReducer;
ステップ6-APIを使用する
結果のコードを表示するには:
git checkout step-6
注意! APIを操作するためのメソッドを使用してサービスを分離し、サンクアクション内でこれらのメソッドを呼び出して、データをリポジトリにバインドすることを好みます。
しかし、redux-axios-middlewareやredux-apiなどのライブラリがあります。これらのライブラリは、ボイラープレートコードを減らし、http要求の作成に対するラッパーを作成するように設計されています。
したがって、ReduxとREST APIをリンクする際のヒントとコメントでこの記事を補足し、将来最も人気のあるテクニックを詳細に説明したいと思います。
APIモックの場合、
jsonplaceholderサービスを使用します。
依存関係のインストール (両方のライブラリに宣言が含まれています):
npm install axios redux-thunk -S
client.tsファイルと
users.tsファイル内のプロジェクトソースに
servicesフォルダーを作成し
ます 。
client.ts import axios from 'axios'; const client = axios.create({ baseURL: 'https://jsonplaceholder.typicode.com' }); export default client;
users.ts import { AxiosPromise } from 'axios'; import client from './client';
次に、新しいアヒルモジュール
users.tsを作成します。この段階で多くの質問が発生し、それらを解決するための多くのオプションがあります。
redux / users.ts import { Dispatch } from 'redux'; import { IStore } from '../store'; import * as client from '../services/users';
rootReducerとストレージインターフェイスを更新し、サンクミドルウェアを追加します。
redux / index.ts import { combineReducers } from 'redux'; import fieldReducer from './field'; import usersReducer from './users'; export default combineReducers({ field: fieldReducer, users: usersReducer });
ストア/index.ts import { createStore, compose, applyMiddleware } from 'redux'; import ReduxThunk from 'redux-thunk' import rootReducer from '../redux'; import { FieldState } from '../redux/field'; import { UsersState } from '../redux/users'; import logger from '../middlewares/logger'; export interface IStore { field: FieldState, users: UsersState } let composeEnhancers = compose; const middlewares = [ logger, ReduxThunk ]; if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) { composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__; } const configureStore = (initialState?: IStore) => { return createStore( rootReducer, initialState, composeEnhancers( applyMiddleware(...middlewares) ) ) }; export default configureStore;
次に、ユーザーのリスト、エラーメッセージ、または条件付きプリローダーを表示するコンポーネントを作成します。
Users.tsx import * as React from 'react'; import { connect, Dispatch, DispatchProp } from 'react-redux'; import { IStore } from '../store'; import { getList, Error } from '../redux/users'; import { IUser } from '../services/users'; interface UsersProps extends DispatchProp<IStore> { isFetching?: boolean; error?: Error; users?: IUser[]; } class Users extends React.Component<UsersProps, {}> { componentDidMount() { const { dispatch } = this.props; dispatch(getList()); } render() { const { isFetching, error, users } = this.props; if (error) { return <b> !</b> } if (isFetching) { return '...'; } return users.map((user) => <div>{user.name}</div>); } } const mapStateToProps = (state: IStore, ownProps: UsersProps) => ({ isFetching: state.users.getList.isFetching, error: state.users.getList.error, users: state.users.getList.data }); export default connect<{}, {}, UsersProps>(mapStateToProps)(Users);
次に、アプリケーションのルートコンポーネントで
<Users />コンポーネントを呼び出します。
明確な答えのない質問:リクエストオブジェクトをリポジトリに保存する必要がありますか?また、どのような利点がありますか? おそらくこれにより、リクエストのキャンセルが簡単になります。
URLのidがdynamicである1つのGETリクエストで、1つの画面で多くのコンポーネントを使用する場合の対処方法
異なるパラメータが同じリクエストに送られる場合の非同期オートコンプリートに関する同様の問題。 応答をキャッシュできますが、そのような場合、各リクエストのステータスを個別に監視する必要があります。これには、個別のリデューサーが必要です。
1つの特定の要求に対してレデューサーを動的に追加するコンポーネント、またはローカルでのみ使用される非同期データの一部をReduxに保存する必要がないコンポーネントを使用することは意味がありますか?
React + ReduxアプリケーションでAPIを使用する方法についての記事に詳細な解説を書き、次のステップに進みます。
第7ステップ-生産および開発アセンブリ
結果のコードを表示するには:
git checkout step-7
1)
クロスブラウザーの互換性依存関係のインストール:
npm install core-js -S npm install @types/core-js -D
Core-jsは、現代のJSコンストラクトのポリフィルライブラリです。
core-js / shimモジュールのインポートは、
babel-polyfillプラグインの使用
とほぼ同じです。
いくつかの
必要なポリフィルのみを使用し、それらをアプリケーションのエントリポイントの先頭に追加します。
src / index.ts import 'core-js/es6/promise'; import 'core-js/es6/map'; import 'core-js/es6/set'; if (typeof window.requestAnimationFrame !== 'function') { window.requestAnimationFrame = (callback: FrameRequestCallback) => window.setTimeout(callback, 0); } ...
tsconfig.jsonファイルでは、「target」プロパティはすでに「es5」として指定されているため、ほとんどのポリフィルは必要ありません。 現在のビルドはIE9 +をサポートしています。
1)
生産組立この段階で、アセンブリパラメーターを追加し、webpack設定自体を変更し、値
process.env.NODE_ENVをグローバルパラメーターとして送信する必要があります。一部のライブラリ(Reactなど)は、このパラメーターに応じてprodまたはdevソースを使用します。
依存関係のインストール:
npm install better-npm-run -D
better-npm-run -npmスクリプトをダウンロードします。
package.jsonの npmスクリプトを編集してみましょう。環境変数は「betterScripts」ブロックで非常に便利に定義されています。
package.json { ... "scripts": { "start": "better-npm-run dev", "build": "better-npm-run build" }, "betterScripts": { "dev": { "command": "webpack-dev-server", "env": { "NODE_ENV": "development" } }, "build": { "command": "webpack", "env": { "NODE_ENV": "production" } } }, ... }
webpackの設定が複雑な場合、
webpack-mergeプラグインが助けになります-現時点では、コードを複雑にしないために、それを使用しません。
webpack.config.jsの変更:
webpack.config.js const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin');
実動アセンブリには次のコマンドを使用します。
npm run build
アセンブリが完了すると、サイズが約180kb、gzipで圧縮された約55kbの合計バンドルが得られます。 さらに、node_modulesのライブラリは、CommonsChunkPluginを使用して別のバンドルに移動できます。
次の記事のトピック:ルーティング、プログレッシブWebアプリケーション(PWA)の作成、サーバーレンダリング、Jestでのテスト。
ご清聴ありがとうございました!