首页 > 解决方案 > 使用 redux 连接的 React 组件时如何保持类型安全?

问题描述

我一直在 React/Redux/Redux-Thunk 项目中使用 TypeScript,并且我一直遇到这个问题,在connecting 一个组件之后,如果不强制转换它似乎不可能明智地使用它,因为连接过程没有似乎无法向类型系统传达连接操作已满足部分或全部属性要求。例如,考虑这些组件/类型/等:

import * as React from 'react';
import {connect} from "react-redux";
import {Action, bindActionCreators, Dispatch} from "redux";
import {ThunkDispatch} from "redux-thunk";

// Our store model
interface Model {
    name: string,
}

// Types for our component's props
interface FooDataProps {
    name: string // Single, required, string property
}

interface FooDispatchProps {
    onClick: React.MouseEventHandler<HTMLButtonElement>, // Single, required, event handler.
}

interface FooProps extends FooDataProps, FooDispatchProps { // Union the two types
}

// Make our first component...
function TrivialComponent(props: FooProps) {
    return (<button onClick={props.onClick}>{props.name}</button>);
}

// Now make a Redux "container" that wires it to the store...
const mapStateToProps = (state: Model): FooDataProps => { return { name: state.name }; };
const mapDispatchToProps = (dispatch: Dispatch): FooDispatchProps => {
    return bindActionCreators({onClick: doStuff}, dispatch);
};

// Wire it up with all the glory of the heavily-genericized `connect`
const ConnectedTrivialComponent = connect<FooDataProps, FooDispatchProps, FooProps, Model>(mapStateToProps, mapDispatchToProps)(TrivialComponent);

// Then let's try to consume it
function ConsumingComponent1() {
    // At this point, I shouldn't need to provide any props to the ConnectedTrivialComponent -- they're 
    // all being provided by the `connect` hookup, but if I try to use the tag like I'm doing here, I 
    // get this error: 
    //
    // Error:(53, 10) TS2322: Type '{}' is not assignable to type 'Readonly<Pick<FooProps, never> & FooProps>'.
    // Property 'name' is missing in type '{}'.
    //
    return (<ConnectedTrivialComponent/>)
}

// If I do something like this:
const ConnectedTrivialComponent2 = ConnectedTrivialComponent as any as React.ComponentClass<{}, {}>;

// Then let's try to consume it
function ConsumingComponent2() {
    // I can do this no problem.
    return (<ConnectedTrivialComponent2/>)
}

// Handler...
const doStuff = (e: React.MouseEvent<HTMLButtonElement>) => (dispatch: ThunkDispatch<Model, void, Action>, getStore: () => Model) => {
    // Do stuff
};

好的,所以,在思考这个问题时,我经历了一些想法:

想法#1)使所有道具都是可选的。我从第三方那里看到的许多组件都是可选的,但根据我的经验,让所有东西都是可选的会导致到处都有大量的样板零检查,并且使代码更难阅读。

想法 #2)为操作填充的任何属性强制转换React.ComponentClass<P,S>并创建其他类型。演员表显然有效,但现在你有三组东西要彼此保持同步(原始的 Props 类型、and列表和“剩余的 Props”类型。)这种方法感觉很冗长,容易出错,并且它还会删除其他可能有用的类型信息。connectmapStateToPropsmapDispatchToProps

有没有更好的方法来管理connected 组件的类型?

标签: reactjstypescriptreduxredux-thunk

解决方案


我的理解是connect(在声明中命名TOwnProps)的第三个类型参数应该是您mapStateToPropsmapDispatchToProps函数本身使用的任何道具的类型。由于您的mapStateToPropsandmapDispatchToProps函数不使用任何道具,因此您可以将此类型参数设置为{},而不是FooProps,然后错误就会消失。(删除显式类型参数并依赖推理将为您提供相同的最终结果。)


推荐阅读