首页 > 解决方案 > 为什么强制类型转换的类型安全性低于显式类型变量声明?

问题描述

我喜欢打字安全,这就是我喜欢打字稿的原因。

但是,我发现最终不得不显式声明额外变量以获得所需的类型安全的情况。


更新示例:

假设我想在一个字符串数组上调用“.join”,并且我真的想确保它是一个字符串数组(而不是说对象,它最终会输出为[object Object]):

如果我只是在这里执行此代码,它不会阻止我意外传入一个对象somethingElse

如果我为 创建一个数组urlParts并将其键入为string[],我可以防止该问题:

const baseUrl = "https://stackoverflow.com/"
const somethingElse = { not: "a string" };
const urlParts: string[] = [baseUrl, somethingElse];
window.location.replace(urlParts.join("\n"))

但是,我似乎没有办法在不创建单独的urlParts变量的情况下做到这一点,而我一直希望避免这种情况。例如,通过以下之一:

window.location.replace(([baseUrl, somethingElse] as string[]).join("\n"))
window.location.replace((<string[]>[baseUrl, somethingElse]).join("\n"))

我发现避免创建变量的唯一方法是将结果内联为自执行匿名强类型函数——但这似乎是一个荒谬的解决方法:

window.location.replace(((): string[] => [baseUrl, somethingElse])().join("\n"))


TS Playground 中的实验


这是 TypeScript 错误/差距吗?还是我错过了什么?

我问了几个同事,看看他们是否有什么想法,但我们无法弄清楚如何获得更严格的类型。

谢谢!

标签: typescript

解决方案


类型断言不如类型注释安全,因为虽然注释和断言都允许您扩大表达式的类型,这对于阅读是安全的,但只有类型断言允许您缩小表达式的类型,这对于阅读是不安全的。编译器允许您这样做,因为您断言它是正确的。

举个例子……假设给你一个xtype的变量string。如果您只是读取该变量的值,则可以安全地将变量视为更广泛的类型,例如string | number. 这是因为 type 的所有值也是typestring的值。所以两者都是有效的,因为编译器可以验证这些表达式的有效性。string | numberlet y: string | number = xx as string | number

另一方面,如果给定一个类型为 的变量y,则将该变量视为更窄的类型(如string | number)是不安全的。那是因为有 type 的值不是type的值。所以会报编译错误;编译器知道将 a视为 a是不安全的。但是是允许的,因为你告诉编译器你知道的比它多。有时编译器会推断出比您所能推断的更宽的类型,例如. “显然”将是 a ,但编译器不会(从 TS3.3 开始)注意到只有三元运算符的真正分支将被评估,因此它推断stringstring | numberstringlet x: string = ystring | numberstringy as stringlet y = true ? "a" : 1;ystringstring | numbery. 所以你可以断言y as string是安全的,编译器会相信你。

此外,正如评论中所述,术语“类型断言”优于“类型转换”,通常是因为“类型转换”有时意味着一些运行时效果。TypeScript 的类型系统在运行时被完全擦除,因此只有在设计时类型系统才会做任何事情。断言是您告诉编译器不要担心如果它无法验证特定表达式是否属于特定类型。如果你的断言是正确的,那么一切都很好。如果您的断言不正确,那么在设计时一切似乎都很好,然后您将获得任何运行时后果发生在您身上。

类型断言并非完全不安全;x as T如果推断的类型x既不窄也不宽于 type ,编译器仍然可能拒绝断言T。例如,如果您有一个z类型的变量string并且您编写z as number编译器仍然会警告您。在这种情况下,您仍然可以强制编译器提交,方法是您始终可以先扩大到一个公共超类型或缩小到一个公共子类型,然后再继续。该类型unknown是所有类型的通用超类型,因此您始终可以编写z as unknown as number并消除编译器的任何可能的反对意见。

另请注意,x as T类型断言的语法与语法完全相同<T> x。有两种写法的唯一原因是尖括号不能很好地与 JSX 配合使用

好的,希望有帮助;祝你好运!


更新:我认为以内联方式获得类型注释安全性的最不乏味的方法是使用辅助函数。这并不像您使用的 IIFE 那样疯狂,但它是相似的。这是我的做法:

const ann = <T>(x: T) => x;

该函数ann(“annotate”的缩写)允许您显式地注释传入参数的类型。让我们来看看:

window.location.replace(ann<string[]>([baseUrl, somethingElse]).join("\n"))
// error on somethingElse, as expected

这对你来说足够好吗?


推荐阅读