Definitelytyped: O `connect` do react-redux não pode ser usado como um decorador: o tipo" não pode ser atribuído ao tipo 'void' "

Criado em 4 jul. 2016  ·  32Comentários  ·  Fonte: DefinitelyTyped/DefinitelyTyped

Tentar usar connect do react-redux como decorador de classe pode causar mensagens de erro no formato Type 'foo' is not assignable to type 'void' . O problema com essas tipificações parece ser que o TypeScript não permite que ClassDecorator s altere a assinatura do que eles decoram , mas esta é a implementação atual das tipificações react -redux e é feita propositalmente, uma vez que react-redux retorna uma instância diferente com uma assinatura diferente para os adereços .

Usar connect como uma função funciona conforme o esperado; é apenas o uso como decorador que causa problemas de tipo.

Sugestões são bem-vindas, mas qualquer solução proposta deve ser um aprimoramento estrito do que as digitações podem expressar atualmente, ou seja, sacrificar a correção atual das digitações de uso como uma função não é aceitável (em parte porque isso seria um regressão severa e em parte porque os decoradores são considerados "experimentais" e, portanto, afirmo, secundários ao uso de função padrão).

Não sei se uma solução completa para o problema de digitação do decorador é possível, enquanto https://github.com/Microsoft/TypeScript/issues/4881 é excelente. Dito isso, há melhorias incrementais neste caso, como (primeira passagem) gerando qualquer tipo de React.ComponentClass para que o código pelo menos compile, então (segunda passagem) gerando um componente que aceita a interseção de todos os props (próprios e conectados), mesmo que isso seja muito tolerante, então o tipo de código verifica pelo menos alguns dos props.

A única solução que tenho agora é não usar connect como decorador. Alguns podem achar que é feio estilisticamente, mas ele verifica a digitação corretamente, não é mais longo e pode ser usado em mais lugares do que um decorador.

Ao invés de:

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

use (algo parecido com):

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

Observe que https://github.com/DefinitelyTyped/DefinitelyTyped/issues/8787 entra em jogo aqui e às vezes é confundido com esse problema. Você também pode querer uma dica de tipo ou uma conversão para certificar-se de que o componente de saída tem o tipo apropriado.

Editar: https://github.com/Microsoft/TypeScript/pull/12114 pode ajudar neste caso: o decorador poderia ser escrito para criar uma versão totalmente opcional dos adereços para o componente gerado, o que não é totalmente ideal mas continua a permitir que sua implementação seja mais assertiva sobre a nulidade da propriedade.

Comentários muito úteis

Para usar @connect diretamente (sem apresentar decoradores personalizados), uso a seguinte solução alternativa:

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

Mas eu concordo totalmente com @seansfkelley que qualquer tipo de digitação realmente não é o que queremos ...

Todos 32 comentários

Eu estava tendo problemas semelhantes outro dia e deixei cair para a função de conexão. Os tipos pareciam de lado e usando velhos tipos de reação. Posso tentar consertar isso e enviar um PR?

Claro! Muita coisa mudou desde que esses tipos foram originalmente escritos, embora eu ainda seja cético de que, sem o suporte do decorador mutante, algo útil seja possível. Mas eu já trabalhei em outras inadequações no sistema de tipos antes, então tente.

Talvez os tipos mapeados possam levar você a parte do caminho, então pelo menos você tem _algumas_ informações de tipo úteis na saída?

YOLO

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

Não acho que haja muito sentido em yolo-digitar isso, porque as soluções alternativas listadas aqui e em # 8787, especificamente aquela sobre como usar as dicas para a chamada de função, não são tão ruins (especialmente com modelos IDE para classes de componentes) e ter um componente com qualquer tipo é triste. :( Qualquer conexão de digitação apenas para usá-lo como um decorador em vez de uma chamada de função é realmente colocar o carrinho na frente dos bois.

Para usar @connect diretamente (sem apresentar decoradores personalizados), uso a seguinte solução alternativa:

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

Mas eu concordo totalmente com @seansfkelley que qualquer tipo de digitação realmente não é o que queremos ...

Apesar da sintaxe do decorador ser legal, eu ainda acho que usar HOC como conectar de uma forma padrão é uma prática melhor.

1) Modifica o tipo de classe e vai contra as especificações do decorador.
2) O Connect não é um decorador, é um HOC.
3) Ele quebra a portabilidade do código por ter uma sintaxe diferente para componentes sem estado e com estado.

Então, eu gostaria de dizer por favor, não forneça uma definição que permita que o desenvolvedor use da maneira errada :)

@npirotte Agree. Você está absolutamente certo, usar HOCs como decoradores viola as especificações dos decoradores. Não é mais a classe base, mas sim uma classe completamente diferente.

Estou tendo exatamente os mesmos problemas com decoradores personalizados e olhando para o estado de seu suporte no TS (eles ainda estão atrás da bandeira e AFAICS, não há planos para habilitá-los por padrão). Estou prestes a encerrar o suporte para eles. Além disso, os decoradores ainda estão em projeto de w3c.

A melhor maneira de unir um componente a um novo é usar uma função de ordem superior para ele em vez de tentar _decorar_ a classe existente.

Tenho o mesmo erro se o importar. No entanto, o seguinte funciona para mim:

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

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

@TriStarGod que depende de como você digitou require . Parece provável que você termine com connect digitado como any nesse caso.

Outra solução / solução alternativa.

Como já tenho meu próprio estado de aplicativo, preciso de uma função muito curta na forma 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> {
...
}

E aqui está aquele método de conexão empacotado:

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
}

Obrigado pelo seu trabalho. Inscrevendo-se e aguardando o progresso desse problema.

@offbeatful forneça a declaração de tipo de outra variação de mapDispatchToProps

Se um objeto é passado, cada função dentro dele é considerada um criador de ação Redux. Um objeto com os mesmos nomes de função, mas com cada criador de ação envolvido em uma chamada de despacho para que possam ser chamados diretamente, será mesclado nos props do componente.

outra solução alternativa com base no comentário @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;

Atualizado com base no snippet @pravdomil e nos tipos mais recentes (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;

Ei @offbeatful (cc: @pravdomil, eu acho?), Recentemente fiz uma pergunta sobre Stack Overflow relacionada a esse problema, onde foi concluído que isso não é compatível no momento. Como parte dessa pergunta, preparei um repositório para mostrar o que tentei. Concluiu-se que atualmente não há suporte para isso, então fiquei animado para ver seu snippet de código hoje e tentei atualizar meu repositório para usá-lo.

Aqui você pode ver o commit com seu snippet de código integrado

Isso não grita mais com os erros que eu recebia antes, mas descobri que no meu caso de uso em que meu componente está conectado, mas também tem seus próprios adereços, esta nova assinatura agora fará com que até mesmo os adereços de estado sejam necessários como próprios adereços (TSX). cd my-app && yarn start no meu repositório mostrará o que quero dizer.

Você acha que isso também pode ser resolvido ou não é possível?

você pode estar usando esta versão connect

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

em caso afirmativo, você só precisa corrigir o parâmetro options para ser Options<any, any, any> dessa forma você não terá o problema de TStateProps & TDispatchProps porque será TStateProps & any

em poucas palavras:

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

legal como você

@TomasHubelbauer Apenas tentei seu método, se você voltar para o método padrão de conexão redux
vai funcionar de maneira bastante estranha.

É melhor garantir que você use tipos compartilhados entre os três:

export type CounterStateProps = {
    count: number;
};

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

export type CounterOwnProps = {
    initialCount: number;
};

export type CounterProps = CounterStateProps & CounterDispatchProps & CounterOwnProps;

Em seguida, implemente o componente com estado

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

E, finalmente, faça a classe do conector usando redux's build in connect NÃO o código de conexão customizado.

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 Sim, eu sei que funciona, no meu exemplo mostrei que antes tentei algumas das sugestões para fazer funcionar como decorador. Eu uso a variante não decorador com sucesso, mas eu realmente gostaria de fazer o decorador funcionar também.

Além disso, acho que você misturou seus tipos, você usa CounterStateProps (que é o tipo de retorno de mapDispatchToProps como um componente de CounterProps (o que está bem, ele tem estado Redux props, Redux dispatch props e JSX próprios props), mas também como um tipo para o estado dos componentes. Em vez disso, state deve ter seu próprio tipo, que não está envolvido na assinatura de tipo do componente externo de forma alguma.

É possível que eu não tenha entendido sua solução completamente, então se você puder fazer isso funcionar no meu repositório (onde eu uso os próprios props em TSX e os props de estado Redux) sem obter um erro dizendo que você precisa especificar os props de estado Redux em TSX , também, isso seria ótimo!

+1 neste assunto

Eu atualizei o snippet de @offbeatful e estou usando-o com sucesso agora.

connect.ts ( IAppState é a interface do estado 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

Então, meu componente conectado fica assim:

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>

O casting direto MyComponent as React.ComponentType<IOwnProps> falhará, então eu digitei any primeiro. Eu sei que é um hack, mas funciona bem para o pai e para o próprio componente também.

Esta é a solução mais viável, mas ainda restritiva, que consegui propor.

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

qualquer notícia?

@ctretyak não, eventualmente você terá que criar um arquivo de declaração e modificar o decorador. Parece que o React Eco e o TS não são os melhores amigos, por isso mudei disso.

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

esse tipo de importação permite que o IDE mostre adereços e oculte o erro ts. um pouco feio, mas decoradores fica melhor do que uma conexão simples. especialmente se você configurar para adicionar algum seletor automaticamente, como tradução ou navegação

Não é uma questão de reação-redux. Tenho o mesmo comportamento com @withRouter (da React-Router) e @graphql

O texto datilografado parece não entender que um decorador pode injetar adereços ...

O texto datilografado parece não entender que um decorador pode injetar adereços ...

Ugh, isso ainda é um problema? Apenas corri para isso.

Estou triste por algo estar destruindo nosso cérebro, mas eles foram projetados para nos deixar mais confortáveis ​​:(

Estou triste por algo estar destruindo nosso cérebro, mas eles foram projetados para nos deixar mais confortáveis ​​:(

eu também :(

Isso provavelmente não cobre todos os casos de uso, mas tem funcionado bem para mim.
Também me dá a chance de adicionar meu tipo de loja (MyAppStore) em um só lugar.

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

você tem alguma atualização?

Eu gostaria de usar o Connect como decorador também.

Todas as soluções oficiais fornecidas ??
Acho que muitos desenvolvedores preferem usar @connect em vez de conectar (mapStateToProps, mapDispatchToProps) (componentes)

Esta página foi útil?
0 / 5 - 0 avaliações