TypeScript struggles with Redux containers

后端 未结 3 1123
生来不讨喜
生来不讨喜 2020-12-29 06:05

I\'m having some trouble figuring out how to properly type Redux containers.

Consider a simple presentational component that might look like this:

in         


        
相关标签:
3条回答
  • 2020-12-29 06:52
    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);
    

    You can use something use called Intersection Types https://www.typescriptlang.org/docs/handbook/advanced-types.html#intersection-types

    0 讨论(0)
  • 2020-12-29 07:02

    You're on the right track with your last example. What you also need to define is a MyOwnProps interface, and type the connect function.

    With these typings: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/react-redux/react-redux.d.ts, you can do something like this

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

    This also lets you type ownProps in mapStateToProps and mapDispatchToProps, e.g.

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

    You don't need to define an intersection type for MyProps type as long as you define dispatch, state, and ownProps types. That way you can keep MyProps in its own place and let containers, or places where the component is used without containers, apply the props as they need to. I guess this is up to you and your use case - if you define MyProps = MyStateProps & MyDispatchProps & MyOwnProps, it's tied to one specific container, which is less flexible (but less verbose).

    As a solution, it is pretty verbose, but I don't think there's any way of getting around telling TypeScript that the different pieces of required props will be assembled in different places, and connect will tie them together.

    Also, for what it's worth, I have typically gone with optional props, for simplicity's sake, so I don't have much in the way of experience to share on using this approach.

    0 讨论(0)
  • 2020-12-29 07:05

    I just use Partial<MyProps>, where Partial is a built-in TypeScript type defined as:

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

    It takes an interface and makes every property in it optional.

    Here's an example of a presentational/Redux-aware component pair I've written:

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

    The presentational component props are non-optional - exactly as required for the presentation without any bearing on the corresponding "smart" component.

    Meanwhile, mapStateToProps and mapDispatchToProps will allow me to assign a subset of the required presentational props in each function, while flagging any props not defined in the presentational props interface as an error.

    0 讨论(0)
提交回复
热议问题