Definitelytyped: react-reduxの `connect`をデコレータとして使用することはできません:タイプ"はタイプ 'void'に割り当てることができません "

作成日 2016年07月04日  ·  32コメント  ·  ソース: DefinitelyTyped/DefinitelyTyped

react-reduxのconnectをクラスデコレータとして使用しようとするType 'foo' is not assignable to type 'void'形式のエラーメッセージが表示される可能性があります。 これらのタイピングの問題は、 TypeScriptがClassDecoratorで装飾するものの署名を変更できないことですが、これはreact-reduxタイピングの現在の実装であり、意図的に行われているためです。 react-reduxは、小道具の署名が異なる別のインスタンスを返します

connectを関数として使用すると、期待どおりに機能します。 タイプの問題を引き起こすのは、デコレータとしての使用法だけです。

提案は大歓迎ですが、提案された解決策は、タイピングが現在表現できるものを厳密に改善する必要があります。つまり、関数としての使用法のタイピングの現在の正確さを犠牲にすることは受け入れられません(深刻な回帰であり、デコレータが「実験的」であると見なされているため、標準的な関数の使用法に次ぐものであると私は主張します。

https://github.com/Microsoft/TypeScript/issues/4881が優れている一方で、デコレータタイピングの問題に対する完全な解決策が可能かどうかはわかりません。 とは言うものの、この場合、コードが少なくともコンパイルされるように(最初のパス)任意の種類のReact.ComponentClass出力し、次に(2番目のパス)すべての交差を受け入れるコンポーネントを出力するなど、段階的な改善があります。小道具(所有されているだけでなく接続されている)も、寛大すぎるため、コードタイプは少なくともいくつかの小道具をチェックします。

私が今持っている唯一の回避策は、デコレータとしてconnectを使用しないことです。 スタイリスト的に醜いと感じる人もいるかもしれませんが、タイプチェックは正しく、長くはなく、デコレータよりも多くの場所で使用できます。

それ以外の:

@connect(mapStateToProps, mapDispatchToProps)
class MyComponent extends React.Component<...> { ... }

使用(の線に沿った何か):

class MyComponentImpl extends React.Component<...> { ... }
const MyComponent = connect(mapStateToProps, mapDispatchToProps)(MyComponentImpl);

https://github.com/DefinitelyTyped/DefinitelyTyped/issues/8787がここで機能し、この問題と混同される場合があることに注意して

編集: https

最も参考になるコメント

@connect直接使用するには(カスタムデコレータを導入せずに)、次の回避策を使用します。

@(connect(mapStateToProps, mapDispatchToProps) as any)
class MyComponent extends React.Component<...> {...}

しかし、私は@seansfkelleyに完全に同意します。タイピングは、私たちが望んでいるものではありません...

全てのコメント32件

私は先日同様の問題を抱えていて、接続機能のためにそれを落としました。 タイプは見送られ、古い反応タイプを使用していました。 私はこれを修正してPRを提出しようとするかもしれませんか?

もちろん! これらのタイプが最初に作成されてから多くの変更がありましたが、変異デコレータのサポートがなくても何か便利なことが可能であるかどうかについてはまだ懐疑的です。 しかし、私は以前に型システムの他の不備を回避したことがあるので、それを試してみてください。

おそらく、マップされた型はそこへの道の一部を得ることができるので、少なくとも出力には_いくつかの_有用な型情報がありますか?

YOLO

export function myConnect(mapStateToProps, mapDispatchToProps) {
    return function (target) {
        return <any>connect(mapStateToProps, mapDispatchToProps)(target);
    }
}

これをyolo-typingする意味はあまりないと思います。ここと#8787にリストされている回避策、特に関数呼び出しのヒントの使用に関する回避策はそれほど悪くはありません(特にコンポーネントクラスのIDEテンプレートの場合)そして、任意のタイプのコンポーネントを持つことは悲しいことです。 :(関数呼び出しの代わりにデコレータとして使用するためだけに接続を入力すると、実際にはカートが馬の前に置かれます。

@connect直接使用するには(カスタムデコレータを導入せずに)、次の回避策を使用します。

@(connect(mapStateToProps, mapDispatchToProps) as any)
class MyComponent extends React.Component<...> {...}

しかし、私は@seansfkelleyに完全に同意します。タイピングは、私たちが望んでいるものではありません...

デコレータの構文はかっこいいですが、標準的な方法で接続のようにHOCを使用する方が良い方法だと思います。

1)デコレータの仕様に反するクラスのタイプを変更します。
2)Connectはデコレータではなく、HOCです。
3)ステートレスコンポーネントとステートフルコンポーネントの構文が異なるため、コードの移植性が損なわれます。

だから私はお願いしたいのですが、開発者がそれを間違った方法で使用することを可能にする定義を提供しないでください:)

@npirotte同意します。 デコレータとしてHOCを使用すると、デコレータの仕様に違反します。 それはもはや基本クラスではありませんが、完全に異なるものです。

私はカスタムデコレータとまったく同じ問題を抱えており、TSでのサポートの状態を調べています(それらはまだフラグの後ろにあり、AFAICSはデフォルトでそれらを有効にする予定はありません)彼ら。 さらに、デコレータはまだw3cドラフトにあります。

コンポーネントを新しいコンポーネントにラップする最良の方法は、既存のクラスを_装飾_するのではなく、高階関数を使用することです。

インポートしても同じエラーが発生します。 ただし、次のことは私にとってはうまくいきます。

const { connect } = require('react-redux');

@connect(mapStateToProps, mapDispatchToProps)
class MyComponent extends React.Component<...> { ... }

@TriStarGodはrequire入力方法によって異なります。 その場合、 connectanyと入力されてしまう可能性があります。

別の解決策/回避策。

私はすでに自分のAppStateを持っているので、次の形式の非常に短い関数が必要です。

export interface PageProps {
    routing: RouterState;
}

export interface PageDispatch {
    navigate: () => void
}

@connect<PageProps, PageDispatch>(
    state => ({
        routing: state.routing
    }),
    dispatch => ({
        navigate: () => dispatch(push("/"))
    })
)
export class Page extends React.Component<PageProps & PageDispatch> {
...
}

そして、これがそのラップされた接続メソッドです。

import { connect } from "react-redux";
import { ApplicationState } from './rootReducer';

interface MapPropsParam<TProps>{
    (state: ApplicationState, ownProps?: TProps): TProps
}

interface MapDispatchParam<TProps, TDispatchProps>{
   (dispatch: Redux.Dispatch<ApplicationState>, ownProps?: TProps): TDispatchProps;
}

export interface ConnectedComponent<TProps> {
    <TComponent extends React.ComponentType<TProps>>(component: TComponent): TComponent;
}

function connectToAppState<TProps, TDispatchProps = {}>(mapProps: MapPropsParam<TProps>, mapDispatch?: MapDispatchParam<TProps, TDispatchProps>) : ConnectedComponent<TProps> {
    return connect<TProps, TDispatchProps, TProps>(mapProps, mapDispatch) as ConnectedComponent<TProps & TDispatchProps>;    
}

export {
    connectToAppState as connect
}

あなたの仕事のためのThx。 購読して、この問題の進捗を待っています。

@offbeatfulmapDispatchToProps他のバリエーションの型宣言をお願いします

オブジェクトが渡された場合、その中の各関数はReduxアクションクリエーターであると見なされます。 同じ関数名を持つオブジェクトですが、すべてのアクションクリエーターがディスパッチ呼び出しにラップされているため、直接呼び出される可能性があり、コンポーネントの小道具にマージされます。

@offbeatfulコメントに基づく別の回避策

myConnect.ts

import {
    connect as originalConnect, MapDispatchToPropsParam, MapStateToPropsParam, MergeProps, Options
} from "react-redux";
import * as React from "react";

export interface InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> {
    <TComponent extends React.ComponentType<TInjectedProps & TNeedsProps>>(component: TComponent): TComponent;
}

interface MyConnect {
    <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}>(
        mapStateToProps?: MapStateToPropsParam<TStateProps, TOwnProps>,
        mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>
    ): InferableComponentEnhancerWithProps<TStateProps & TDispatchProps, TOwnProps>;

    <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, TMergedProps = {}>(
        mapStateToProps?: MapStateToPropsParam<TStateProps, TOwnProps>,
        mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
        mergeProps?: MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps>,
        options?: Options<TStateProps, TOwnProps, TMergedProps>
    ): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>;

}

export const connect = originalConnect as MyConnect;

@pravdomilスニペットと最新のタイプに基づいて更新(5.0.13)

import { ApplicationState } from "./rootReducer";

import * as React from "react";
import {
    connect as originalConnect, MapDispatchToPropsParam, MapStateToPropsParam, MergeProps, Options
} from "react-redux";

export type InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> = <TComponent extends React.ComponentType<TInjectedProps & TNeedsProps>>(component: TComponent) => TComponent;

interface MyConnect {
    <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}>(
        mapStateToProps?: MapStateToPropsParam<TStateProps, TOwnProps, ApplicationState>,
        mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>
    ): InferableComponentEnhancerWithProps<TStateProps & TDispatchProps, TOwnProps>;

    <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, TMergedProps = {}>(
        mapStateToProps?: MapStateToPropsParam<TStateProps, TOwnProps, ApplicationState>,
        mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
        mergeProps?: MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps>,
        options?: Options<TStateProps, TOwnProps, TMergedProps>
    ): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>;

}

export const connect = originalConnect as MyConnect;

ねえ@offbeatful (cc: @pravdomilだと思いますか?)、私は最近、この問題に関連するStack Overflowの質問をしましたが、これは現在サポートされていないと結論付けられました。 その質問の一部として、私が試したことを紹介するためのリポジトリを用意しまし

ここでは、コードスニペットが統合されたコミットを確認できます

これは以前に発生したエラーでもはや怒鳴りませんが、コンポーネントが接続されているだけでなく、独自の小道具もあるユースケースでは、この新しい署名により、州の小道具でさえも必要になることがわかりました。独自の(TSX)小道具。 私のリポジトリのcd my-app && yarn startは、私が何を意味するかを示しています。

これも対処できるものだと思いますか、それとも不可能ですか?

このconnectバージョンを使用している可能性があります

connect(
        mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
        mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
        mergeProps: null | undefined,
        options: Options<State, TStateProps, TOwnProps>
): InferableComponentEnhancerWithProps<TStateProps & TDispatchProps, TOwnProps>

その場合は、 optionsパラメータをOptions<any, any, any>に修正する必要があります。これにより、 TStateProps & TDispatchProps問題が発生しなくなり、 TStateProps & any

一言で言えば:

const options = { withRef:true } as Options<any, any, any>;
export const Component = connect(mapStateToProps, mapDispatchToProps, null, options)

あなたのようにかっこいい

@TomasHubelbauerデフォルトのredux接続メソッドに戻った場合は、メソッドを試してみました
それは奇妙なことに十分に機能します。

3つの間で共有タイプを使用することを確認するのが最善です。

export type CounterStateProps = {
    count: number;
};

export type CounterDispatchProps = {
    onIncrement: () => void;
};

export type CounterOwnProps = {
    initialCount: number;
};

export type CounterProps = CounterStateProps & CounterDispatchProps & CounterOwnProps;

次に、ステートフルコンポーネントを実装します

export class StatefulCounter extends React.Component<CounterProps, CounterStateProps> {
    timer: number;

    componentDidMount() {
        this.timer = setInterval(this.props.onIncrement, 5000);
    }

    componentWillUnmount() {
        clearInterval(this.timer);
    }

    render() {
      return (
        <StatelessCounter count={this.props.count}/>
      );
    }
}

そして最後に、カスタム接続コードではなく、reduxのビルドイン接続を使用して、コネクタクラスをそのようにします。

const mapStateToProps =
    (state: RootState, ownProps: CounterOwnProps): CounterStateProps => ({
        count: countersSelectors.getReduxCounter(state) + ownProps.initialCount,
    });

const mapDispatchToProps =
    (dispatch: Dispatch<CounterActionType>): CounterDispatchProps => bindActionCreators({
        onIncrement: CounterActions.increment,
    }, dispatch);

export const ConnectedCounter =
    connect(mapStateToProps, mapDispatchToProps)(StatefulCounter);

@JohnHandleyええ、私はこれが機能することを知っています。私の例では、デコレータとして機能させるための提案をいくつか試す前に示しました。 私はデコレータ以外のバリアントをうまく使用していますが、デコレータも1つ機能させたいと思っています。

また、タイプを混同していると思います。 CounterStatePropsCounterPropsコンポーネントとしてmapDispatchToPropsのリターンタイプです)を使用します(これは問題ありません。Redux状態です。小道具、Reduxディスパッチ小道具、JSX独自の小道具)だけでなく、コンポーネントの状態の型としても使用できます。代わりに、 stateは、外部コンポーネントの型署名にまったく関与しない独自の型を持つ必要があります。

私があなたの解決策を完全に理解していなかった可能性があるので、TSXでRedux状態の小道具を指定する必要があるというエラーを受け取らずに私のリポジトリ(TSXで独自の小道具とRedux状態の小道具の両方を使用する)でこれを機能させることができれば、それも素晴らしいことです!

この問題について+1

@offbeatfulからスニペットを更新し、現在は正常に使用しています。

connect.tsIAppStateはredux状態のインターフェースです)

import React from 'react'
import {
    connect as originalConnect,
    MapDispatchToPropsParam,
    MapStateToPropsParam,
    MergeProps,
    Options,
} from 'react-redux'


export type InferableComponentEnhancerWithProps<IInjectedProps, INeedsProps> =
    <IComponent extends React.ComponentType<IInjectedProps & INeedsProps>>(component: IComponent) => IComponent

export interface IConnect {
    <IStateProps = {}, IDispatchProps = {}, IOwnProps = {}>(
        mapStateToProps?: MapStateToPropsParam<IStateProps, IOwnProps, IAppState>,
        mapDispatchToProps?: MapDispatchToPropsParam<IDispatchProps, IOwnProps>,
    ): InferableComponentEnhancerWithProps<IStateProps & IDispatchProps, IOwnProps>

    <IStateProps = {}, IDispatchProps = {}, IOwnProps = {}, IMergedProps = {}>(
        mapStateToProps?: MapStateToPropsParam<IStateProps, IOwnProps, IAppState>,
        mapDispatchToProps?: MapDispatchToPropsParam<IDispatchProps, IOwnProps>,
        mergeProps?: MergeProps<IStateProps, IDispatchProps, IOwnProps, IMergedProps>,
        options?: Options<IStateProps, IOwnProps, IMergedProps>,
    ): InferableComponentEnhancerWithProps<IMergedProps, IOwnProps>

}

export const connect = originalConnect as IConnect

次に、接続されたコンポーネントは次のようになります。

import {connect} from 'path-to-my-connect/connect'

interface IOwnProps {
    ... props exposed for the real parent component
}

interface IStateProps {
    ... props from mapStateToProps
}

interface IDispatchProps {
    ... props from mapDispatchToProps
}

interface IProps extends IStateProps, IDispatchProps, IOwnProps {}

@connect<IStateProps, IDispatchProps, IOwnProps>(
    (state: IAppState) => {
            return {
                foo: getFoo(state), // getFoo is a selector using 'reselect'
            }
        },
    {setFoo, increment, decrement, ... your action creators},
)
class MyComponent extends React.PureComponent<IProps> {
        // your component implementation
}

export default (MyComponent as any) as React.ComponentType<IOwnProps>

MyComponent as React.ComponentType<IOwnProps>直接キャストは失敗するので、最初にanyと入力しました。 私はそれがハックであることを知っていますが、親にとってもコンポーネント自体にとってもうまく機能します。

これは、私が思いついた中で最も実行可能でありながら、依然として制限的な解決策です。

"react-redux": "^5.0.6",
"typescript": "^2.8.1",
"@types/react-redux": "^6.0.0",

連絡あった?

@ctretyakいいえ、最終的には自分で宣言ファイルを作成し、デコレータを変更する必要があります。 React EcoとTSは親友ではないようです。それで、私はそれから先に進みました。

const DecoratedComponent = require('../DecoratedComponent').default;

この種のインポートにより、IDEは小道具を表示してtsエラーを非表示にできます。 少し醜いですが、デコレータは単純な接続よりも見栄えがします。 特に、翻訳やナビゲーションなどのセレクターを自動的に追加するように構成した場合

それはreact-reduxの問題ではありません。 @withRouter (React-Routerから)と@graphqlと同じコンポートメントを持っています

Typescriptは、デコレータが小道具を注入できることを理解していないようです...

Typescriptは、デコレータが小道具を注入できることを理解していないようです...

うーん、これはまだ問題ですか? ちょうどこれに遭遇しました。

何かが私たちの脳を破壊しているのは悲しいことですが、それらは私たちをより快適にするように設計されています:(

何かが私たちの脳を破壊しているのは悲しいことですが、それらは私たちをより快適にするように設計されています:(

私もです :(

これはおそらくすべてのユースケースを網羅しているわけではありませんが、私にとってはうまく機能しています。
また、ストアタイプ(MyAppStore)を1か所に追加する機会もあります。

export function connectTs<TDispatchProps={}, TOwnProps=any>(
  mapStateToProps?: (state: MyAppStore, ownProps?: TOwnProps) => any,
  mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>
): any {
  return connect(mapStateToProps, mapDispatchToProps)
}

更新はありますか?

connectをデコレータとしても使いたいです。

提供される公式ソリューションはありますか?
connect(mapStateToProps、mapDispatchToProps)(components)よりも@connectを使用することを好む開発者はたくさんいると思います

このページは役に立ちましたか?
0 / 5 - 0 評価