web-dev-qa-db-ja.com

TypeScriptはReduxコンテナーと格闘する

Reduxコンテナ を適切に入力する方法を理解するのに問題があります。

次のような単純なプレゼンテーションコンポーネントについて考えてみます。

interface MyProps {
  name: string;
  selected: boolean;
  onSelect: (name: string) => void;
}
class MyComponent extends React.Component<MyProps, {}> { }

このコンポーネントの観点から、すべての小道具が必要です。

次に、これらすべての小道具を状態から外すコンテナーを作成します。

function mapStateToProps(state: MyState) {
  return {
    name: state.my.name,
    selected: state.my.selected
  };
}

function mapDispatchToProps(dispatch: IDispatch) {
  return {
    onSelect(name: string) {
      dispatch(mySelectAction(name));
    }
  };
}

const MyContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent);

これは機能しますが、大きなタイピングの問題があります。マッピング関数(mapStateToPropsおよびmapDispatchToProps)は、MyPropsを満たすための適切なデータを提供しているという保護がありません。これは、エラー、タイプミス、および不十分なリファクタリングの傾向があります。

マッピング関数にタイプMyPropsを返させることができます:

function mapStateToProps(state: MyState): MyProps { }

function mapDispatchToProps(dispatch: IDispatch): MyProps { }

ただし、すべてのMyPropプロパティをオプションにしない限り、これは機能しません。そのため、各マッピング関数は、必要な部分のみを返すことができます。小道具はプレゼンテーションコンポーネントではオプションではないため、オプションにしたくありません。

別のオプションは、各マップ関数の小道具を分割し、コンポーネント小道具のためにそれらを結合することです:

// MyComponent
interface MyStateProps {
  name: string;
  selected: boolean;
}

interface MyDispatchProps {
  onSelect: (name: string) => void;
}

type MyProps = MyStateProps & MyDispatchProps;

class MyComponent extends React.Component<MyProps, {}> { }

// MyContainer
function mapStateToProps(state: MyState): MyStateProps { }

function mapDispatchToProps(dispatch: IDispatch): MyDispatchProps { }

わかりました。これで私は自分の望むものに近づいていますが、それはうるさくて、コンテナの形の周りにプレゼンテーションコンポーネントのプロップインターフェイスを書くので、私は好きではありません。

そして今、2番目の問題が発生します。コンテナーを別のコンポーネント内に配置する場合はどうなりますか?

<MyContainer />

これにより、nameselected、およびonSelectがすべて見つからないというコンパイルエラーが発生します...しかし、コンテナーがReduxに接続してそれらを提供しているため、これは意図的なものです。したがって、これによりすべてのコンポーネントの小道具をオプションにすることに戻りますが、実際にはオプションではないので、私はそれが好きではありません。

MyContainerに渡したい独自の小道具がいくつかあると、事態はさらに悪化します。

<MyContainer section="somethng" />

今私がやろうとしていることは、sectionの必須の小道具MyContainerを持っていますが、MyComponentの小道具はなく、nameselected、およびonSelectMyComponentの必須の小道具ですが、MyContainerのすべての小道具はオプションであるか、またはありません。私はこれをどう表現するか全く途方に暮れています。

これに関するどんな指導もいただければ幸いです!

20
Aaron Beall

あなたは最後の例で正しい軌道に乗っています。また、定義する必要があるのはMyOwnPropsインターフェースであり、connect関数を入力します。

これらのタイピング: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/react-redux/react-redux.d.ts 、次のようなことができます

interface MyProps {
  section: string;
  name: string;
  selected: boolean;
  onSelect: (name: string) => void;
}

interface MyStateProps {
  name: string;
  selected: boolean;
}

interface MyDispatchProps {
  onSelect: (name: string) => void;
}

interface MyOwnProps {
  section: string;
}

class MyComponent extends React.Component<MyProps, {}> { }

function mapStateToProps(state: MyState): MyStateProps { }

function mapDispatchToProps(dispatch: IDispatch): MyDispatchProps { }

const MyContainer = connect<MyStateProps, MyDispatchProps, MyOwnProps>(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent);

これにより、ownPropsmapStateToPropsmapDispatchToPropsと入力することもできます。

function mapStateToProps(state: MyState, ownProps: MyOwnProps): MyStateProps

ディスパッチタイプ、ステートタイプ、およびownPropsタイプを定義する限り、MyPropsタイプの交差タイプを定義する必要はありません。そうすることで、MyPropsを独自の場所に保持し、コンテナー、またはコンテナーなしでコンポーネントが使用される場所に、必要に応じて小道具を適用させることができます。これはあなたとあなたのユースケース次第だと思います-MyProps = MyStateProps & MyDispatchProps & MyOwnProps、1つの特定のコンテナーに関連付けられているため、柔軟性が低くなります(ただし、詳細度は低くなります)。

解決策としてはかなり冗長ですが、必要な小道具のさまざまな部分がさまざまな場所で組み立てられ、connectがそれらを結合することをTypeScriptに伝える方法はないと思います。

また、それだけの価値があるので、簡単にするために、通常はオプションの小道具を使いました。そのため、このアプローチを使用する上で共有する経験があまりありません。

19
Radio-

Partial<MyProps>、ここでPartialは、次のように定義される組み込みのTypeScriptタイプです。

type Partial<T> = {
    [P in keyof T]?: T[P];
}

インターフェースを取り、その中のすべてのプロパティをオプションにします。

これは、私が作成したプレゼンテーションとRedux対応のコンポーネントのペアの例です。

/ components/ConnectionPane/ConnectionPane.tsx

export interface IConnectionPaneProps {
  connecting: boolean;
  connected: boolean;
  onConnect: (hostname: string, port: number) => void;
}

interface IState {
  hostname: string;
  port?: number;
}

export default class ConnectionPane extends React.Component<IConnectionPaneProps, IState> {
   ...
}

/ containers/ConnectionPane/ConnectionPane.ts

import {connect} from 'react-redux';
import {connectionSelectors as selectors} from '../../../state/ducks';
import {connect as connectToTestEcho} from '../../../state/ducks/connection';
import {ConnectionPane, IConnectionPaneProps} from '../../components';

function mapStateToProps (state): Partial<IConnectionPaneProps> {
  return {
    connecting: selectors.isConnecting(state),
    connected: selectors.isConnected(state)
  };
}

function mapDispatchToProps (dispatch): Partial<IConnectionPaneProps> {
  return {
    onConnect: (hostname, port) => dispatch(connectToTestEcho(hostname, port))
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(ConnectionPane) as any;

プレゼンテーションコンポーネントの小道具はオプションではありません。対応する「スマート」コンポーネントに関係なく、プレゼンテーションに必要なものとまったく同じです。

一方、mapStateToPropsmapDispatchToPropsを使用すると、各関数で必要なプレゼンテーション小道具のサブセットを割り当てることができ、プレゼンテーション小道具インターフェースで定義されていない小道具にはエラーのフラグが付けられます。

2
Tagc
interface MyStateProps {
    name: string;
    selected: boolean;
}

interface MyDispatchProps {
    onSelect: (name: string) => void;
}

interface MyOwnProps {
    section: string;
}

// Intersection Types
type MyProps = MyStateProps & MyDispatchProps & MyOwnProps;


class MyComponent extends React.Component<MyProps, {}> { }

function mapStateToProps(state: MyState): MyStateProps { }

function mapDispatchToProps(dispatch: IDispatch): MyDispatchProps { }

const MyContainer = connect<MyStateProps, MyDispatchProps, MyOwnProps>(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent);

Intersection Typesと呼ばれる使用法を使用できます https://www.typescriptlang.org/docs/handbook/advanced-types.html#交差タイプ

2
Vikas Kumar