reduxに接続されたReactコンポーネントを使用するときに、型の安全性を維持するにはどうすればよいですか?

ipmcc

私はReact / Redux / Redux-ThunkプロジェクトでTypeScriptをいじっていますがconnect、コンポーネントを作成した、接続プロセスがそうではないため、キャストせずにそれを賢明に使用することができないように見えるこの障害にぶつかり続けています接続操作によってプロパティ要件の一部またはすべてが満たされていることを型システムに伝えることができないようです。たとえば、次のコンポーネント/タイプなどについて考えてみます。

import * as React from 'react';
import {connect} from "react-redux";
import {Action, bindActionCreators, Dispatch} from "redux";
import {ThunkDispatch} from "redux-thunk";

// Our store model
interface Model {
    name: string,
}

// Types for our component's props
interface FooDataProps {
    name: string // Single, required, string property
}

interface FooDispatchProps {
    onClick: React.MouseEventHandler<HTMLButtonElement>, // Single, required, event handler.
}

interface FooProps extends FooDataProps, FooDispatchProps { // Union the two types
}

// Make our first component...
function TrivialComponent(props: FooProps) {
    return (<button onClick={props.onClick}>{props.name}</button>);
}

// Now make a Redux "container" that wires it to the store...
const mapStateToProps = (state: Model): FooDataProps => { return { name: state.name }; };
const mapDispatchToProps = (dispatch: Dispatch): FooDispatchProps => {
    return bindActionCreators({onClick: doStuff}, dispatch);
};

// Wire it up with all the glory of the heavily-genericized `connect`
const ConnectedTrivialComponent = connect<FooDataProps, FooDispatchProps, FooProps, Model>(mapStateToProps, mapDispatchToProps)(TrivialComponent);

// Then let's try to consume it
function ConsumingComponent1() {
    // At this point, I shouldn't need to provide any props to the ConnectedTrivialComponent -- they're 
    // all being provided by the `connect` hookup, but if I try to use the tag like I'm doing here, I 
    // get this error: 
    //
    // Error:(53, 10) TS2322: Type '{}' is not assignable to type 'Readonly<Pick<FooProps, never> & FooProps>'.
    // Property 'name' is missing in type '{}'.
    //
    return (<ConnectedTrivialComponent/>)
}

// If I do something like this:
const ConnectedTrivialComponent2 = ConnectedTrivialComponent as any as React.ComponentClass<{}, {}>;

// Then let's try to consume it
function ConsumingComponent2() {
    // I can do this no problem.
    return (<ConnectedTrivialComponent2/>)
}

// Handler...
const doStuff = (e: React.MouseEvent<HTMLButtonElement>) => (dispatch: ThunkDispatch<Model, void, Action>, getStore: () => Model) => {
    // Do stuff
};

さて、この問題について考える際に、私はいくつかのアイデアを経験しました。

アイデア#1)すべての小道具をオプションにします。私がサードパーティから見た多くのコンポーネントにはすべてがオプションですが、私の経験では、すべてがオプションであると、いたるところに多くの定型的なゼロチェックが発生し、コードが読みにくくなります。

アイデア#2)操作で入力されていないプロパティにキャストしReact.ComponentClass<P,S>て追加のタイプを作成しますキャストは明らかに機能しますが、同期を保つための3つのセット(元の小道具タイプ、andリスト、および「残りの小道具」タイプ)があります。このアプローチは、冗長で、エラーが発生しやすく、また、他の潜在的に有用なタイプ情報も消去します。connectmapStateToPropsmapDispatchToProps

connectタイプの観点からedコンポーネントを管理するためのより良い方法はありますか?

ipmcc

もう少し掘り下げた後、私はこれを理解したと思います。質問に示されている形式(およびconnect、型定義ファイル呼び出される可能性のある呼び出し/型パターンが12個あることに注意してください-これは1つだけです)、に4つの型パラメーターがありconnectます。それらは以下を表すように見えます:

  1. TStateProps-mapStateToPropsパラメータを入力するプロパティを含むタイプconnect(ちなみにmapStateToProps関数の戻り値の型でもあります)
  2. TDispatchProps-mapDispatchToPropsパラメータを入力するプロパティを含むタイプconnect(ちなみにmapDispatchToProps関数の戻り値の型でもあります)
  3. TOwnProps-呼び出しによって生成され新しいコンポーネントに設定される残りのプロパティを含むタイプconnect(これは私の混乱があった場所です)。TOwnPropsはおよびへパラメーターのタイプであります。ownPropsmapStateToPropsmapDispatchToProps
  4. 状態-Reduxストアモデルルートのタイプ。

No. 3が呼び出されるのTOwnPropsownPropsmapStateToPropsandmapDispatchToProps関数へのパラメーターとの関連付けを意味するため、少し混乱ます。ownProps実際に使う機会がなかったので、実際にはそれ以上のものがあることをすぐには理解しませんでした。さらに掘り下げてみると、次のようになりconnectます。

InferableComponentEnhancerWithProps<TStateProps & TDispatchProps, TOwnProps>

その定義は次のとおりです。

export interface InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> {
    <C extends ComponentType<Matching<TInjectedProps, GetProps<C>>>>(
        component: C
    ): ConnectedComponentClass<C, Omit<GetProps<C>, keyof Shared<TInjectedProps, GetProps<C>>> & TNeedsProps>
}

TStateProps & TDispatchPropsと呼ばれ、TInjectedPropsTOwnProps呼ばれるのを見ると、TNeedsProps物事をもう少し焦点を合わせるのに役立ちました。TStateProps & TDispatchPropsプロパティはでラップ成分に「注入」されconnect、およびTOwnProps特性は、接続されたコンポーネントの消費者からのラッパーが依然として「必要」ということです。

私が持っていた他の認識はconnect<FooDataProps, FooDispatchProps, FooProps, Model>(mapStateToProps, mapDispatchToProps)、3番目の型パラメーターが(意味的に)「すべての小道具、状態、またはディスパッチ」を表すことになっている場合、型システムは簡単に達成できたので、質問での方法(つまり)は意味がないということでしたそれによる&INGのFooDataPropsFooDispatchProps、それはのparam#1とのparam#2となっていること。私が使用していたので、その3番目のタイプのパラメーターを持つことによって伝えられる新しい情報はありません。

Matt McCutchenの答えは、役に立ちますが、関数のパラメーターTOwnPropsに関するの役割にのみ焦点を当てていownPropsます彼は、私がこれらの関数でパラメーターを使用しないことを正しく観察し、を渡すことを提案しましたその答えは、それが説明するコンテキストには正しいように見えますが、高階コンポーネントが受け入れることができる小道具の決定要因としての他の役割を無視しています。mapStateToPropsmapDispatchToPropsownProps{}TOwnPropsconnect

ここでの要約のポイントは、マップ関数へのパラメーターのTOwnPropsタイプとしてだけでなく、ラップされた/接続されたコンポーネントのコンシューマーによって設定されたままのプロパティをキャプチャするタイプとしてownPropsここで二重の義務を果たしているということです

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

Related 関連記事

ホットタグ

アーカイブ