実用的なTypeScript。 React + Redux

厳密なタイピングをせずに一般的にどのように生活しているかわかりません。何してるの一日中借方ですか?



現在、最新のフロントエンドアプリケーションの開発は、チームが作業しているhello world



レベル(構成が定期的に変更される)よりも複雑であり、コードベースの品質に高い要求があります。 コードの品質レベルを適切なレベルに維持するために、 #gostgroupフロントエンドチームは最新の状態を保ち 、さまざまな規模企業のプロジェクトで実用的な利点を示す最新のテクノロジーを使用することを恐れません。







TypeScriptの例に対する静的型付けとその利点については、さまざまな記事で詳しく説明されています。したがって、今日は、チームのお気に入りのスタック(React + Redux)の例として、フロントエンド開発者が直面するより多くの適用タスクに焦点を当てます。







「厳格なタイピングなしであなたが一般的にどのように生きているか理解できません。何をしているのですか。何日も借方ですか?」 -著者は私に知られていない。



「いいえ、私たちは一日中型を書きます」-私の同僚。



TypeScriptでコードを作成するとき(以降、本文では件名スタックが暗示されます)、多くの人が型を手動で記述するのに多くの時間を費やさなければならないと文句を言います。 問題を説明する良い例は、 react-redux



からのコネクター接続機能です。 以下のコードを見てみましょう。







 type Props = { a: number, b: string; action1: (a: number) => void; action2: (b: string) => void; } class Component extends React.PureComponent<Props> { } connect( (state: RootStore) => ({ a: state.a, b: state.b, }), { action1, action2, }, )(Component);
      
      





ここで問題は何ですか? 問題は、コネクタを介して注入される新しいプロパティごとに、コンポーネントプロパティの一般的なタイプ(React)でこのプロパティのタイプを記述する必要があることです。 あまりおもしろい職業ではありませんが、コネクタのプロパティのタイプを1つのタイプに収集し、コンポーネントのプロパティの一般的なタイプに一度「接続」できるようにしたいということです。 あなたに朗報があります。 TypeScriptを使用すると、今日これを行うことができます! 準備はいい? 行こう!







TypeScriptの力



TypeScriptは止まっておらず、絶えず進化しています(私は大好きです)。 バージョン2.8から、非常に興味深い関数(条件型)が登場し、条件式に基づいた型のマッピングが可能になりました。 ここでは詳細を説明しませんが、単にドキュメントへのリンクを残し、 それからコードの一部を図として挿入します。







 type TypeName<T> = T extends string ? "string" : T extends number ? "number" : T extends boolean ? "boolean" : T extends undefined ? "undefined" : T extends Function ? "function" : "object"; type T0 = TypeName<string>; // "string" type T1 = TypeName<"a">; // "string" type T2 = TypeName<true>; // "boolean" type T3 = TypeName<() => void>; // "function" type T4 = TypeName<string[]>; // "object"
      
      





この場合、この機能がどのように役立つか。 react-redux



ライブラリタイプ説明を見ると、 InferableComponentEnhancerWithProps



タイプを見つけることができますInferableComponentEnhancerWithProps



タイプは、注入されたプロパティのタイプが、コンポーネントをインスタンス化するときに明示的に設定する必要がある外部タイプのコンポーネントプロパティに入らないようにする役割を果たします。 InferableComponentEnhancerWithProps



型には、 TInjectedProps



TNeedsProps



2つの一般的なパラメーターがあります。 最初に興味があります。 実際のコネクタからこのタイプを引き出しましょう!







 type TypeOfConnect<T> = T extends InferableComponentEnhancerWithProps<infer Props, infer _> ? Props : never ;
      
      





そして、 リポジトリから実際の例のタイプを直接プルしますリポジトリからテストプログラムを複製して実行できます)。







 import React from 'react'; import { connect } from 'react-redux'; import { RootStore, init, TypeOfConnect, thunkAction, unboxThunk } from 'src/redux'; const storeEnhancer = connect( (state: RootStore) => ({ ...state, }), { init, thunkAction: unboxThunk(thunkAction), } ); type AppProps = {} & TypeOfConnect<typeof storeEnhancer> ; class App extends React.PureComponent<AppProps> { componentDidMount() { this.props.init(); this.props.thunkAction(3000); } render() { return ( <> <div>{this.props.a}</div> <div>{this.props.b}</div> <div>{String(this.props.c)}</div> </> ); } } export default storeEnhancer(App);
      
      





上記の例では、リポジトリ(Redux)への接続を2段階に分けています。 最初の段階では、 TypeOfConnect



ヘルパーTypeOfConnect



を使用してTypeOfConnect



からInferableComponentEnhancerWithProps



プロパティタイプを抽出し、取得したプロパティタイプをコンポーネントのネイティブプロパティタイプとさらに組み合わせます( &



の交差を通じて)、高次コンポーネントstoreEnhancer



(別名InferableComponentEnhancerWithProps



)を取得します。 第2段階では、ソースコンポーネントを単純に装飾します。 これで、コネクタに追加するものは何でも、自動的にコンポーネントプロパティタイプに分類されます。 素晴らしい、私たちが達成したかったもの!







注意深い読者は、副作用(サンクアクションクリエーター)を持つアクションジェネレーター(簡潔にするために、アクションの用語を簡略化する)がunboxThunk



関数を使用して追加処理されることにunboxThunk



。 このような追加の対策の原因は何ですか? 正しくしましょう。 最初に、リポジトリのプログラムの例を使用して、このようなアクションのシグネチャを見てみましょう。







 const thunkAction = (delay: number): ThunkAction<void, RootStore, void, AnyAction> => (dispatch) => { console.log('waiting for', delay); setTimeout(() => { console.log('reset'); dispatch(reset()); }, delay); };
      
      





シグネチャからわかるように、アクションはすぐにターゲット関数を返すのではなく、最初に中間関数を返します。これは、 redux-middleware



がピックアップしてメイン関数の副作用を有効にします。 ただし、コンポーネントのプロパティで接続された形式でこの関数を使用すると、この関数のシグネチャは削減され、中間関数は除外されます。 型で記述する方法は? 特別なコンバーター機能が必要です。 繰り返しになりますが、TypeScriptはその力を発揮します。 最初に、中間関数を署名から削除するタイプを説明します。







 CutMiddleFunction<T> = T extends (...arg: infer Args) => (...args: any[]) => infer R ? (...arg: Args) => R : never ;
      
      





ここでは、条件付きの型に加えて、TypeScript 3.0の最新の技術革新を使用して、任意の(残りのパラメーター)数の関数引数の型を推測できます。 詳細については、 ドキュメントを参照してください。 今では、アクションから余分な部分をかなり厳格な方法で切り取ることが残っています。







 const unboxThunk = <Args extends any[], R, S, E, A extends Action<any>>( thunkFn: (...args: Args) => ThunkAction<R, S, E, A>, ) => ( thunkFn as any as CutMiddleFunction<typeof thunkFn> );
      
      





このようなコンバーターにアクションを渡すと、出力で必要な署名を取得できます。 これで、アクションをコネクタで使用する準備ができました。







したがって、単純な操作により、スタックに型指定されたコードを記述する際の手作業を削減します。 もう少し進めば、 redux-modusで行ったように、アクションとレデューサーの入力を簡素化することもできます。







PS functionおよびredux.bindActionCreators



を介してコネクタでアクションの動的バインディングを使用する場合、このユーティリティのより適切な入力に注意する必要があります(独自のラッパーを作成することにより)。







更新0

誰かがこの解決策が便利だと思ったなら、あなたはそれを好きにできます。そうすればtypeユーティリティが@types/react-redux



追加され@types/react-redux









アップデート1

ホックの注入された小道具のタイプを明示的に指定する必要のない、さらにいくつかのタイプ。 ホキを取り出して、それらから型を引き出してください:







 export type BasicHoc<T> = (Component: React.ComponentType<T>) => React.ComponentType<any>; export type ConfiguredHoc<T> = (...args: any[]) => (Component: React.ComponentType<T>) => React.ComponentType<any>; export type BasicHocProps<T> = T extends BasicHoc<infer Props> ? Props : never; export type ConfiguredHocProps<T> = T extends ConfiguredHoc<infer Props> ? Props : never; export type HocProps<T> = T extends BasicHoc<any> ? BasicHocProps<T> : T extends ConfiguredHoc<any> ? ConfiguredHocProps<T> : never ; const basicHoc = (Component: React.ComponentType<{a: number}>) => class extends React.Component {}; const configuredHoc = (opts: any) => (Component: React.ComponentType<{a: number}>) => class extends React.Component {}; type props1 = HocProps<typeof basicHoc>; // {a: number} type props2 = HocProps<typeof configuredHoc>; // {a: number}
      
      





Update2

件名のタイプは、現在@types/react-redux



ConnectedProps )にあります。








All Articles