Definitelytyped: Le `connect` de react-redux ne peut pas être utilisé comme décorateur : type "n'est pas assignable au type 'void'"

Créé le 4 juil. 2016  ·  32Commentaires  ·  Source: DefinitelyTyped/DefinitelyTyped

Essayer d'utiliser connect de Type 'foo' is not assignable to type 'void' . Le problème avec ces typages semble être que TypeScript ne permet pas à ClassDecorator s de changer la signature de la chose qu'ils décorent , mais c'est l'implémentation actuelle des typages react-redux et est fait à dessein, puisque react-redux renvoie une instance différente avec une signature différente pour les props .

L'utilisation de connect comme fonction fonctionne comme prévu ; c'est seulement l'utilisation en tant que décorateur qui cause des problèmes de type.

Les suggestions sont les bienvenues, mais toute solution proposée devrait être une amélioration stricte de ce que les typages peuvent actuellement exprimer, c'est-à-dire que sacrifier l'exactitude actuelle des typages d'utilisation en tant que fonction n'est pas acceptable (en partie parce que ce serait un régression sévère et en partie parce que les décorateurs sont considérés comme "expérimentaux" et donc, je l'affirme, secondaires à l'utilisation des fonctions standard).

Je ne sais pas si une solution complète au problème de frappe du décorateur est possible alors que https://github.com/Microsoft/TypeScript/issues/4881 est en suspens. Cela dit, il y a des améliorations incrémentielles dans ce cas, comme par (première passe) sortie n'importe quel type de React.ComponentClass pour que le code compile au moins, puis (seconde passe) sortie d'un composant qui accepte l'intersection de tous les props (propres ainsi que connectés), même si cela serait trop clément, donc le type de code vérifie au moins certains des props.

La seule solution de contournement que j'ai pour le moment est de ne pas utiliser connect comme décorateur. Certains peuvent le trouver moche stylistiquement, mais il vérifie correctement le type, n'est pas plus long et est utilisable dans plus d'endroits qu'un décorateur.

À la place de:

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

utiliser (quelque chose dans le sens de):

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

Notez que https://github.com/DefinitelyTyped/DefinitelyTyped/issues/8787 entre en jeu ici et est parfois confondu avec ce problème. Vous pouvez également avoir besoin d'un indice de type ou d'un transtypage pour vous assurer que le composant de sortie a le type approprié.

Edit : https://github.com/Microsoft/TypeScript/pull/12114 peut aider dans ce cas : le décorateur pourrait potentiellement être écrit pour créer une version entièrement facultative des accessoires pour le composant généré, ce qui n'est pas totalement idéal mais continue à laisser votre implémentation être plus affirmée sur la nullité des props.

Commentaire le plus utile

Pour utiliser directement @connect (donc sans introduire de décorateurs personnalisés), j'utilise la solution de contournement suivante :

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

Mais je suis tout à fait d'accord avec @seansfkelley que n'importe quelle frappe n'est vraiment pas ce que nous voulons avoir...

Tous les 32 commentaires

J'avais des problèmes similaires l'autre jour et je l'ai abandonné pour la fonction de connexion. Les types regardaient et utilisaient d'anciens types de réaction. Je peux essayer de résoudre ce problème et soumettre un PR ?

Bien sûr! Beaucoup de choses ont changé depuis que ces types ont été écrits à l'origine, bien que je sois toujours sceptique quant au fait que sans le support du décorateur en mutation, quelque chose d'utile soit possible. Mais j'ai déjà travaillé sur d'autres insuffisances du système de types, alors essayez-le.

Peut-être que les types mappés pourraient vous aider en partie, donc au moins vous avez _quelques_ informations de type utiles dans la sortie ?

YOLO

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

Je ne pense pas qu'il y ait beaucoup d'intérêt à taper cela, car les solutions de contournement répertoriées ici et dans # 8787, en particulier celle concernant l'utilisation des astuces pour l'appel de fonction, ne sont pas si mauvaises (en particulier avec les modèles IDE pour les classes de composants) et avoir un composant n'importe quel type est triste. :( Tout type de connexion juste pour l'utiliser comme décorateur au lieu d'un appel de fonction met vraiment la charrue avant les bœufs.

Pour utiliser directement @connect (donc sans introduire de décorateurs personnalisés), j'utilise la solution de contournement suivante :

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

Mais je suis tout à fait d'accord avec @seansfkelley que n'importe quelle frappe n'est vraiment pas ce que nous voulons avoir...

Bien que la syntaxe du décorateur soit cool, je pense toujours que l'utilisation de HOC comme connect de manière standard est une meilleure pratique.

1) Il modifie le type d'une classe qui va à l'encontre des spécifications du décorateur.
2) Connect n'est pas du tout un décorateur, c'est un HOC.
3) Il brise la portabilité du code en ayant une syntaxe différente pour les composants sans état et avec état.

Je voudrais donc dire s'il vous plaît, ne fournissez pas une définition qui permet aux développeurs de l'utiliser dans le mauvais sens :)

@npirotte D'accord. Vous avez tout à fait raison, utiliser des HOC en tant que décorateurs viole les spécifications des décorateurs. Ce n'est plus la classe de base mais une classe complètement différente.

J'ai exactement les mêmes problèmes avec les décorateurs personnalisés et je regarde l'état de leur support dans TS (ils sont toujours derrière le drapeau et AFAICS il n'est pas prévu de les activer par défaut) Je suis sur le point d'abandonner le support pour eux. De plus les décorateurs sont encore en projet w3c.

La meilleure façon d'envelopper un composant dans un nouveau est d'utiliser une fonction d'ordre supérieur au lieu d'essayer de _décorer_ la classe existante.

J'ai la même erreur si je l'importe. Cependant, ce qui suit fonctionne pour moi:

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

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

@TriStarGod cela dépend de la façon dont vous avez tapé require . Il semble probable que vous vous retrouviez avec connect tapé comme any dans ce cas.

Une autre solution/solution de contournement.

Comme j'ai déjà mon propre état d'application, j'ai besoin d'une fonction très courte sous la forme de :

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

Et voici cette méthode de connexion enveloppée :

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
}

Merci pour votre travail. Inscription et attente de progrès sur cette question.

@offbeatful s'il vous plaît donner une déclaration de type d'une autre variation de mapDispatchToProps

Si un objet est passé, chaque fonction qu'il contient est supposée être un créateur d'action Redux. Un objet avec les mêmes noms de fonction, mais avec chaque créateur d'action enveloppé dans un appel de répartition afin qu'ils puissent être invoqués directement, sera fusionné dans les accessoires du composant.

une autre solution de contournement basée sur le commentaire @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;

Mis à jour en fonction de l'extrait de code @pravdomil et des derniers types (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 je suppose?), J'ai récemment posé une question sur le débordement de pile liée à ce problème où il a été conclu que cela n'est actuellement pas pris en charge. Dans le cadre de cette question, j'ai préparé un référentiel pour présenter ce que j'ai essayé. Il a été conclu que cela n'est actuellement pas pris en charge, j'étais donc ravi de voir votre extrait de code aujourd'hui et j'ai essayé de mettre à jour mon référentiel pour l'utiliser.

Ici vous pouvez voir le commit avec votre extrait de code intégré

Cela ne crie plus avec les erreurs que je recevais auparavant, mais j'ai constaté que dans mon cas d'utilisation où mon composant est connecté, mais a également ses propres accessoires, cette nouvelle signature fera désormais en sorte que même les accessoires d'état soient requis comme propres accessoires (TSX). cd my-app && yarn start dans mon référentiel montrera ce que je veux dire.

Pensez-vous que c'est quelque chose qui peut être abordé aussi ou n'est-ce pas possible?

vous pouvez éventuellement utiliser cette version connect

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

si c'est le cas, il vous suffit de corriger le paramètre options pour qu'il soit Options<any, any, any> cette façon vous n'aurez pas le problème TStateProps & TDispatchProps car ce sera TStateProps & any

en un mot:

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

cool comme toi

@TomasHubelbauer Je
cela fonctionnera assez curieusement.

Il est préférable de vous assurer que vous utilisez des types partagés entre les trois :

export type CounterStateProps = {
    count: number;
};

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

export type CounterOwnProps = {
    initialCount: number;
};

export type CounterProps = CounterStateProps & CounterDispatchProps & CounterOwnProps;

Ensuite, implémentez le composant avec état

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

Et enfin, créez la classe de connecteur comme telle en utilisant le build in connect de redux PAS le code de connexion personnalisé.

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 Oui, je sais que cela fonctionne, dans mon exemple, j'ai montré qu'avant d'essayer certaines des suggestions pour que cela fonctionne en tant que décorateur. J'utilise avec succès la variante sans décorateur, mais j'aimerais vraiment que le décorateur fonctionne aussi.

De plus, je pense que vous avez mélangé vos types, vous utilisez CounterStateProps (qui est le type de retour de mapDispatchToProps tant que composant de CounterProps (ce qui est bien, il a l'état Redux props, Redux dispatch props et JSX own props), mais aussi en tant que type pour l'état des composants. Au lieu state cela,

Il est possible que je n'aie pas complètement compris votre solution, donc si vous pouvez faire en sorte que cela fonctionne dans mon référentiel (où j'utilise à la fois mes propres accessoires dans TSX et les accessoires d'état Redux) sans obtenir une erreur indiquant que vous devez spécifier les accessoires d'état Redux dans TSX , aussi, ce serait super !

+1 sur ce problème

J'ai mis à jour l'extrait de @offbeatful et je l'utilise avec succès maintenant.

connect.ts ( IAppState est l'interface de redux state)

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

Ensuite, mon composant connecté ressemble à ceci:

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>

La diffusion directe de MyComponent as React.ComponentType<IOwnProps> échouera, alors je l'ai d'abord tapé comme any . Je sais que c'est un hack mais ça marche bien pour le parent et pour le composant lui-même aussi.

C'est la solution la plus viable, mais toujours restrictive, que j'ai pu trouver.

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

des nouvelles?

@ctretyak non , vous devrez éventuellement créer vous-même un fichier de déclaration et modifier le décorateur. On dirait que le React Eco et le TS ne sont pas les meilleurs amis, c'est pourquoi je l'ai abandonné.

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

ce type d'importation permet à l'IDE d'afficher les accessoires et de masquer l'erreur ts. un peu moche, mais les décorateurs ont l'air mieux qu'une simple connexion. surtout si vous configurez pour ajouter automatiquement un sélecteur tel que la traduction ou la navigation

Ce n'est pas un problème de réaction-redux. J'ai le même comportement avec le @withRouter (de React-Router) et @graphql

Tapuscrit semble ne pas comprendre qu'un décorateur puisse injecter des accessoires...

Tapuscrit semble ne pas comprendre qu'un décorateur puisse injecter des accessoires...

Euh, c'est toujours un problème? Je viens de tomber sur ça.

Je suis triste que quelque chose détruise notre cerveau, mais ils ont été conçus pour nous mettre plus à l'aise :(

Je suis triste que quelque chose détruise notre cerveau, mais ils ont été conçus pour nous mettre plus à l'aise :(

Moi aussi :(

Cela ne couvre probablement pas tous les cas d'utilisation, mais cela a bien fonctionné pour moi.
Cela me donne également la possibilité d'ajouter mon type de magasin (MyAppStore) en un seul endroit.

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

avez-vous des mises à jour?

J'aimerais aussi utiliser connect comme décorateur.

Des solutions officielles fournies ???
Je pense que de nombreux développeurs préfèrent utiliser @connect plutôt que connect(mapStateToProps,mapDispatchToProps)(components)

Cette page vous a été utile?
0 / 5 - 0 notes