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

IT技术 javascript reactjs typescript redux react-redux
2021-05-04 02:33:38

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

定义通过 接收 redux-actions 的组件的 props 的最佳实践是什么@connect例子:

// 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不是平面对象的props编写很多逻辑

  2. 设置帕拉姆中IPropsMyChildComponent 这样的可选:

——

// mychild.tsx
export namespace MyChildComponent {

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

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

3个回答

您需要拆分你的props-你需要一个DispatchPropsStatePropsOwnProps类型。您还必须使用 TypeScript 的泛型connect

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

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

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>which 来装饰并返回一个接受OwnProps.

如果你看一下connectTS的实现

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

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

connect<...>返回 a ComponentDecorator,当传递组件时(在您的情况下,这是在将装饰器转出时透明地完成),而不管StateProps,并DispatchProps返回一个期望OwnProps.

connect (非通用)回报 InferableComponentDecorator

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

它试图根据提供给组件的props推断props,在您的情况下,它是所有props的组合(从上面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 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)

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

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

按照建议,最好将组件 props 分成 3 个部分 ( ComponentStateProps, ComponentDispatchProps & ComponentOwnProps),然后在connect(). 并且,加入这 3 个props形成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'} />
    </>
  );
};