键入同时使用 withRouter 和 connect 的 HOC 组件

typing a HOC component that's using both withRouter and connect

我有以下堆栈:

react@16.13.1
react-dom@16.13.1
react-redux@7.2.0
react-router-dom@5.2.0

我按照 中建议的方法对如何对使用 withRouter 创建的 HOC 组件进行类型检查进行了操作,并且工作正常。例如。以下组件类型检查:

import React from "react";
import { withRouter } from "react-router-dom";
import { RouteComponentProps } from "react-router-dom";

type Props = RouteComponentProps<any>;

class Test extends React.Component<Props, {}> {
  componentDidMount = () => {
    this.props.history.push("foo");
  };

  render() {
    return <div>foo</div>;
  }
}

export default withRouter(Test);

当我还希望使用 connect link 组件到 Redux 存储时,问题就出现了。以下代码无法进行类型检查:

import React from "react";
import { connect, ConnectedProps } from "react-redux";

import { withRouter } from "react-router-dom";

import { RouteComponentProps } from "react-router-dom";

type RootState = { foo: number };

const mapStateToProps = (state: RootState) => {
  foo: state.foo;
};

const connector = connect(mapStateToProps, null);

type PropsFromRedux = ConnectedProps<typeof connector>;

type Props = PropsFromRedux & RouteComponentProps<any>;

class Test extends React.Component<Props, {}> {
  componentDidMount = () => {
    this.props.history.push("foo"); // TS2339: Property 'history' does not exist on type 'never'.
  };

  render() {
    return <div>foo</div>;
  }
}

export default withRouter(connector(Test));

具体来说,我得到:

TS2339: Property 'history' does not exist on type 'never'.

两个问题:

  1. 为什么props的类型被评估为never
  2. 如何正确地对通过连续应用 withRouterconnect 创建的 HOC 组件进行类型检查?

从不

如果您开始将鼠标悬停在一些道具上,您会看到有很多 never 正在进行。

Props 是 never 因为 PropsFromReduxnevernever 和任何东西的联合总是 never)。

type ConnectedProps<TConnector> = TConnector extends InferableComponentEnhancerWithProps<infer TInjectedProps, any> ? TInjectedProps : never

如上所示,ConnectedProps 实用程序类型 returns 第一个泛型 InferableComponentEnhancerWithProps

PropsFromRedux,即 ConnectedProps<typeof connector> 的计算结果为 never,因为 connected 的类型是 InferableComponentEnhancerWithProps<never, {}>

所以回溯,核心问题是connected的类型。

无效

您实际上在 mapStateToProps 函数中犯了一个错误,因为您忘记将它括在括号中,所以您实际上并没有返回任何东西!这本身并不能解决您的问题(它只是将 never 替换为 void),但它是其他修复的先决条件。

const mapStateToProps = (state: RootState) => ({
  foo: state.foo;
});

现在 returns {foo: number} 而不是 void.

解决方案 1

connected 的推断类型出错的原因是您将 dispatch 参数设置为 nullconnect 函数有 14 different overloads,但唯一允许第二个参数为 null 的是设置第三个或第四个参数的那些。如果您不需要设置第二个 dispatch 参数,则应完全将其关闭。

当我们去掉 null 参数时,突然间一切都好了,this.props.history 变成了类型 History<any>.

const connector = connect(mapStateToProps);
// => InferableComponentEnhancerWithProps<{foo: number} & DispatchProp<AnyAction>, {}>

type PropsFromRedux = ConnectedProps<typeof connector>;
// => {foo: number} & DispatchProp<AnyAction>

type Props = PropsFromRedux & RouteComponentProps<any>;
// => {foo: number} & DispatchProp<AnyAction> & RouteComponentProps<any, StaticContext, any>

解决方案 2

我喜欢我的 typesinterfaces 独立于我的代码,所以我不喜欢 typeof connector。我们知道我们想要从 redux 中提取什么 props,所以我们可以直接输入它们。

type PropsFromRedux = {
  foo: number;
  dispatch: Dispatch;
}

Typescript Playground Link