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

时间:2020-07-10 11:56:50

标签: reactjs typescript react-redux react-router react-router-v4

我有以下堆栈:

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

我遵循了this answer中建议的方法,该方法涉及如何对由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将组件链接到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组件?

1 个答案:

答案 0 :(得分:1)

从不

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

道具是never,因为PropsFromReduxnevernever的并集总是never的结合)。

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

如上所示,ConnectedProps实用程序类型返回InferableComponentEnhancerWithProps的第一个泛型。

PropsFromRedux(即ConnectedProps<typeof connector>的值为never,因为connected的类型为InferableComponentEnhancerWithProps<never, {}>

因此回溯一下,我们发现核心问题是connected的类型。

void

您实际上在mapStateToProps函数中犯了一个错误,因为您忘记将其包装在括号中,因此实际上并没有返回任何内容!仅此一项并不能解决您的问题(仅将never替换为void),但这是其他解决方案的前提。

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

现在,它返回{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中提取哪些道具,因此我们可以直接输入它们。

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

Typescript Playground Link