简体   繁体   English

Typescript 中更严格的类型断言

[英]Stricter type assertion in Typescript

So lets's say I have所以让我们说我有

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

interface Example {
    a: number
    b: number
}

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

This does not report an error as that object extends Example and typescript is happy, so I end up using an ugly identity function:这不会报告错误,因为该对象扩展了Example并且 typescript 很高兴,所以我最终使用了一个丑陋的身份函数:

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

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

It irritates the heck out of me that I need to actually call an %^#% no-op function just to do a proper type cast.这让我很恼火,我需要实际调用 %^#% 无操作函数只是为了进行正确的类型转换。 I end up declaring it in every file that needs it just to give the js compiler better chances of optimizing it out.我最终在每个需要它的文件中声明它,只是为了给 js 编译器更好的优化机会。

Is there any ts magic I don't know about that can avoid the function call?是否有任何我不知道的 ts 魔法可以避免函数调用?

And yes, I know I can do this:是的,我知道我可以这样做:

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

and this:和这个:

declare function doSomething(arg: Example): any

that's really not the point here.这真的不是重点。 Consider the following lambda:考虑以下 lambda:

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

to properly set the type without resulting to the identity function I would need to write正确设置类型而不导致我需要编写的身份函数

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

which is no very DRY这不是很干

And yes, I can just write是的,我可以写

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

again, not the point再说一遍,不是重点

// edit // 编辑

as @thedude just blew my mind in the comment with the lambda return typeing syntax I did not know about, one more example where I use this casting因为@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[])

// edit 2 // 编辑 2

as it seems I ma terrible at explaining what I want, another example: this generic function solves the filter example-problem因为看起来我在解释我想要的东西方面很糟糕,另一个例子:这个通用函数解决了过滤器示例问题

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
]))

but requires defining a specialized function just for this specific task.但需要为这个特定任务定义一个专门的函数。 I am asking if there is a generic way to force a strict type check on a compile-time literal without resulting to a function call.我在问是否有一种通用方法可以强制对编译时文字进行严格的类型检查,而不会导致函数调用。 So exactly what cast<T> does but without the pointless call in the js output.所以cast<T>做了什么,但在 js 输出中没有毫无意义的调用。

I don't see why you need a cast function in any of these example.我不明白为什么在这些示例中的任何一个中都需要cast函数。 I think you may be overthinking this.我想你可能想多了。


Let's start here, which is perfectly fine as is.让我们从这里开始,这完全没问题。

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

The as keyword is still generally typesafe. as关键字通常仍然是类型安全的。 It lets you cast a value to supertype.它允许您将值转换为超类型。 In this case { a: number, b: number } is a supertype of { a: number, b: number, c: number } .在这种情况下, { a: number, b: number }{ a: number, b: number, c: number }的超类型。

For example:例如:

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

And in your first example, if you omit a required property you will have a type error:在您的第一个示例中,如果省略必需的属性,则会出现类型错误:

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

So then why does this provide an error?那么为什么这会提供错误呢?

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

Because when you create a value and assign it to a supertype, then you're only reference to that value will be of that supertype.因为当您创建一个值并将其分配给超类型时,您只会引用该值将属于该超类型。 Meaning whatever other properties may exist would be inaccessible.这意味着可能存在的任何其他属性都将无法访问。 Example does not have a c property. Example没有c属性。 So typescript assumes that's a mistake, and rightly so.所以打字稿认为这是一个错误,这是正确的。

But in real code, if you passed an object with a, b, and c to a function that only expect a and b, then nothing bad would happen.但在实际代码中,如果你将一个带有 a、b 和 c 的对象传递给一个只需要 a 和 b 的函数,那么就不会发生任何不好的事情。 You've satisfied the constraints, so there's no problem.您已满足约束条件,因此没有问题。


In this snippet:在这个片段中:

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

The cast is pointless.剧组毫无意义。 If doSomething cares about the type it receives, then it should declare that in its arguments.如果doSomething关心它接收的类型,那么它应该在它的参数中声明。 And if it doesn't care, then there is no need for a cast at all.如果它不在乎,那么根本不需要演员表。


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

This is basically the same as as Example from above.这与上面的as Example基本相同。


declare function doSomething(arg: Example): any

This is correct, and exactly what you should be doing.这是正确的,正是您应该做的。 If the function takes a specific type, let it enforce that on its own.如果函数采用特定类型,则让它自己强制执行。 You shouldn't need to cast anything if you are assigning it to any , since that cast will be lost anyway.如果您将它分配给any ,则不需要强制转换any ,因为无论如何该转换都会丢失。


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

If you want a function to return a type, you typically just annotate the return type.如果您希望函数返回类型,通常只需注释返回类型。 The following works fine:以下工作正常:

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

To properly set the type without resulting to the identity function I would need to write:要正确设置类型而不导致身份函数,我需要编写:

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

Incorrect, see previous example.不正确,请参见前面的示例。 One function, type is enforced as expected.一种功能,类型按预期强制执行。


Lastly:最后:

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

If you want a function that creates and returns an Example , this is perfect.如果你想要一个创建并返回一个Example的函数,这是完美的。 It properly alerts you that you have a property that could never be ever be used, and clearly documents the return value.它正确地提醒您,您有一个永远不会被使用的属性,并清楚地记录了返回值。


TLDR:域名注册地址:

None of your code snippets should require a cast<T>() function at all.您的任何代码片段都不应该需要cast<T>()函数。 I think your best bet is eliminate all occurrences of any and then just let Typescript enforce the types.我认为你最好的选择是消除所有出现的any ,然后让 Typescript 强制执行类型。 That's what it's there for.这就是它的用途。 The compiler is pretty good, and doesn't typically need that much help.编译器非常好,通常不需要那么多帮助。


Filtering过滤

Filtering is indeed a weird case in all this.在这一切中,过滤确实是一个奇怪的例子。 The problem stems from the fact that filter() is typed to return the exact same array type that it started with, regardless of what you filter.问题源于这样一个事实,即filter()被键入以返回与它开始时完全相同的数组类型,而不管您过滤什么。 The common solution is to make the filter callback function a type predicate function常见的解决方案是将过滤器回调函数设为类型谓词函数

Then you could do something like:然后你可以做这样的事情:

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 看游乐场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM