【问题标题】:Can mapStateToProps narrow the types of props?mapStateToProps 可以缩小 props 的类型吗?
【发布时间】:2020-02-03 02:06:29
【问题描述】:

我正在尝试向旧组件添加类型注释,但不确定connect 的类型是否支持我正在尝试做的事情。该组件是一个连接组件,它接收一个广泛类型的 prop,然后使用该 prop 在 state 中查找一个值,并吐出一个较窄的 prop。

目前,我可以使用广泛的类型定义道具,并且代码将起作用。但是,我在组件中编写的任何代码都必须符合这种广泛的类型,即使我知道它已经被缩小了。因此,为了满足打字稿的要求,我要么必须添加类型断言,要么添加不必要的类型检查。

下面是显示行为的代码的简化示例:

interface ExampleProps {
  thing: string | number;
}

const Example extends React.Component<ExampleProps> {
  render() {
    const problem = this.props.thing * 2; // type error

    // Workaround, but losing some typesafety
    // const notAProblem = (this.props.thing as number) * 2;

    // Workaround, but doing unnecessary checks
    // if (typeof this.props.thing === 'number') {
    //   const notAProblem = this.props.thing * 2;
    // }

    return null;
  }
}

const mapStateToProps = (state: RootState, otherProps: ExampleProps) => {
  if (typeof otherProps.thing === 'string') {
    return {
      thing: state.lookup[otherProps.thing]; //resolves to a number
    }
  }
  return {
    thing, // also a number
  }
}

export default connect(mapStateToProps)(Example)

在上面的代码中,typescript 正确地指出 const problem: number = this.props.thing * 2; 是一个问题。对于我定义的类型,this.props.thing 可能是一个字符串,所以它正确地给出了错误The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.

是否可以使用connect 以使不同的道具进来而不是出去?换句话说,我需要有人能够渲染&lt;Example thing={2} /&gt;&lt;Example thing={"2"} /&gt;,但是对于示例中的代码来说,由于mapStateToProps,thing 只能是number

【问题讨论】:

  • 我认为你只需要在计算之前进行类型检查就可以在 Example 中使用 type-guard

标签: reactjs typescript react-redux


【解决方案1】:

方法 1:每个字段的通用类型

一种方法是使“事物”成为ExampleProps 上的泛型类型,即:

interface ExampleProps<T extends (string | number)> {
  thing: T;
}

然后你可以分别在类和函数类型签名中指定不同的具体类型:

class Example extends Component<ExampleProps<number>> {
  render() {
    const problem: number = this.props.thing * 2;
    return null;
  }
}

const mapRootStateToExampleProps = (state: RootState, {thing}: ExampleProps<string | number>) => {
  if (typeof thing === 'string') {
    return {
      thing: state.lookup[thing] //resolves to a number
    }
  }
  return {
    thing, // also a number
  }
}

不过,这可能不是最佳解决方案,因为您的类型签名可能会很快变得冗长。它只在 ExampleProps 接口的一两个字段上才真正有意义。

方法 2:“out”参数的基本接口,“in”参数扩展 base

另一种方法是声明一个表示传入道具的基本接口,然后声明一个扩展它的接口来表示道具输出 - 然后您只需指定“输出”时要修改的类型,因此您'没有重复很多代码:

interface ExamplePropsIn {
  thing: string | number;
  other: string;
}

interface ExamplePropsOut extends ExamplePropsIn {
    thing; number;
}

class Example extends Component<ExamplePropsOut> {
  render() {
    const problem: number = this.props.thing * 2;
    return null;
  }
}

const mapRootStateToExampleProps = (state: RootState, {thing}: ExamplePropsIn) => {
  if (typeof thing === 'string') {
    return {
      thing: state.lookup[thing] //resolves to a number
    }
  }
  return {
    thing, // also a number
  }
}

【讨论】:

  • @NicholasTower 很有趣!我想我个人会选择 2,但我很高兴你有适合你的东西:)
  • 好吧,我在您编辑方法 2 之前就开始尝试了:) 我现在正在调查这是否会有所改进,但无论哪种方式,我都知道我有一个可行的解决方案。
猜你喜欢
  • 2019-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-07
  • 2017-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多