Typescript 下 Redux connect 带有 Props 组件的问题

Typescript 什么辣鸡,类型这么复杂,坑这么多,我绝不用 TS 写一行代码

真香

考虑以下在 Typescript 语法下使用 redux 的代码片段:

import * as React from 'react';
import { connect, Dispatch } from 'react-redux';

import { State } from './types';

interface Props {
    id: string;
}

class MyComponent extends React.Component<Props> {
    constructor(props: Props) {
        super(props);
    }

    render() {
        return (
            <div id={this.props.id}>
                {/* .... */}
            </div>
        );
    }
}

const mapStateToProps = (state: State) => ({
    // ...
});

const mapDispatchToProps = (dispatch: Dispatch) => ({
    // ...
});

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

这是个很简单的把组件和 redux 连接起来的部分代码。以上的代码是可以通过编译的,也可以运行。

现在假如我们有另一个 Component 引入了这个 Component:

import MyComponent from './MyComponent';

// ....
<MyComponent id={'example-id'} />

看起来没毛病啊,MyComponent 确实需要一个叫 id 的参数,也在组件里声明过了,然而 Typescript 会提示编译错误:

类型“IntrinsicAttributes & IntrinsicClassAttributes, ComponentState, any>…”上不存在属性“id”。

这里不是因为编译器瞎了,回看我们的 MyComponent 中,export 的是 connect() 处理过的 HOC 而不是我们原始的组件,而 经过 connect() 之后的组件会把 state 和 dispatch 映射到新组件的 props 里,但这个组件原有 props 的类型就丢掉了。

解决方法之一就是把 id 这个 props 写到 reducer 的 state 中;但有时候为了代码的整洁性,这个 props 只在这个组件中需要并起作用,便没有必要弄到全局的 state 里。事实上,connect() 函数在 Typescript 中也可以指定类型:

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

以上代码等效为:

interface TypeOfState {
    // ...
}

interface TypeOfDispatch {
    // ...
}

export default connect<TypeOfState, TypeOfDispatch, void>(
    mapStateToProps,
    mapDispatchToProps
)(MyComponent);

注意在后者的 connect() 中我们为其指定了 3 个类型,分别是 mapStateToProps() 返回值的类型mapDispatchToProps() 返回值的类型,但最后一个 void 有什么作用?第三个类型实际上指定的就是目标组件原本需要的 Props 的类型。所以我们可以通过改写成如下的代码:

interface TypeOfState {
    // ...
}

interface TypeOfDispatch {
    // ...
}

export default connect<TypeOfState, TypeOfDispatch, Props>(
    mapStateToProps,
    mapDispatchToProps
)(MyComponent);

即可解决上述问题。