Definitelytyped: `connect` von React-Redux kann nicht als Dekorator verwendet werden: Typ "ist dem Typ 'void' nicht zuordenbar"

Erstellt am 4. Juli 2016  ·  32Kommentare  ·  Quelle: DefinitelyTyped/DefinitelyTyped

Der Versuch, connect React-Redux als Klassen-Dekorator zu verwenden, kann Fehlermeldungen der Form Type 'foo' is not assignable to type 'void' . Das Problem mit diesen Eingaben scheint zu sein, dass TypeScript es ClassDecorator s nicht erlaubt, die Signatur des Dings zu ändern, das sie dekorieren , aber dies ist die aktuelle Implementierung der React-Redux-Typisierungen und wird React-redux gibt eine andere Instanz mit einer anderen Signatur für die props zurück .

Die Verwendung von connect als Funktion funktioniert wie erwartet; nur die Verwendung als Dekorateur verursacht Typprobleme.

Vorschläge sind willkommen, aber alle vorgeschlagenen Lösungen sollten eine strikte Verbesserung dessen darstellen, was die Typisierungen derzeit ausdrücken können, d schwere Regression und teilweise weil Dekorateure als "experimentell" angesehen werden und daher, behaupte ich, sekundär gegenüber der Verwendung von Standardfunktionen sind).

Ich weiß nicht, ob eine vollständige Lösung des Decorator-Typing-Problems möglich ist, während https://github.com/Microsoft/TypeScript/issues/4881 aussteht. Allerdings gibt es in diesem Fall inkrementelle Verbesserungen, z. B. indem (erster Durchgang) eine beliebige Art von React.ComponentClass ausgegeben wird, damit der Code zumindest kompiliert wird, und dann (zweiter Durchgang) eine Komponente ausgeben, die die Schnittmenge aller Requisiten (sowohl eigene als auch verbundene), obwohl das zu nachsichtig wäre, sodass der Codetyp zumindest einige der Requisiten überprüft.

Die einzige Problemumgehung, die ich derzeit habe, besteht darin, connect als Dekorateur zu verwenden. Manche mögen es stilistisch hässlich finden, aber es prüft korrekt, ist nicht länger und kann an mehr Stellen verwendet werden als ein Dekorateur.

Anstatt von:

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

verwenden (etwas in der Art):

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

Beachten Sie, dass https://github.com/DefinitelyTyped/DefinitelyTyped/issues/8787 hier ins Spiel kommt und manchmal mit diesem Problem verwechselt wird. Möglicherweise möchten Sie auch einen Typhinweis oder eine Umwandlung, um sicherzustellen, dass die Ausgabekomponente den richtigen Typ hat.

Bearbeiten: https://github.com/Microsoft/TypeScript/pull/12114 kann in diesem Fall helfen: Der Dekorator könnte möglicherweise geschrieben werden, um eine optionale Version der Requisiten für die generierte Komponente zu erstellen, was nicht ganz ideal ist lässt Ihre Implementierung jedoch weiterhin durchsetzungsfähiger in Bezug auf die Nichtigkeit von Props sein.

Hilfreichster Kommentar

Um @connect direkt zu verwenden (also ohne benutzerdefinierte Dekorateure einzuführen), verwende ich den folgenden Workaround:

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

Aber ich stimme @seansfkelley voll und ganz zu, dass jegliches Tippen wirklich nicht das ist, was wir haben wollen ...

Alle 32 Kommentare

Ich hatte neulich ähnliche Probleme und habe es wegen der Verbindungsfunktion fallen lassen. Die Typen schauten ab und benutzten alte Reaktionstypen. Ich kann versuchen, dies zu beheben und eine PR einreichen?

Natürlich! Vieles hat sich geändert, seit diese Typen ursprünglich geschrieben wurden, obwohl ich immer noch skeptisch bin, dass ohne Mutating-Decorator-Unterstützung etwas Nützliches möglich ist. Aber ich habe schon andere Unzulänglichkeiten im Typensystem umgangen, also probiere es aus.

Vielleicht könnten Ihnen zugeordnete Typen einen Teil des Weges dorthin bringen, sodass Sie zumindest _einige_ nützliche Typinformationen in der Ausgabe haben?

YOLO

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

Ich denke nicht, dass es viel Sinn macht, dies mit Yolo zu tippen, da die hier und in #8787 aufgeführten Workarounds, insbesondere die über die Verwendung der Hinweise zum Funktionsaufruf, nicht so schlecht sind (insbesondere bei IDE-Vorlagen für Komponentenklassen). und eine beliebige typisierte Komponente zu haben, ist traurig. :( Connect mit beliebiger Eingabe, nur um es als Dekorator anstelle eines Funktionsaufrufs zu verwenden, stellt wirklich den Wagen vor das Pferd.

Um @connect direkt zu verwenden (also ohne benutzerdefinierte Dekorateure einzuführen), verwende ich den folgenden Workaround:

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

Aber ich stimme @seansfkelley voll und ganz zu, dass jegliches Tippen wirklich nicht das ist, was wir haben wollen ...

Obwohl die Decorator-Syntax cool ist, denke ich immer noch, dass die Verwendung von HOC wie Connect auf Standardmethode eine bessere Vorgehensweise ist.

1) Es ändert den Typ einer Klasse, die gegen die Dekorateur-Spezifikationen verstößt.
2) Connect ist überhaupt kein Dekorateur, sondern ein HOC.
3) Es unterbricht die Portabilität des Codes, indem es eine unterschiedliche Syntax für zustandslose und zustandsbehaftete Komponenten hat.

Also ich möchte bitte sagen, geben Sie keine Definition an, die es Entwicklern ermöglicht, sie falsch zu verwenden :)

@npirotte stimme zu. Sie haben absolut Recht, die Verwendung von HOCs als Dekoratoren verstößt gegen die Dekoratorenspezifikation. Es ist nicht mehr die Basisklasse, sondern eine ganz andere.

Ich habe genau die gleichen Probleme mit benutzerdefinierten Dekorateuren und schaue mir den Status ihrer Unterstützung in TS an (sie sind immer noch hinter der Flagge und AFAICS es gibt keine Pläne, sie standardmäßig zu aktivieren). Sie. Außerdem befinden sich Dekorateure noch im w3c-Entwurf.

Der beste Weg, eine Komponente in eine neue einzuschließen, besteht darin, eine Funktion höherer Ordnung dafür zu verwenden, anstatt zu versuchen, eine vorhandene Klasse zu _dekorieren_.

Ich habe den gleichen Fehler, wenn ich es importiere. Bei mir funktioniert jedoch folgendes:

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

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

@TriStarGod das hängt davon ab, wie Sie require eingegeben haben. Es ist wahrscheinlich, dass Sie in diesem Fall connect als any eingeben.

Eine andere Lösung / Problemumgehung.

Da ich bereits einen eigenen App State habe, benötige ich eine sehr kurze Funktion in Form von:

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> {
...
}

Und hier ist die umschlossene Verbindungsmethode:

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 für deine Arbeit. Abonnieren und warten auf Fortschritte zu diesem Thema.

@offbeatful Bitte geben Sie die Typdeklaration einer anderen Variation von mapDispatchToProps an

Wenn ein Objekt übergeben wird, wird jede darin enthaltene Funktion als Redux-Aktionsersteller angenommen. Ein Objekt mit den gleichen Funktionsnamen, aber mit jedem Aktionsersteller, der in einen Dispatch-Aufruf eingeschlossen ist, damit er direkt aufgerufen werden kann, wird in die Props der Komponente eingebunden.

ein weiterer Workaround basierend auf @offbeatful Kommentar

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;

Aktualisiert basierend auf dem @pravdomil- Snippet und den neuesten Typen (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;

Hey @offbeatful (cc: @pravdomil nehme ich an?), Ich habe kürzlich eine Stack Overflow-Frage zu diesem Problem gestellt, bei der festgestellt wurde, dass dies derzeit nicht unterstützt wird. Als Teil dieser Frage habe ich ein Repository vorbereitet, um zu zeigen, was ich versucht habe. Es wurde festgestellt, dass dies derzeit nicht unterstützt wird, also habe ich mich sehr gefreut, Ihr Code-Snippet heute zu sehen, und habe versucht, mein Repository zu aktualisieren, um es zu verwenden.

Hier sehen Sie den Commit mit Ihrem integrierten Code-Snippet

Dies schreit nicht mehr mit den Fehlern, die ich zuvor erhalten habe, aber ich habe festgestellt, dass in meinem Anwendungsfall, in dem meine Komponente verbunden ist, aber auch eigene Requisiten hat, diese neue Signatur es jetzt so macht, dass sogar Zustandsprops erforderlich sind, da eigene (TSX) Requisiten. cd my-app && yarn start in meinem Repository zeigt, was ich meine.

Denkst du, das könnte man auch angehen oder ist das nicht möglich?

Sie können möglicherweise diese connect Version verwenden

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

wenn ja, Sie brauchen nur zu beheben , die options Parameter zu Options<any, any, any> diese Weise werden Sie nicht das haben TStateProps & TDispatchProps Ausgabe Cuz es wird TStateProps & any

in einer Nussschale:

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

cool wie du

@TomasHubelbauer Habe gerade deine Methode ausprobiert, wenn du zur Standard-Redux-Connect-Methode zurückkehrst
es wird seltsam genug funktionieren.

Stellen Sie am besten sicher, dass Sie gemeinsame Typen zwischen den drei verwenden:

export type CounterStateProps = {
    count: number;
};

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

export type CounterOwnProps = {
    initialCount: number;
};

export type CounterProps = CounterStateProps & CounterDispatchProps & CounterOwnProps;

Implementieren Sie dann die zustandsbehaftete Komponente

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}/>
      );
    }
}

Und schließlich machen Sie die Connector-Klasse so, indem Sie redux's build in connect verwenden, NICHT den benutzerdefinierten Connect-Code.

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 Ja, ich weiß, dass das funktioniert, in meinem Beispiel habe ich das gezeigt, bevor ich einige der Vorschläge ausprobiert habe, damit es als Dekorateur funktioniert. Ich benutze die Nicht-Dekorator-Variante erfolgreich, aber ich würde gerne auch die Dekorateur-Variante zum Laufen bringen.

Außerdem glaube ich, dass Sie Ihre Typen verwechselt haben, Sie verwenden CounterStateProps (das ist der Rückgabetyp von mapDispatchToProps als eine Komponente von CounterProps (was in Ordnung ist, es hat den Redux-Zustand) props, Redux-Dispatch-Props und JSX-eigene Props), sondern auch als Typ für den Komponentenzustand, stattdessen sollte state einen eigenen Typ haben, der in keiner Weise an der Typsignatur der externen Komponente beteiligt ist.

Es ist möglich, dass ich Ihre Lösung nicht vollständig verstanden habe. Wenn Sie dies in meinem Repository zum Laufen bringen können (wo ich sowohl eigene Requisiten in TSX als auch Redux-State-Props verwende), ohne eine Fehlermeldung zu erhalten, dass Sie Redux-State-Props in TSX angeben müssen , das wäre auch toll!

+1 zu diesem Problem

Ich habe das Snippet von @offbeatful aktualisiert und verwende es jetzt erfolgreich.

connect.ts ( IAppState ist die Schnittstelle des Redux-Zustands)

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

Dann sieht meine angeschlossene Komponente so aus:

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>

Das direkte Casting von MyComponent as React.ComponentType<IOwnProps> schlägt fehl, also habe ich es zuerst als any eingegeben. Ich weiß, es ist ein Hack, funktioniert aber auch gut für das übergeordnete Element und für die Komponente selbst.

Dies ist die praktikabelste und dennoch restriktivste Lösung, die ich finden konnte.

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

irgendwelche Neuigkeiten?

@ctretyak nein, Sie müssen schließlich selbst eine Deklarationsdatei erstellen und den Dekorator ändern. Anscheinend sind React Eco und TS nicht die besten Freunde, daher habe ich mich davon entfernt.

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

Diese Art des Imports ermöglicht es der IDE, Requisiten anzuzeigen und den Fehler zu verbergen. ein bisschen hässlich, aber Dekorateure sehen besser aus als einfaches Verbinden. vor allem, wenn Sie so konfigurieren, dass einige Selektoren automatisch hinzugefügt werden, z. B. Übersetzungen oder Navigation

Es ist kein Reagieren-Redux-Problem. Das gleiche Verhalten habe ich mit @withRouter (von React-Router) und @graphql

Typoskript scheint nicht zu verstehen, dass ein Dekorateur Requisiten einspritzen könnte ...

Typoskript scheint nicht zu verstehen, dass ein Dekorateur Requisiten einspritzen könnte ...

Äh, das ist immer noch ein Thema? Bin gerade darauf gestoßen.

Ich bin traurig, dass etwas unser Gehirn zerstört, aber sie wurden entwickelt, um uns wohler zu machen :(

Ich bin traurig, dass etwas unser Gehirn zerstört, aber sie wurden entwickelt, um uns wohler zu machen :(

ich auch :(

Dies deckt wahrscheinlich nicht alle Anwendungsfälle ab, hat aber für mich gut funktioniert.
Es gibt mir auch die Möglichkeit, meinen Store-Typ (MyAppStore) an einem Ort hinzuzufügen.

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

Hast du irgendwelche Updates?

Ich möchte Connect auch als Dekorateur verwenden.

Irgendwelche offiziellen Lösungen geliefert???
Ich denke, es gibt viele Entwickler, die lieber @connect als connect(mapStateToProps,mapDispatchToProps)(components) verwenden.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen