【问题标题】:Stricter type assertion in TypescriptTypescript 中更严格的类型断言
【发布时间】:2021-09-21 04:02:14
【问题描述】:

假设我有

declare function doSomething(...args: any[]): any

interface Example {
    a: number
    b: number
}

doSomething({a: 2, b: 1, c: 10} as Example)

这不会报告错误,因为该对象扩展了 Example 并且打字稿很高兴,所以我最终使用了一个丑陋的标识函数:

function cast<T>(arg: T) {
    return arg
}

doSomething(cast<Example>({a: 2, b: 1, c: 10})) // yay, error

这让我很恼火,我需要实际调用 %^#% 无操作函数来进行正确的类型转换。我最终在每个需要它的文件中声明它,只是为了给 js 编译器更好的优化它的机会。

有什么不知道的ts魔法可以避免函数调用吗?

是的,我知道我可以做到:

const x: Example = {a: 2, b: 1, c: 10}
doSomething(x)

还有这个:

declare function doSomething(arg: Example): any

这真的不是重点。考虑以下 lambda:

const example = (i: number, j: number, k: number) => cast<Example>({a: 1, b: 2, c: 3})

正确设置类型而不导致我需要编写的标识函数

const example: (i: number, j: number, k: number) => Example = (i, j, k) => ({a: 1, b: 2, c: 3})

这不是很干燥

是的,我可以写

function example (i: number, j: number, k: number): Example {
    return {a: 1, b: 2, c: 3}
}

再说一遍,不是重点

// 编辑

@thedude 刚刚在评论中用我不知道的 lambda 返回类型语法让我大吃一惊,还有一个我使用这种转换的例子

declare function doSomethingWithArray(arg: Example[]): void

doSomethingWithArray(cast<(Example | boolean)[]>([
    {a: 1, b: 2},
    false,
    {a: 1, b: 2, c: 3}
]).filter(x => x) as Example[])

//编辑2

我似乎很难解释我想要什么,另一个例子:这个通用函数解决了过滤器示例问题

function filterFalse<T>(x: (T | false)[]) {
    return x.filter(x => x) as Exclude<T, false>[]
}

doSomethingWithArray(filterFalse<Example>([
    {a: 1, b: 2},
    false,
    {a: 1, b: 2, c: 3} // error
]))

但需要为这个特定任务定义一个专门的函数。我在问是否有一种通用的方法来强制对编译时文字进行严格的类型检查,而不会导致函数调用。这正是cast&lt;T&gt; 所做的,但在 js 输出中没有毫无意义的调用。

【问题讨论】:

  • 你也可以写:const example = (i: number, j: number, k: number): Example =&gt; ({a: 1, b: 2, c: 3})
  • 最后一个sn-p有什么问题?为什么这不是重点?如果您编写的代码不使用any,那么您应该很少需要像这样进行强制转换。
  • 重点是我想要一个 lambda 声明而不是函数声明,这确实可以通过 @thedude 在上面的评论中所写的内容解决,请参阅编辑以获取另一个示例
  • @OtisVallone 在新的 sn-p 中,您仍然不需要 cast 函数。 filter 函数可以作用于任何数组,因此您可以给它一个混合布尔值和Example 对象的数组。 Typescript 已经推断出该数组的类型是(boolean | Example)[]
  • @siride 过滤器推断文字对象(和布尔值)的联合数组,而不是(布尔值 | 示例)[]。除了一些我不知道的原因之外,即使这个doSomethingWithArray([{a: 1, b: 2, c: 3}].filter(x =&gt; x))c 上也不会失败,而没有过滤器它会失败。

标签: typescript types type-assertion


【解决方案1】:

我不明白为什么在这些示例中需要 cast 函数。我想你可能想多了。


让我们从这里开始,这很好。

doSomething({a: 2, b: 1, c: 10} as Example)

as 关键字通常仍然是类型安全的。它允许您将值转换为超类型。在这种情况下,{ a: number, b: number }{ a: number, b: number, c: number } 的超类型。

例如:

const value = { a: 1, b: 2, c: 2 }
const exampleValue: { a: number, b: number } = value // works!

在你的第一个例子中,如果你省略了一个必需的属性,你会遇到类型错误:

doSomething({b: 1, c: 10} as Example)
// Property 'a' is missing in type '{ b: number; c: number; }'
// but required in type 'Example'.(2352)

那为什么会报错呢?

cast<Example>({a: 1, b: 2, c: 3}) // error

因为当您创建 一个值并将其分配给超类型时,您仅引用该值将属于该超类型。这意味着可能存在的任何其他属性都将无法访问。 Example 没有 c 属性。所以打字稿认为这是一个错误,这是正确的。

但在实际代码中,如果您将带有 a、b 和 c 的对象传递给只需要 a 和 b 的函数,则不会发生任何不好的事情。你已经满足了约束,所以没有问题。


在这个sn-p中:

doSomething(cast<Example>({a: 2, b: 1, c: 10})) // yay, error

演员阵容毫无意义。如果doSomething 关心它接收的类型,那么它应该在它的参数中声明它。如果它不在乎,那么根本不需要演员表。


const x: Example = {a: 2, b: 1, c: 10}
doSomething(x)

这与上面的as Example基本相同。


declare function doSomething(arg: Example): any

这是正确的,并且正是您应该做的。如果函数采用特定类型,则让它自己强制执行。如果您将其分配给 any,则不需要转换任何内容,因为无论如何该转换都会丢失。


const example = (i: number, j: number, k: number) =>
  cast<Example>({a: 1, b: 2, c: 3})

如果你想要一个函数返回一个类型,你通常只注解返回类型。以下工作正常:

const example(i: number, j: number, k: number): Example =>
  ({ a: i, b: j, c: k }) // error, as expected

要正确设置类型而不产生标识函数,我需要编写:

const example: (i: number, j: number, k: number) => Example = (i, j, k) => ({a: 1, b: 2, c: 3})

不正确,请参见前面的示例。一个函数,类型按预期强制执行。


最后:

function example (i: number, j: number, k: number): Example {
    return {a: 1, b: 2, c: 3} // error as expected.
}

如果您想要一个创建并返回Example 的函数,这是完美的。它会正确地提醒您有一个永远不会被使用的属性,并清楚地记录返回值。


TLDR:

您的代码 sn-ps 根本不需要 cast&lt;T&gt;() 函数。我认为你最好的选择是消除所有出现的any,然后让 Typescript 强制执行这些类型。这就是它的用途。编译器非常好,通常不需要那么多帮助。


过滤

在这一切中,过滤确实是一个奇怪的案例。问题源于这样一个事实,即filter() 被键入以返回与它开始时完全相同的数组类型,而不管您过滤什么。常见的解决方法是让过滤器回调函数为type predicate function

然后你可以这样做:

interface Example {
    a: number
    b: number
}

declare function doSomethingWithArray(arg: Example[]): void

doSomethingWithArray([
    {a: 1, b: 2},
    false,
    {a: 1, b: 2, c: 3} // no error because of perfectly safe casting to supertype
].filter((x): x is Example => typeof x !== 'boolean'))

See playground

【讨论】:

  • 第一个 sn-p 在哪里解释我想要什么,后两个在哪里解释我不想要什么,lambda 是我使用它的唯一真实示例。我不知道可以直接指定返回类型的 lambda 语法,这解决了 lambda 示例,我添加了另一个使用这种类型转换的示例。
  • 请参阅我的答案末尾以获取有关过滤的更新。您可以使用类型谓词函数告诉 typescript 您的过滤操作会更改数组的类型。
  • 看,我在可选对象字段中打错了太多次,无法同意你的观点,即它是完全安全的。基本上,您是在建议所有字段可选的每个接口都与Record&lt;string, any&gt; 一样好(嗯,不完全是因为它仍会检查现有键的值类型,但我希望您能明白)。此外,如果它是这样一个没有错误,那么为什么当你只传递一个原始数组而不是过滤版本时它是一个错误?该值可以作为超类型安全地转换为参数类型,但编译器仍会报告错误(应该如此)。
猜你喜欢
  • 2021-12-29
  • 2022-01-14
  • 2019-08-21
  • 2021-12-03
  • 2019-01-27
  • 2020-10-30
  • 1970-01-01
  • 2018-03-08
  • 1970-01-01
相关资源
最近更新 更多