Definitelytyped: react-redux 的 `connect` 不能用作装饰器:类型“不可分配给类型 'void'”

创建于 2016-07-04  ·  32评论  ·  资料来源: DefinitelyTyped/DefinitelyTyped

尝试使用react-redux 的connect作为类装饰器可能会导致Type 'foo' is not assignable to type 'void'形式的错误消息。 这些类型的问题似乎是TypeScript 不允许ClassDecorator s 更改它们装饰的东西的签名,但这是 react-redux 类型的当前实现并且是有目的地完成的,因为react-redux 为 props 返回具有不同签名的不同实例

使用connect作为函数可以正常工作; 只有用作装饰器才会导致类型问题。

欢迎提出建议,但任何提议的解决方案都应该严格改进当前类型可以表达的内容,也就是说,牺牲当前作为函数类型使用的正确性是不可接受的(部分原因是这将是一个严重的回归,部分原因是装饰器被认为是“实验性的”,因此,我断言,次要于标准函数使用)。

我不知道在https://github.com/Microsoft/TypeScript/issues/4881非常出色的情况下,装饰器类型问题的完整解决方案是否可行。 也就是说,在这种情况下有增量改进,例如通过(第一遍)输出任何类型的React.ComponentClass这样代码至少可以编译,然后(第二遍)输出一个接受所有交集的组件props(拥有的和连接的),即使这太宽松了,所以代码类型至少检查一些道具。

我现在唯一的解决方法是不使用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<...> {...}

但我完全同意@seaansfkelley 的观点,即任何类型的输入都不是我们想要的......

所有32条评论

前几天我遇到了类似的问题,并为连接功能放弃了它。 这些类型被忽略并使用旧的反应类型。 我可以尝试解决这个问题并提交 PR?

当然! 自从这些类型最初被编写以来发生了很多变化,尽管我仍然怀疑没有 mutating-decorator 支持一些有用的东西是可能的。 但是我之前已经解决了类型系统中的其他不足之处,所以试一试。

也许映射类型可以让你在那里做一部分,所以至少你在输出中有_一些_有用的类型信息?

优洛

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

我认为 yolo 输入这个没有多大意义,因为这里和#8787 中列出的解决方法,特别是关于使用函数调用提示的方法,还不错(尤其是组件类的 IDE 模板)拥有任何类型的组件是可悲的。 :( Any-typing connect 只是将它用作装饰器而不是函数调用实际上是把车放在马之前。

要直接使用@connect (因此不引入自定义装饰器),我使用以下解决方法:

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

但我完全同意@seaansfkelley 的观点,即任何类型的输入都不是我们想要的......

尽管装饰器语法很酷,但我仍然认为以标准方式使用像连接这样的 HOC 是更好的做法。

1) 它修改了类的类型 a 违反了装饰器规范。
2) Connect 根本不是装饰器,它是 HOC。
3)它通过对无状态和有状态组件使用不同的语法破坏了代码的可移植性。

所以我想说,请不要提供允许开发人员以错误方式使用它的定义:)

@npirotte同意。 你说得对,使用 HOC 作为装饰器违反了装饰器规范。 它不再是基类,而是完全不同的基类。

我遇到了与自定义装饰器完全相同的问题,并查看了它们在 TS 中的支持状态(它们仍然落后于标志和 AFAICS,默认情况下没有启用它们的计划)我即将放弃对 TS 的支持他们。 此外,装饰器仍处于 w3c 草案中。

将组件包装到新组件的最佳方法是为其使用高阶函数,而不是尝试_装饰_现有类。

如果我导入它,我有同样的错误。 但是,以下对我有用:

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

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

@TriStarGod这取决于您如何输入require 。 在这种情况下,您似乎最终将connect键入为any

另一个解决方案/解决方法。

因为我已经有了自己的 App State,所以我需要一些非常简短的函数,形式如下:

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
}

谢谢你的工作。 订阅并等待此问题的进展。

@offbeatful请给出其他变体的类型声明

如果传递了一个对象,则假定其中的每个函数都是 Redux 动作创建者。 具有相同函数名称但每个动作创建者都包含在调度调用中以便可以直接调用它们的对象将合并到组件的 props 中。

基于@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我猜?),我最近问了一个与此问题相关的堆栈溢出问题,得出的结论是目前不支持。 作为该问题的一部分,我准备了一个存储库来展示我的尝试。 结论是目前不支持这个,所以我很高兴今天看到你的代码片段,并继续尝试更新我的存储库以使用它。

在这里您可以看到集成了代码片段的提交

这不再因为我之前遇到的错误而大喊大叫,但我发现在我的用例中,我的组件已连接,但也有自己的道具,这个新签名现在将使它成为甚至需要状态道具自己的 (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 连接方法
它会很奇怪地工作。

最好确保您在三者之间使用共享类型:

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是的,我知道这在我的示例中,我在尝试一些建议以使其作为装饰器工作之前展示了这一点。 我成功地使用了非装饰器变体,但我真的很想让装饰器也能工作。

另外,我想你混淆了你的类型,你可以使用CounterStateProps (这是返回类型mapDispatchToProps作为一个组件CounterProps (这是好的,它具有终极版状态props、Redux dispatch props 和 JSX 自己的 props),但也作为组件状态的类型。相反, state应该有自己的类型,不以任何方式参与外部组件的类型签名。

可能我没有完全理解你的解决方案,所以如果你能在我的存储库中完成这项工作(我在 TSX 中使用自己的道具和 Redux 状态道具)而不会收到错误消息,说你需要在 TSX 中指定 Redux 状态道具也,那太好了!

+1 在这个问题上

我更新了@offbeatful 的片段,

connect.tsIAppState为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

然后我的连接组件看起来像这样:

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 有同样的态度

打字稿似乎不明白装饰者可以注入道具......

打字稿似乎不明白装饰者可以注入道具......

呃,这还是个问题吗? 刚碰到这个。

我为某些东西正在摧毁我们的大脑而感到难过,但它们旨在让我们更舒服:(

我为某些东西正在摧毁我们的大脑而感到难过,但它们旨在让我们更舒服:(

我也是 :(

这可能没有涵盖所有用例,但对我来说效果很好。
它还让我有机会在一个地方添加我的商店类型 (MyAppStore)。

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

你有任何更新吗?

我也想使用 connect 作为装饰器。

提供任何官方解决方案吗???
我认为有很多开发人员更喜欢使用@connect而不是 connect(mapStateToProps,mapDispatchToProps)(components)

此页面是否有帮助?
0 / 5 - 0 等级