【问题标题】:TypeScript struggles with Redux containersTypeScript 与 Redux 容器的斗争
【发布时间】:2017-04-02 00:04:56
【问题描述】:

我在弄清楚如何正确输入 Redux containers 时遇到了一些麻烦。

考虑一个可能如下所示的简单展示组件:

interface MyProps {
  name: string;
  selected: boolean;
  onSelect: (name: string) => void;
}
class MyComponent extends React.Component<MyProps, {}> { }

从这个组件的角度来看,所有的道具都是必需的。

现在我想编写一个容器,将所有这些道具拉出状态:

function mapStateToProps(state: MyState) {
  return {
    name: state.my.name,
    selected: state.my.selected
  };
}

function mapDispatchToProps(dispatch: IDispatch) {
  return {
    onSelect(name: string) {
      dispatch(mySelectAction(name));
    }
  };
}

const MyContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent);

这可行,但有一个大的打字问题:映射函数(mapStateToPropsmapDispatchToProps)没有保护它们提供正确的数据来满足MyProps。这很容易出现错误、拼写错误和糟糕的重构。

我可以让映射函数返回类型MyProps

function mapStateToProps(state: MyState): MyProps { }

function mapDispatchToProps(dispatch: IDispatch): MyProps { }

但是,除非我将所有 MyProp 属性设为可选,否则这不起作用,以便每个映射函数只能返回他们关心的部分。我不想让道具成为可选的,因为它们对于展示组件来说不是可选的。

另一种选择是拆分每个地图功能的道具并将它们组合为组件道具:

// MyComponent
interface MyStateProps {
  name: string;
  selected: boolean;
}

interface MyDispatchProps {
  onSelect: (name: string) => void;
}

type MyProps = MyStateProps & MyDispatchProps;

class MyComponent extends React.Component<MyProps, {}> { }

// MyContainer
function mapStateToProps(state: MyState): MyStateProps { }

function mapDispatchToProps(dispatch: IDispatch): MyDispatchProps { }

好的,这让我更接近我想要的,但它有点吵,它让我围绕容器的形状编写我的演示组件道具界面,我不喜欢。

现在出现了第二个问题。如果我想将容器放在另一个组件中怎么办:

<MyContainer />

这会产生 nameselectedonSelect 都丢失的编译错误...但这是故意的,因为容器正在连接到 Redux 并提供这些错误。所以这促使我回到让所有组件道具成为可选的,但我不喜欢这样,因为它们并不是真正的可选。

MyContainer 有一些自己想要传递的道具时,事情会变得更糟:

<MyContainer section="somethng" />

现在我要做的是让sectionMyContainer 的必需道具,但不是MyComponent 的道具,而nameselectedonSelect 是@ 的必需道具987654343@ 但可选或根本不是 MyContainer 的道具。我完全不知道该怎么表达。

对此的任何指导将不胜感激!

【问题讨论】:

    标签: reactjs typescript redux react-redux


    【解决方案1】:

    您在上一个示例中走在正确的轨道上。您还需要定义一个MyOwnProps 接口,并键入connect 函数。

    使用这些类型:https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/react-redux/react-redux.d.ts,你可以做这样的事情

    interface MyProps {
      section: string;
      name: string;
      selected: boolean;
      onSelect: (name: string) => void;
    }
    
    interface MyStateProps {
      name: string;
      selected: boolean;
    }
    
    interface MyDispatchProps {
      onSelect: (name: string) => void;
    }
    
    interface MyOwnProps {
      section: string;
    }
    
    class MyComponent extends React.Component<MyProps, {}> { }
    
    function mapStateToProps(state: MyState): MyStateProps { }
    
    function mapDispatchToProps(dispatch: IDispatch): MyDispatchProps { }
    
    const MyContainer = connect<MyStateProps, MyDispatchProps, MyOwnProps>(
      mapStateToProps,
      mapDispatchToProps
    )(MyComponent);
    

    这也可以让您在mapStateToPropsmapDispatchToProps 中输入ownProps,例如

    function mapStateToProps(state: MyState, ownProps: MyOwnProps): MyStateProps
    

    你不需要为 MyProps 类型定义交集类型,只要你定义了 dispatch、state 和 ownProps 类型。这样,您可以将 MyProps 保留在自己的位置,并让容器或在没有容器的情况下使用组件的地方根据需要应用道具。我想这取决于您和您的用例 - 如果您定义 MyProps = MyStateProps &amp; MyDispatchProps &amp; MyOwnProps,它会绑定到一个特定的容器,该容器不太灵活(但不太冗长)。

    作为一种解决方案,它非常冗长,但我认为没有任何方法可以绕过告诉 TypeScript 所需的不同道具将在不同的地方组装,connect 会将它们捆绑在一起。

    另外,为了它的价值,为了简单起见,我通常使用可选的道具,所以我没有太多的经验可以分享使用这种方法。

    【讨论】:

    • 谢谢,这有帮助。我想如果没有 TypeScript,一切都是可选的。我只是喜欢组件道具可以告诉你什么是预期的,什么是可选的。
    • 问题:因为这个解决方案基本上意味着你定义每个道具两次(一次在MyProps,一次在适当的MyStatePropsMyDispatchProps),如果你重命名或更改道具会发生什么在MyProps,例如nameidselected: booleanselected: SelectionEnum?类型检查器是否意识到MyOwnProps, MyDispatchProps, MyStateProps 不再满足MyProps
    • 有点。自从写下答案以来,我一直在玩弄它。 State 和 Dispatch 道具必须匹配,但 OwnProps 不必匹配。我想这可能是因为可以仅在 mapStateToProps 和 mapDispatchToProps 中使用 ownProps 而在 MyComponent 中不使用它们?
    • 错误类似于(123,3): error TS2345: Argument of type 'typeof FeatureTab' is not assignable to parameter of type 'ComponentClass&lt;IStateToProps &amp; IDispatchToProps&gt; | StatelessComponent&lt;IStateToProps &amp; IDispatchTo...'...
    • 哈,我想我不知道那个错误在说什么......它会渗透到像'name' exists in {...} but not in {...} 这样有用的东西吗?
    【解决方案2】:

    我只使用Partial&lt;MyProps&gt;,其中Partial 是一个内置的TypeScript 类型,定义为:

    type Partial<T> = {
        [P in keyof T]?: T[P];
    }
    

    它接受一个接口,并使其中的每个属性都是可选的。

    这是我编写的演示/Redux 感知组件对的示例:

    /components/ConnectionPane/ConnectionPane.tsx

    export interface IConnectionPaneProps {
      connecting: boolean;
      connected: boolean;
      onConnect: (hostname: string, port: number) => void;
    }
    
    interface IState {
      hostname: string;
      port?: number;
    }
    
    export default class ConnectionPane extends React.Component<IConnectionPaneProps, IState> {
       ...
    }
    

    /containers/ConnectionPane/ConnectionPane.ts

    import {connect} from 'react-redux';
    import {connectionSelectors as selectors} from '../../../state/ducks';
    import {connect as connectToTestEcho} from '../../../state/ducks/connection';
    import {ConnectionPane, IConnectionPaneProps} from '../../components';
    
    function mapStateToProps (state): Partial<IConnectionPaneProps> {
      return {
        connecting: selectors.isConnecting(state),
        connected: selectors.isConnected(state)
      };
    }
    
    function mapDispatchToProps (dispatch): Partial<IConnectionPaneProps> {
      return {
        onConnect: (hostname, port) => dispatch(connectToTestEcho(hostname, port))
      };
    }
    
    export default connect(mapStateToProps, mapDispatchToProps)(ConnectionPane) as any;
    

    展示组件道具是非可选的 - 完全符合展示的要求,与相应的“智能”组件没有任何关系。

    同时,mapStateToPropsmapDispatchToProps 将允许我在每个函数中分配所需的演示道具的子集,同时将演示道具界面中未定义的任何道具标记为错误。

    【讨论】:

    • 是的,自从有了 Partial 支持的 TS 版本以来,我一直在一些项目中使用它。它很容易使用。它不像正式的StatePropsDispatchPropsOwnProps 方法那么严格,因为它不能确保映射函数是完整的,只是它不会返回错误实现的道具。我玩过使用Pick 来撬开单个展示组件道具界面,但这仍然很乏味。
    • 将所有内容设为可选并不是解决方案,因为问题明确提出了反对意见。
    • @wegginho OP 写道:“但是,除非我将所有 MyProp 道具设为可选,否则这不起作用,因此每个映射函数只能返回他们关心的部分。我不'不想让道具 [MyProp] 可选”。我的建议是 使MyProp 成为可选的,或者实际上对演示组件或其界面进行任何更改。它将 MyProp 包装在一个类型中,该类型将其所有属性公开为可选,仅适用于 Redux 类型。这是 IMO 的理想解决方案,因为它允许每个 Redux 函数处理 props 子集,同时防止未知 props。
    • @Tagc 如果您没有传递所需的道具,它会抱怨吗?
    • Wegginho 说得对,这不是我一直在寻找的 理想 解决方案,因为它确实使映射函数的所有道具都是可选的,即映射函数不是确保归还他们应该归还的一切。然而,它比显式地将组件道具全部设为可选要好得多,而且它是一个很好的替代方案,easy 易于使用,所以我赞成它。 :)
    【解决方案3】:
    interface MyStateProps {
        name: string;
        selected: boolean;
    }
    
    interface MyDispatchProps {
        onSelect: (name: string) => void;
    }
    
    interface MyOwnProps {
        section: string;
    }
    
    // Intersection Types
    type MyProps = MyStateProps & MyDispatchProps & MyOwnProps;
    
    
    class MyComponent extends React.Component<MyProps, {}> { }
    
    function mapStateToProps(state: MyState): MyStateProps { }
    
    function mapDispatchToProps(dispatch: IDispatch): MyDispatchProps { }
    
    const MyContainer = connect<MyStateProps, MyDispatchProps, MyOwnProps>(
      mapStateToProps,
      mapDispatchToProps
    )(MyComponent);
    

    你可以使用一种叫做Intersection Types的东西 https://www.typescriptlang.org/docs/handbook/advanced-types.html#intersection-types

    【讨论】:

    • 感谢这个例子。它基本上是我最初的尝试(使用交集类型)和@radio 接受的答案(使用connect() 类型参数)的组合。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-18
    • 2018-05-23
    • 2013-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多