代码之家  ›  专栏  ›  技术社区  ›  ipmcc

在使用redux连接的React组件时,如何保持类型安全?

  •  1
  • ipmcc  · 技术社区  · 7 年前

    connect 对于一个组件,如果不进行强制转换,就不可能明智地使用它,因为连接过程似乎无法向类型系统传递连接操作已经满足了部分或全部属性要求。例如,考虑以下组件/类型/等:

    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> 并为任何属性创建其他类型 填表人 连接 操作。演员阵容显然很有效,但现在你有三套东西要彼此保持同步(原来的道具类型 mapStateToProps mapDispatchToProps 这种方法感觉冗长,容易出错,而且它还删除了其他可能有用的类型信息。

    有没有更好的管理方法 连接

    2 回复  |  直到 7 年前
        1
  •  1
  •   Matt McCutchen    7 年前

    connect (已命名) TOwnProps 在声明中)应该是 mapStateToProps mapDispatchToProps 功能本身。自从你的 MapStateTops公司 地图调度程序 函数不使用任何道具,可以将此类型参数设置为 {} FooProps ,然后错误消失(删除显式类型参数并依赖于推断将得到相同的最终结果。)

        2
  •  0
  •   ipmcc    7 年前

    经过进一步的挖掘,我想我已经弄明白了。以问题所示的形式(注意,有12种可能的调用/类型模式) connect 连接 . 它们似乎代表:

    1. t状态道具 mapStateToProps 参数到 连接 MapStateTops公司
    2. mapDispatchToProps 参数到 . (顺便说一下,它也是
    3. 城镇道具 新的 组件生成的 呼叫(这是我困惑的地方)TOwnProps是 ownProps 参数到 MapStateTops公司 地图调度程序 .
    4. -Redux存储模型根的类型。

    3号电话 TOwnProps 自己的道具 MapStateTops公司 地图调度程序 功能。我从来没有机会使用 在实践中,所以我并没有立即明白,其实还有更多的。再多挖点,我就发现了 连接 退货:

    InferableComponentEnhancerWithProps<TStateProps & TDispatchProps, TOwnProps>
    

    它的定义是:

    export interface InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> {
        <C extends ComponentType<Matching<TInjectedProps, GetProps<C>>>>(
            component: C
        ): ConnectedComponentClass<C, Omit<GetProps<C>, keyof Shared<TInjectedProps, GetProps<C>>> & TNeedsProps>
    }
    

    看到 TStateProps & TDispatchProps TInjectedProps 城镇道具 称为 TNeedsProps TStateProps和;t喷洒道具 属性是否被“注入”到包装的组件中 连接 ,和 包装器仍然“需要”连接组件的使用者提供的属性。

    我的另一个认识是我在问题中的方式(即。 connect<FooDataProps, FooDispatchProps, FooProps, Model>(mapStateToProps, mapDispatchToProps) )毫无意义,因为如果第三个类型参数(语义上)应该表示“所有props、state或dispatch”,那么类型系统就可以通过 & 惯性导航与制导 FooDataProps FooDispatchProps 它得到了param#1和param#2。当我使用第三个类型参数时,它不会传递新的信息。

    Matt McCutchen's answer ,虽然有帮助,但只关注 城镇道具 关于 自己的道具 参数 功能。他正确地指出我不使用 自己的道具 参数,并建议我通过 {} . 这个答案在它描述的上下文中似乎是正确的,但它忽略了 另一个角色是决定什么样的道具能被高阶接受 连接 ed组件。

    这里的总结是 城镇道具 自己的道具 参数,但是 作为一种类型,它捕获要由包装/连接组件的使用者设置的剩余属性。