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);
即可解决上述问题。