代码之家  ›  专栏  ›  技术社区  ›  Lukas Bach

如何正确键入Redux connect调用?

  •  2
  • Lukas Bach  · 技术社区  · 7 年前

    我正在尝试将Redux状态存储与TypeScript结合使用。我正在尝试使用Redux的官方打字,并希望在 connect 方法(用于连接 mapStatetoProps mapDispatchToProps 带有组件)类型安全。

    我通常看到方法 mapStatetoProps mapDispatchToProps 只是自定义类型,并返回部分组件道具,例如:

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

    这是类型化的,可以工作,但并不真正安全,因为可以实例化缺少道具的组件,因为使用部分接口允许不完整的定义。但是,这里需要部分接口,因为您可能需要在中定义一些道具 mapStateToProps 还有一些 mapDispatchToProps ,而不是一个函数中的所有函数。这就是为什么我想避免这种风格。

    连接 使用redux提供的通用类型调用并键入connect调用:

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

    但是,这也会抛出一个错误,即 mapStatetoProps mapDispatchToProps 因为两者都只需要它们的一个子集,但一起定义所有道具。

    如何正确键入connect调用,以便 mapStatetoProps mapDispatchToProps 调用实际上是类型安全的,并且类型检查两个方法定义的组合值是否提供了所有必需的prop,而没有一个方法需要同时定义所有prop?这在我的方法中是可能的吗?

    2 回复  |  直到 7 年前
        1
  •  10
  •   NSjonas    7 年前

    选项1:拆分 IComponentProps

    实现这一点最简单的方法可能就是为“状态派生的道具”、“自己的道具”和“分派道具”定义单独的接口,然后使用 intersection type i组件支柱

    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);
    

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

    选项2:让你的函数定义你的道具界面

    我在野外看到的另一个选择是利用 ReturnType mapped type 允许你的 mapX2Props i组件支柱

    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);
    

    这里最大的优势是,它减少了一点锅炉板,使它成为一个地方,所以你只有一个更新时,你添加一个新的映射道具。

    我总是避开你 ,简化,因为让您的实现定义您的编程接口“契约”(IMO)感觉有些倒退。它几乎变成 改变你的想法 i组件支柱

    然而,由于这里的一切都是非常独立的,所以它可能是一个可以接受的用例。

        2
  •  2
  •   sn42    7 年前

    一种解决方案是将组件属性拆分为状态属性、分派属性和可能的自有属性:

    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);
    
        3
  •  1
  •   karuhanga    6 年前

    非常喜欢@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);