typescript + redux:在父组件

时间:2017-04-19 17:23:41

标签: javascript reactjs typescript redux react-redux

我正在使用redux和typescript作为我当前的webapp。

定义组件的道具的最佳做法是什么,它通过@connect接收redux-actions,还有来自父级的道具? 例如:

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

}

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

  1. 刚刚通过行动&amp;通过我的整个应用程序说明。但这意味着即使只是一些小的子组件必须改变,我的整个应用程序也会被重新渲染。或者是在所有商店更改中收听我的顶级组件的redux方式?然后我必须在shouldComponentUpdate内写出很多逻辑,用于没有扁平物体的道具。

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

  3. -

    // mychild.tsx
    export namespace MyChildComponent {
    
      export interface IProps {
        propertyFromParent: string;
        propertyFromRedux?: typeof MyAction;  // This is the problem
      }
    }
    

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

3 个答案:

答案 0 :(得分:17)

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

  • DispatchProps是您的行动创作者。
  • StateProps是你的状态道具(duh) - 这些来自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<StateProps, DispatchProps, OwnProps>来装饰并返回一个接受OwnProps的类。

如果你看一下TS中的connect实现

export declare function connect<TStateProps, TDispatchProps, TOwnProps>(...): ComponentDecorator<TStateProps & TDispatchProps, TOwnProps>

interface ComponentDecorator<TOriginalProps, TOwnProps> {
    (component: ComponentClass<TOriginalProps> | StatelessComponent<TOriginalProps>): ComponentClass<TOwnProps>;
}

connect<...>返回一个ComponentDecorator,当传递组件时(在你的情况下,这是在转发装饰器时透明地完成),无论StateProps和{{1}返回一个期望DispatchProps的组件。

OwnProps(非通用)返回connect

InferableComponentDecorator

试图根据提供给组件的道具来推断道具,在你的情况下是所有道具的组合(export interface InferableComponentDecorator { <P, TComponentConstruct extends (ComponentClass<P> | StatelessComponent<P>)>(component: TComponentConstruct): TComponentConstruct; } 从上面变为OwnProps)。

答案 1 :(得分:0)

当然,您可以手动设置类型。但是实际上使用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状态和操作/调度功能获取类型。

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

示例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)

答案 2 :(得分:0)

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

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

按照建议,最好将组件 props 分成 3 部分(ComponentStateProps, ComponentDispatchProps & 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'} />
    </>
  );
};