reactjs 如何正确输入Redux连接调用?

p8ekf7hl  于 12个月前  发布在  React
关注(0)|答案(3)|浏览(66)

我正在尝试将Redux状态存储与TypeScript结合使用。我正在尝试使用Redux的官方类型,并希望对connect方法(将mapStatetoPropsmapDispatchToProps与组件连接起来)进行类型安全的整个调用。
我通常看到的方法mapStatetoPropsmapDispatchToProps只是自定义类型的,并返回组件props的一部分,如下所示:

function mapStateToProps(state: IStateStore, ownProps: Partial<IComponentProps>)
  : Partial<IProjectEditorProps> {}
function mapDispatchToProps (dispatch: Dispatch, ownProps: Partial<IComponentProps>)
  : Partial<IProjectEditorProps> {}

这是类型化的,可以工作,但并不真正安全,因为它可能会示例化一个缺少属性的组件,因为Partial接口的使用允许不完整的定义。但是,Partial接口在这里是必需的,因为您可能希望在mapStateToPropsmapDispatchToProps中定义一些props,而不是在一个函数中定义所有props。这就是为什么我想避免这种风格。
我目前尝试使用的是直接将函数嵌入到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);

然而,这也抛出了错误,嵌入式mapStatetoPropsmapDispatchToProps调用不定义 all Props each,因为两者都只需要它们的子集,而是一起定义所有Props。
我如何正确地键入connect调用,以便mapStatetoPropsmapDispatchToProps调用是真正类型安全的,并且键入检查两个方法定义的组合值是否提供了所有必需的props,而无需一次定义所有props所需的方法之一?我的方法有可能吗?

6vl6ewon

6vl6ewon1#

选项一:拆分IComponentProps

最简单的方法可能就是为“state derived props”、“own props”和“dispatch props”定义单独的接口,然后使用交集类型将它们连接到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);

我们可以这样设置connect函数的泛型参数:<TStateProps, TDispatchProps, TOwnProps, State>

选项二:让你的函数定义你的Props接口

另一个我见过的方法是利用ReturnType mapped type来允许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);

这里最大的优点是,它减少了一点锅炉板,使它只有一个地方更新时,你添加一个新的Map prop 。
我总是避开ReturnType,simplify,因为让你的实现定义你的编程接口“契约”(IMO)感觉很倒退。以一种您不希望的方式更改您的IComponentProps变得几乎太容易了。
但是,由于这里的所有内容都是自包含的,因此它可能是一个可接受的用例。

ebdffaop

ebdffaop2#

一种解决方案是将组件属性拆分为state-、dispatch-和own-属性:

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

import { deleteItem } from "./action";
import { getItemById } from "./selectors";

interface StateProps {
  title: string;
}

interface DispatchProps {
  onDelete: () => any;
}

interface OwnProps {
  id: string;
}

export type SampleItemProps = StateProps & DispatchProps & OwnProps;

export const SampleItem: React.SFC<SampleItemProps> = props => (
  <div>
    <div>{props.title}</div>
    <button onClick={props.onDelete}>Delete</button>
  </div>
);

// You can either use an explicit mapStateToProps...
const mapStateToProps = (state: RootState, ownProps: OwnProps) : StateProps => ({
  title: getItemById(state, ownProps.id)
});

// Ommitted mapDispatchToProps...

// ... and infer the types from connects arguments ...
export default connect(mapStateToProps, mapDispatchToProps)(SampleItem);

// ... or explicitly type connect and "inline" map*To*.
export default connect<StateProps, DispatchProps, OwnProps, RootState>(
  (state, ownProps) => ({
    title: getItemById(state, ownProps.id)
  }),
  (dispatch, ownProps) => ({
    onDelete: () => dispatch(deleteItem(ownProps.id))
  })
)(SampleItem);
eaf3rand

eaf3rand3#

非常喜欢@NSjonas的拆分方法,但我也想从他的第二种方法中借鉴一些东西,以便在实用性之间取得平衡,不要让实现完全定义接口,也不要在输入分派操作时过于冗长;

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

interface IComponentOwnProps {
  foo: string;
}

interface IComponentStoreProps {
  bar: string;
}

interface IComponentDispatchProps {
  doFoo: (...args: Parameters<typeof 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.doFoo}>Do Foo</button>
      </div>
    );
  }
}

export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
  (state, ownProps): IComponentStoreProps => {
    return {
      bar: state.bar + ownProps.foo
    };
  },
  {
      doFoo: fooAction
  }
)(IComponent);

相关问题