TypeScript与Redux容器

时间:2016-11-18 07:58:13

标签: reactjs typescript redux react-redux

我在弄清楚如何正确输入Redux containers时遇到了一些麻烦。

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

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

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

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

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 { }

然而,除非我使所有MyProp道具可选,否则这不起作用,这样每个映射函数只能返回它们关心的部分。我不想让道具成为可选的,因为它们不是演示组件的可选项。

另一个选择是为每个地图功能拆分道具,并将它们组合成组件道具:

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

好吧,这让我更接近我想要的东西,但它有点吵,它让我在容器的形状周围写出我的演示组件道具界面,我不喜欢。

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

<MyContainer />

这会导致nameselectedonSelect全部丢失的编译错误......但这是故意的,因为容器正在连接到Redux并提供这些错误。所以这促使我回到使所有组件道具可选,但我不喜欢它,因为它们不是真正可选的。

MyContainer有一些想要传递的道具时,事情变得更糟:

<MyContainer section="somethng" />

现在我要做的是让section成为MyContainer所需的道具而不是MyComponent的道具,nameselectedonSelectMyComponent所需的道具,但MyContainer的所有道具都是可选的或不道具。我完全不知道如何表达这一点。

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

3 个答案:

答案 0 :(得分:16)

你的最后一个例子是正确的。您还需要定义的是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);

这也可让您在ownPropsmapStateToProps中输入mapDispatchToProps,例如

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

只要定义dispatch,state和ownProps类型,就不需要为MyProps类型定义交集类型。这样,您可以将MyProps保留在自己的位置,并让容器或使用组件而没有容器的位置根据需要应用道具。我想这取决于你和你的用例 - 如果你定义MyProps = MyStateProps & MyDispatchProps & MyOwnProps,它就绑定了一个特定的容器,这个容器灵活性较差(但不那么详细)。

作为一个解决方案,它非常冗长,但我认为没有办法告诉TypeScript所需的道具将在不同的地方组装,而connect会将它们绑在一起

另外,为了它的价值,我通常选择道具,为了简单起见,所以我没有太多的经验来分享使用这种方法。

答案 1 :(得分:2)

我只使用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;

演示组件道具是非选择性的 - 完全符合演示文稿的要求,而不会影响相应的&#34; smart&#34;成分

与此同时,mapStateToPropsmapDispatchToProps将允许我在每个函数中指定所需表示道具的子集,同时将在展示道具界面中未定义的任何道具标记为错误。

答案 2 :(得分:1)

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