【问题标题】:typescript + redux: exclude redux props in parent componenttypescript + redux:在父组件中排除 redux props
【发布时间】:2017-09-16 01:41:32
【问题描述】:

我正在为我当前的 web 应用程序使用 redux 和 typescript。

定义通过@connect 接收redux-actions 的组件的props 以及来自父级的props 的最佳实践是什么? 示例:

// mychild.tsx
export namespace MyChildComponent {

  export interface IProps {
    propertyFromParent: string;
    propertyFromRedux: string;  // !!!! -> This is the problem
    actionsPropertyFromRedux: typeof MyReduxActions;  // !!!! -> And this     
  }
}

@connect(mapStateToProps, mapDispatchToProps)
export class MyChildComponent extends React.Component <MyChildComponent.IProps, any> {

... react stuff

}

function mapStateToProps(state: RootState) {
  return {
    propertyFromRedux: state.propertyFromRedux
  };
}
function mapDispatchToProps(dispatch) {
  return {
    actionsPropertyFromRedux: bindActionCreators(MyReduxActions as any, dispatch)
  };
}




// myparent.tsx
export class MyParentComponent extends React.Component <MyParentComponent.IProps, any> {

... react stuff

    render(){
        // typescript complains, because I am not passing `propertyFromRedux`! 
        return <div><MyChildComponent propertyFromParent="yay" /></div>;
    }

}

在我看来,我有 2 个解决方案。

  1. 只需通过我的整个应用程序传递操作和状态。但这意味着我的整个应用程序会被重新渲染,即使只需要更改一些小的子组件。还是在我的顶级组件中监听所有商店更改的 redux 方式?然后我必须在shouldComponentUpdate 中为非平面对象的道具编写大量逻辑。

  2. IProps 中的参数设置为MyChildComponent 可选,如下所示:

-

// mychild.tsx
export namespace MyChildComponent {

  export interface IProps {
    propertyFromParent: string;
    propertyFromRedux?: typeof MyAction;  // This is the problem
  }
}

还有其他方法吗?以上两种方式在我看来都太乱了。

【问题讨论】:

    标签: javascript reactjs typescript redux react-redux


    【解决方案1】:

    您需要拆分道具 - 您需要 DispatchPropsStatePropsOwnProps 类型。您还必须使用 TypeScript 的泛型和 connect

    • DispatchProps 是您的动作创建者。
    • StateProps 是您的状态道具(废话) - 这些来自 mapStateToProps - 该函数的返回类型应与此类型匹配。
    • OwnProps 是您的组件接受(并且可能是预期)的道具。可选道具应在界面中标记为可选。

    我这样做的方式(没有装饰器,但我确信它适用于这里)是

    interface ComponentDispatchProps {
        doSomeAction: typeof someAction;
    }
    
    interface ComponentStateProps {
        somethingFromState: any;
    }
    
    interface ComponentOwnProps {
        somethingWhichIsRequiredInProps: any;
        somethingWhichIsNotRequiredInProps?: any;
    }
    
    // not necessary to combine them into another type, but it cleans up the next line
    type ComponentProps = ComponentStateProps & ComponentDispatchProps & ComponentOwnProps;
    
    class Component extends React.Component<ComponentProps, {}> {...}
    
    function mapStateToProps(state, props) { 
        return { somethingFromState };
    }
    
    export default connect<ComponentStateProps, ComponentDispatchProps, ComponentOwnProps>(
        mapStateToProps,
        mapDispatchToProps
    )(Component);
    

    我认为您必须使用 @connect&lt;StateProps, DispatchProps, OwnProps&gt; 来装饰并返回一个接受 OwnProps 的类。

    如果您查看 TS 中的 connects 实现

    export declare function connect<TStateProps, TDispatchProps, TOwnProps>(...): ComponentDecorator<TStateProps & TDispatchProps, TOwnProps>
    
    interface ComponentDecorator<TOriginalProps, TOwnProps> {
        (component: ComponentClass<TOriginalProps> | StatelessComponent<TOriginalProps>): ComponentClass<TOwnProps>;
    }
    

    connect&lt;...&gt; 返回一个ComponentDecorator,它在传递组件时(在您的情况下,这是在将装饰器转出时透明地完成),而不管StatePropsDispatchProps 返回一个期望@987654338 的组件@。

    connect(非泛型)返回InferableComponentDecorator

    export interface InferableComponentDecorator {
        <P, TComponentConstruct extends (ComponentClass<P> | StatelessComponent<P>)>(component: TComponentConstruct): TComponentConstruct;
    }
    

    它尝试根据提供给组件的道具推断道具,在您的情况下,这是所有道具的组合(OwnProps 从上面变为ComponentProps)。

    【讨论】:

    • 非常感谢!那成功了。它不适用于@connect 装饰器,我还必须删除namespace,但是当我使用常规的export default connect(...) 时,就像在您的示例中一样,它可以工作。当我使用装饰器时,我得到一个编译器错误Type 'Component&lt;IOwnProps, ComponentState&gt;' is not assignable to type 'WbUserEditComponent'. Property 'componentDidMount' is missing in type 'Component&lt;IOwnProps, ComponentState&gt;。我认为这是装饰器的打字问题。
    • 抱歉,我无法使用装饰器 - 我过去曾尝试使用装饰器,但无法让它们正常工作,很好。不过,很高兴我至少可以解决您的问题。
    • 如果你明确地指定了 mapStateToProps 和/或 mapDispatchToProps 中的参数类型,那么 TypeScript 应该能够推断出 connect 中的泛型类型参数。我认为每个参数的第一个参数可以是any,第二个参数的类型是ComponentOwnProps
    • @mikebridge 是的,state 实际上是所有 reducer 的组合模式,但 any 也可以工作(尽管你失去了一些很好的智能感知)。第二个参数是ComponentOwnProps,是的。
    • 不知道为什么这个解决方案对我来说不成功——这是什么打字版本和 react-redux 版本?
    【解决方案2】:

    当然,您可以手动设置类型。但是使用起来很舒服,实际上是从connect 获得的。它有助于避免烦人的重复。

    示例 1:

    type Props = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>
    
    class MyComponent extends React.PureComponent<Props> {
      ...
    }
    
    const mapStateToProps = (state: ReduxState) => ({
      me: state.me,
    })
    
    const mapDispatchToProps = (dispatch: ReduxDispatch) => ({
      doSomething: () => dispatch(Dispatcher.doSomething()),
    })
    
    export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
    

    现在我们直接从 redux state 和 action/dispatch 函数中获取类型。

    一段时间后,我们将此示例简化为:

    示例 2:

    //// other file
    import { InferableComponentEnhancerWithProps } from 'react-redux'
    
    type ExtractConnectType<T> = T extends InferableComponentEnhancerWithProps<infer K, any> ? K : T
    //// <= Save it somewhere and import
    
    type Props = ExtractConnectType<typeof connectStore>
    
    class MyComponent extends React.PureComponent<Props> {
      ...
    }
    
    const connectStore = connect(
      (state: ReduxState) => ({
        me: state.me,
      }),
      (dispatch) => ({
        doSomething: () => dispatch(Dispatcher.doSomething()),
      })
    )
    
    export default connectStore(MyComponent)
    
    

    【讨论】:

      【解决方案3】:

      简单来说, 组件应该清楚 props 应该来自父级和 connect (redux)。

      现在,connect() 可以向组件发出redux state(您的应用状态)或action 作为prop,组件的其余props 应该来自父组件。

      按照建议,最好将组件 props 分成 3 个部分(ComponentStatePropsComponentDispatchProps&amp;ComponentOwnProps),然后在 connect() 中使用它们。并且,将这 3 个道具连接起来形成ComponentProps

      我认为下面的代码会更好地理解。

      // Your redux State    
      type SampleAppState = {
         someState: any;
      };
      
      // State props received from redux
      type ChildStateProps = {
        propFromState: any;
      };
      
      // dispatch action received from redux (connect)
      type ChildDispatchProps = {
        actionAsProp: () => void;
      };
      
      // Props received from parent
      type ChildOwnProps = {
        propFromParent: any;
      };
      
      // All Props
      type ChildProps = ChildStateProps & ChildDispatchProps & ChildOwnProps;
      
      const ChildComponent = (props: ChildProps) => {
        return <>{/*....*/}</>;
      };
      
      let ConnectedChildComponent = connect<
        ChildStateProps,
        ChildDispatchProps,
        ChildOwnProps,
        SampleAppState
      >(
        (appState: SampleAppState, ownProps: ChildOwnProps) => {
          // Shape that matches ChildStateProps
          return {
            propFromState: appState.someState,
          };
        },
        (dispatch, ownProps: ChildOwnProps) => {
          return bindActionCreators(
            // Shape that matches ChildDispatchProps
            {
              actionAsProp: () => {},
            },
            dispatch,
          );
        },
      )(ChildComponent);
      
      const ParentComponent = () => {
        return (
          <>
            <ConnectedChildComponent propFromParent={'Prop Value'} />
          </>
        );
      };
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-10-25
        • 2017-07-24
        • 1970-01-01
        • 2018-07-17
        • 1970-01-01
        • 2019-05-09
        • 1970-01-01
        相关资源
        最近更新 更多