TypeScript 与 Redux 容器的斗争

IT技术 reactjs typescript redux react-redux
2021-05-21 04:33:04

我在弄清楚如何正确键入Redux 容器时遇到了一些麻烦

考虑一个可能如下所示的简单展示组件:

interface MyProps {
  name: string;
  selected: boolean;
  onSelect: (name: string) => void;
}
class MyComponent extends React.Component<MyProps, {}> { }

从这个组件的角度来看,所有的props都是必需的。

现在我想编写一个容器,将所有这些props拉出状态:

function mapStateToProps(state: MyState) {
  return {
    name: state.my.name,
    selected: state.my.selected
  };
}

function mapDispatchToProps(dispatch: IDispatch) {
  return {
    onSelect(name: string) {
      dispatch(mySelectAction(name));
    }
  };
}

const MyContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent);

这是有效的,但有一个很大的类型问题:映射函数(mapStateToPropsmapDispatchToProps)没有保护它们提供正确的数据来实现MyProps这很容易出错、打字错误和重构不佳。

我可以使映射函数返回类型MyProps

function mapStateToProps(state: MyState): MyProps { }

function mapDispatchToProps(dispatch: IDispatch): MyProps { }

但是,除非我将所有MyPropprops 设为可选,否则这不起作用,以便每个映射函数只能返回它们关心的部分。我不想让 props 成为可选的,因为它们对于展示组件来说不是可选的。

另一种选择是拆分每个地图功能的props并将它们组合为组件props:

// MyComponent
interface MyStateProps {
  name: string;
  selected: boolean;
}

interface MyDispatchProps {
  onSelect: (name: string) => void;
}

type MyProps = MyStateProps & MyDispatchProps;

class MyComponent extends React.Component<MyProps, {}> { }

// MyContainer
function mapStateToProps(state: MyState): MyStateProps { }

function mapDispatchToProps(dispatch: IDispatch): MyDispatchProps { }

好的,这让我更接近我想要的东西,但它有点嘈杂,它让我围绕容器的形状编写我的展示组件props界面,这是我不喜欢的。

现在出现了第二个问题。如果我想将容器放在另一个组件中怎么办:

<MyContainer />

这会导致nameselectedonSelect都丢失的编译错误......但这是故意的,因为容器正在连接到 Redux 并提供这些。所以这促使我重新让所有组件 props 成为可选的,但我不喜欢那样,因为它们并不是真正的可选。

MyContainer有一些自己的 props 想要传入时,事情会变得更糟

<MyContainer section="somethng" />

现在,我想要做的是有section一个必需的propsMyContainer,但不是一个propsMyComponent,和nameselected以及onSelect需要的propsMyComponent,但可选的,或者不是在所有的propsMyContainer我完全不知道如何表达这一点。

对此的任何指导将不胜感激!

3个回答

你的最后一个例子是正确的。您还需要定义一个MyOwnProps接口,并键入connect函数。

使用这些类型:https : //github.com/DefinitelyTyped/DefinitelyTyped/blob/master/react-redux/react-redux.d.ts,你可以做这样的事情

interface MyProps {
  section: string;
  name: string;
  selected: boolean;
  onSelect: (name: string) => void;
}

interface MyStateProps {
  name: string;
  selected: boolean;
}

interface MyDispatchProps {
  onSelect: (name: string) => void;
}

interface MyOwnProps {
  section: string;
}

class MyComponent extends React.Component<MyProps, {}> { }

function mapStateToProps(state: MyState): MyStateProps { }

function mapDispatchToProps(dispatch: IDispatch): MyDispatchProps { }

const MyContainer = connect<MyStateProps, MyDispatchProps, MyOwnProps>(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent);

这也让你输入ownPropsmapStateToPropsmapDispatchToProps,如

function mapStateToProps(state: MyState, ownProps: MyOwnProps): MyStateProps

只要定义了 dispatch、state 和 ownProps 类型,就不需要为 MyProps 类型定义交集类型。这样,您可以将 MyProps 保留在自己的位置,并让容器或在没有容器的情况下使用组件的地方根据需要应用props。我想这取决于您和您的用例 - 如果您定义MyProps = MyStateProps & MyDispatchProps & MyOwnProps,它会绑定到一个特定的容器,该容器不太灵活(但不那么冗长)。

作为一个解决方案,它非常冗长,但我认为没有任何方法可以告诉 TypeScript 所需的props的不同部分将在不同的地方组装,并将connect它们绑在一起。

此外,为了它的value,为了简单起见,我通常使用可选的props,所以我没有太多关于使用这种方法的经验可以分享。

我只是使用Partial<MyProps>,其中Partial定义为的内置 TypeScript 类型:

type Partial<T> = {
    [P in keyof T]?: T[P];
}

它接受一个接口并使其中的每个属性都是可选的。

这是我编写的展示/Redux 感知组件对的示例:

/components/ConnectionPane/ConnectionPane.tsx

export interface IConnectionPaneProps {
  connecting: boolean;
  connected: boolean;
  onConnect: (hostname: string, port: number) => void;
}

interface IState {
  hostname: string;
  port?: number;
}

export default class ConnectionPane extends React.Component<IConnectionPaneProps, IState> {
   ...
}

/containers/ConnectionPane/ConnectionPane.ts

import {connect} from 'react-redux';
import {connectionSelectors as selectors} from '../../../state/ducks';
import {connect as connectToTestEcho} from '../../../state/ducks/connection';
import {ConnectionPane, IConnectionPaneProps} from '../../components';

function mapStateToProps (state): Partial<IConnectionPaneProps> {
  return {
    connecting: selectors.isConnecting(state),
    connected: selectors.isConnected(state)
  };
}

function mapDispatchToProps (dispatch): Partial<IConnectionPaneProps> {
  return {
    onConnect: (hostname, port) => dispatch(connectToTestEcho(hostname, port))
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(ConnectionPane) as any;

展示组件 props 是非可选的 - 完全符合展示的要求,对相应的“智能”组件没有任何影响。

同时,mapStateToPropsmapDispatchToProps允许我在每个函数中分配所需的展示props的子集,同时将展示props界面中未定义的任何props标记为错误。

interface MyStateProps {
    name: string;
    selected: boolean;
}

interface MyDispatchProps {
    onSelect: (name: string) => void;
}

interface MyOwnProps {
    section: string;
}

// Intersection Types
type MyProps = MyStateProps & MyDispatchProps & MyOwnProps;


class MyComponent extends React.Component<MyProps, {}> { }

function mapStateToProps(state: MyState): MyStateProps { }

function mapDispatchToProps(dispatch: IDispatch): MyDispatchProps { }

const MyContainer = connect<MyStateProps, MyDispatchProps, MyOwnProps>(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent);

您可以使用称为交叉类型的 东西https://www.typescriptlang.org/docs/handbook/advanced-types.html#intersection-types