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

typescript + redux: exclude redux props in parent component

我正在为我当前的网络应用程序使用 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. 设置 MyChildComponent 可选的 IProps 中的参数,如下所示:

-

// mychild.tsx
export namespace MyChildComponent {

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

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

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

一起使用
  • DispatchProps 是您的动作创作者。
  • StateProps 是你的状态道具(duh) - 这些来自 mapStateToProps - 该函数的 return 类型应该匹配这个类型。
  • 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> 来装饰,return 一个 class 接受 OwnProps.

如果您查看 connect 在 TS

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

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

connect<...> returns a ComponentDecorator,当传递组件时(在您的情况下,这是在转译装饰器时透明地完成的),而不管 StateProps ,并且 DispatchProps return 是一个需要 OwnProps 的组件。

connect(非通用)returns InferableComponentDecorator

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

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

当然你可以手动设置类型。但是使用生成的东西很舒服,你实际上是从 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 状态和 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)

简单来说, 一个组件应该清楚什么 props 应该来自父级和 connect (redux).

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

按照建议,最好将组件道具分成3部分(ComponentStatePropsComponentDispatchProps & ComponentOwnProps)然后在[=13中使用它们=].并且,加入这 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'} />
    </>
  );
};