typescript + redux:在父组件中排除 redux props

Posted

技术标签:

【中文标题】typescript + redux:在父组件中排除 redux props【英文标题】:typescript + redux: exclude redux props in parent component 【发布时间】: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 个解决方案。

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

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

-

// mychild.tsx
export namespace MyChildComponent 

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

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

【问题讨论】:

【参考方案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' />
    </>
  );
;

【讨论】:

以上是关于typescript + redux:在父组件中排除 redux props的主要内容,如果未能解决你的问题,请参考以下文章

使用 TypeScript 在 React 组件外部调度 Redux Thunk 操作

强烈键入 react-redux 与 typescript 连接

带有 Typescript 和 react-redux 的有状态组件:“typeof MyClass”类型的参数不可分配给 Component 类型的参数

Typescript 无状态 HOC react-redux 注入道具打字

在父组件中反应 HandleClick

在 .NET Core 2.0 reactredux 模板项目中使用 react redux 和 typescript 将组件与 OwnProps 连接起来