【问题标题】:Why specify function return types?为什么要指定函数返回类型?
【发布时间】:2021-11-17 17:53:25
【问题描述】:

大多数时候我读到我们应该尽可能地使用类型推断。在编写函数时,我知道我们必须输入参数,因为它们无法推断,但为什么我们必须输入返回值呢? TypeScript 正在处理这个问题。显式键入函数的返回值有什么好处?到目前为止,我只读到我应该这样做,但没有人说为什么。

【问题讨论】:

  • 好处是编译器可以仔细检查你的意图。当然它会根据实现推断返回类型,但它可能不是你认为函数应该返回的(一种常见情况:你错过了条件分支,函数返回 T | undefined 而不是 T -编译器会很高兴地推断出条件类型,但如果你告诉它你打算总是返回一个值,它可以告诉你实现中存在问题。
  • @jonrsharpe 除非有很好的重复问题/答案 - 您的评论值得发布(即使是原样)。我会投票:-)
  • @zerkms 我觉得一定有,所以我正在努力寻找!如果不能,我会发布答案。
  • 个人偏好的经验法则:任何公共、私有内容的显式类型都可以依赖于推理。当然,这完全是自以为是。

标签: typescript


【解决方案1】:

编译器可以推断出你的代码做了什么,但它不知道你的意图。举个简单的例子:

function thing(value: string) {
    return value === "foo" ? 123 : "456";
}

推断的类型是function thing(value: string): 123 | "456",它与实现相匹配,但这是否有意义?例如,也许我打算总是返回一个number;如果我告诉编译器,它可以告诉我我没有:

Type 'string | number' is not assignable to type 'number'.
  Type 'string' is not assignable to type 'number'.(2322)

特别是当您使用复杂的包装器/泛型类型时(例如,我在 中看到很多与此相关的问题,其中使用了 RxJS 可观察对象),这确实有助于获得有关您的假设的早期反馈。


我还经常使用测试驱动开发 (TDD),其中在实现之前编写测试的价值之一是它让您有机会在更改接口的成本接近时讨论接口零。使用经典的 RPS 示例:

function rps(left: string, right: string) {
  return "right";
}

it("returns 'right' for 'rock' vs. 'paper'", () => {
  expect(rps("rock", "paper")).to.equal("right");
});

这会编译并通过,但它是我们想要的吗?现在我们可以谈谈选项了:

  • 我们是否接受推断类型function rps(left: string, right: string): string

  • 使用例如更具体

    type Throw = "rock" | "paper" | "scissors";
    type Outcome = "left" | "right" : "draw";
    function rps(left: Throw, right: Throw): Outcome { ... }
    
  • 改用enums

我们可以讨论权衡取舍,并根据我们当时所知道的情况选择最佳选项。显式返回类型用作我们做出的决定的文档


如果您要对代码进行 linting,我建议您打开 @typescript-eslint/explicit-function-return-type,这提供了它的基本原理:

函数返回值的显式类型使任何人都清楚 调用代码返回什么类型。这确保了返回值 分配给正确类型的变量;或者在这种情况下 没有返回值,调用代码不会尝试使用 不应该定义的值。

【讨论】:

    【解决方案2】:

    注释函数返回类型的主要原因与使用 Typescript 的主要原因相同:因为您希望编译器检查您的代码并在您犯某些类型的错误时为您提供有用的错误消息。

    如果你让 Typescript 推断出你的函数的返回类型,那么无论它推断出什么,都将是函数的返回类型,即使推断出的类型不是你想要的类型。在这种情况下,如果您尝试返回错误类型的内容,编译器将使用该错误类型作为函数的返回类型,当您尝试调用该函数并期望某些正确类型的内容时,您将收到错误 - 或更糟,你不会得到错误,但你的代码会在运行时失败或做错事。

    另一方面,如果你知道你想要的返回类型是什么,并且你想让编译器检查你的函数是否确实返回了该类型的东西,那么你应该使用类型注释。在这种情况下,如果您尝试返回错误类型的内容,那么您将在函数本身中收到错误,而错误实际上是在哪里。

    【讨论】:

      【解决方案3】:

      这是使用显式返回类型的一个很好的理由。根据TS wiki

      添加类型注解,尤其是返回类型,可以为编译器节省大量工作。部分原因是命名类型往往比匿名类型(编译器可能推断)更紧凑,这减少了读取和写入声明文件(例如增量构建)所花费的时间。类型推断非常方便,因此无需普遍执行此操作 - 但是,如果您确定了代码的慢速部分,尝试它可能会很有用。

      因此,如果您对编译性能没有任何问题,我认为不需要指定返回类型

      Here 你可以找到另一个关于编译性能的问题/答案。它还涉及 TypeScript Performance wiki

      附:您可以使用显式返回类型来禁止使用返回值的一些额外属性。考虑这个例子:

      const foo = (): { age: number } => {
          const result = {
              age: 42,
              name: 'John'
          }
          return result
      }
      
      const result = foo()
      result.age // ok
      result.name //error
      

      您可能已经注意到,显式{age:number} 返回类型是返回值类型的超类型。但它很弱,因为如果你想返回字面量对象,它就不起作用,就像这里:

      const foo = (): { age: number } => ({
          age: 42,
          name: 'John' // error
      })
      
      

      所以我不推荐使用这种技术,但是值得了解。

      【讨论】:

      • 你的博客不错! (虽然没有 rss :-( )
      • @zerkms 非常感谢。它激励!我会考虑缺少 RSS。
      • 同意,除了“使用显式返回类型的唯一一个好理由”部分。另一个答案说明了另一个很好的理由以及我经常使用它的原因。
      • @jperl 你是对的,我想这值得删除
      • 这些都是很棒的答案,我希望我能接受多个
      猜你喜欢
      • 2017-06-20
      • 1970-01-01
      • 1970-01-01
      • 2023-01-07
      • 2012-12-04
      • 2014-04-09
      • 2011-04-13
      • 2015-03-23
      • 2017-11-12
      相关资源
      最近更新 更多