如何正确键入Redux连接呼叫?

Luk*_*ach 4 connect typescript reactjs redux

我正在尝试将Redux状态存储与TypeScript结合使用。我正在尝试使用Redux的官方Typings,并希望使connect方法(将mapStatetoPropsmapDispatchToProps与组件连接在一起)上的整个调用类型安全。

我通常会看到一些方法,这些方法mapStatetoProps和方法mapDispatchToProps只是自定义类型的,并返回部分props的一部分,例如:

function mapStateToProps(state: IStateStore, ownProps: Partial<IComponentProps>)
  : Partial<IProjectEditorProps> {}
function mapDispatchToProps (dispatch: Dispatch, ownProps: Partial<IComponentProps>)
  : Partial<IProjectEditorProps> {}
Run Code Online (Sandbox Code Playgroud)

这是可以键入的并且可以工作,但是并不是真正安全的,因为可以实例化缺少props的组件,因为Partial接口的使用允许不完整的定义。然而,部分接口需要在这里,因为你可能希望定义一些道具mapStateToProps和一些中mapDispatchToProps,并不是所有的功能于一体。所以这就是为什么我要避免这种风格。

我目前要使用的是直接将函数嵌入到connect调用中,并使用redux提供的通用类型键入connect调用:

connect<IComponentProps, any, any, IStateStore>(
  (state, ownProps) => ({
    /* some props supplied by redux state */
  }),
  dispatch => ({
    /* some more props supplied by dispatch calls */
  })
)(Component);
Run Code Online (Sandbox Code Playgroud)

然而,这也引发了嵌入式的错误 mapStatetoProps,并mapDispatchToProps呼吁没有规定所有的道具既是只需要其中的一个子集,但合在一起定义所有道具。

如果两种方法定义的组合值提供了所有必需的道具,而又没有立即定义所有道具的方法之一,那么如何正确键入connect调用,使 mapStatetoPropsand mapDispatchToProps调用真正具有类型安全性并进行类型检查?我的方法有可能吗?

NSj*_*nas 5

选项1:拆分 IComponentProps

最简单的方法可能是为“状态派生道具”,“自己的道具”和“调度道具”定义单独的接口,然后使用交集类型将它们连接在一起以实现IComponentProps

import * as React from 'react';
import { connect, Dispatch } from 'react-redux'
import { IStateStore } from '@src/reducers';


interface IComponentOwnProps {
  foo: string;
}

interface IComponentStoreProps {
  bar: string;
}

interface IComponentDispatchProps {
  fooAction: () => void;
}

type IComponentProps = IComponentOwnProps & IComponentStoreProps & IComponentDispatchProps

class IComponent extends React.Component<IComponentProps, never> {
  public render() {
    return (
      <div>
        foo: {this.props.foo}
        bar: {this.props.bar}
        <button onClick={this.props.fooAction}>Do Foo</button>
      </div>
    );
  }
}

export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
  (state, ownProps): IComponentStoreProps => {
    return {
      bar: state.bar + ownProps.foo
    };
  },
  (dispatch: Dispatch<IStateStore>): IComponentDispatchProps => (
    {
      fooAction: () => dispatch({type:'FOO_ACTION'})
    }
  )
)(IComponent);
Run Code Online (Sandbox Code Playgroud)

我们可以这样设置连接函数的通用参数: <TStateProps, TDispatchProps, TOwnProps, State>

选项2:让您的函数定义Props接口

我在野外看到的另一种选择是利用ReturnType映射类型,以允许您的mapX2Props函数实际定义它们所起的作用IComponentProps

type IComponentProps = IComponentOwnProps & IComponentStoreProps & IComponentDispatchProps;

interface IComponentOwnProps {
  foo: string;
}

type IComponentStoreProps = ReturnType<typeof mapStateToProps>;
type IComponentDispatchProps = ReturnType<typeof mapDispatchToProps>;

class IComponent extends React.Component<IComponentProps, never> {
  //...
}


function mapStateToProps(state: IStateStore, ownProps: IComponentOwnProps) {
  return {
    bar: state.bar + ownProps.foo,
  };
}

function mapDispatchToProps(dispatch: Dispatch<IStateStore>) {
  return {
    fooAction: () => dispatch({ type: 'FOO_ACTION' })
  };
}

export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
  mapStateToProps,
  mapDispatchToProps
)(IComponent);
Run Code Online (Sandbox Code Playgroud)

此处的最大优点是,它减少了一些样板并使其成为现实,因此当您添加新的映射道具时,只有一个地方可以更新。

我一直避开ReturnType简化,因为让您的实现定义您的编程接口“合同”(IMO)让人感到后退。以您不想要的方式更改您的内容几乎变得太容易IComponentProps

但是,由于这里的所有内容都是相当独立的,因此它可能是一个可以接受的用例。