- 由于TypeScript 3.0在2018年年中引入了
unknown
top type,因此不鼓励使用any
类型。 - TypeScript也长期支持使用
typeof
运算符的succint类型保护,但typeof
仅用作标量值(即单个变量)的第一类类型保护。 - 主要的警告是如果不先使用
as any
或as T
,它不能与对象属性或数组元素一起使用。 - 使用
as any
有明显的问题。 - 但是使用
as T
也会带来它自己的问题。在类型保护函数中这不是一个大问题,因为变量的作用域仅限于类型保护,但是如果在普通函数中使用,它会引入bug。
我目前正在用TypeScript编写客户端错误处理代码,特别是,我正在为window.error
编写一个事件侦听器,它接收一个ErrorEvent
对象,该对象又有一个名为error
的成员属性,实际上,根据不同的情况,它可以是任何东西。
在TypeScript中,我们需要编写作为运行时和编译时类型保护的顶级函数。例如,为了检查window.error
事件侦听器是否真的接收到ErrorEvent
而不是Event
,我会这样写:
function isErrorEvent( e: unknown ): e is ErrorEvent {
// TODO
}
function onWindowError( e: unknown ): void {
if( isErrorEvent( e ) ) {
// do stuff with `e.error`, etc.
}
}
window.addEventListener( 'error', onWindowError );
我的问题是关于我如何按照TypeScript语言设计者的意图实现isErrorEvent
。我还没有找到任何关于这个主题的权威文档。
具体来说,我不知道如何使用运行时typeof
检查来实现isErrorEvent
,而不使用any
或目标类型ErrorEvent
的类型Assert。这两种技术都是必需的,因为当y
不是x
的静态类型的一部分时,TypeScript将不允许您使用typeof x.y
。这让我觉得很奇怪,因为TypeScript * 确实 * 让你在x
是 any 类型的标量时使用typeof x
,而不仅仅是它的静态类型。
下面,使用as any
工作,但我不喜欢asAny.colno
属性取消引用缺乏安全性:
function isErrorEvent( e: unknown ): e is ErrorEvent {
if( !e ) return;
const asAny = e as any;
return (
typeof asAny.colno === 'number' &&
typeof asAny.error === 'object' &&
typeof asAny.lineno === 'number'
);
}
另一种选择是使用as ErrorEvent
,但我觉得这同样不安全,因为TypeScript然后 * 允许 * 取消引用e
* 的成员,而无需事先检查typeof
*!
function isErrorEvent( e: unknown ): e is ErrorEvent {
if( !e ) return;
const assumed = e as ErrorEvent;
return (
typeof assumed.colno === 'number' &&
typeof assumed.error === 'object' &&
typeof assumed.lineno === 'number' &&
// For example, TypeScript will not complain about the line below, even though I haven't proved that `e.message` actually exists, just because `ErrorEvent.message` is defined in `lib.dom.d.ts`:
assumed.message.length > 0
);
}
我想我问的是如何让这样的东西(见下文)工作,其中TypeScript要求在允许任何解引用之前使用typeof
检查每个成员,并允许e
将其静态类型保留为unknown
:
function isErrorEvent( e: unknown ): e is ErrorEvent {
if( !e ) return;
return (
typeof e.colno === 'number' &&
typeof e.error === 'object' &&
typeof e.lineno === 'number' &&
typeof e.message === 'string' &&
e.message.length > 0
);
}
...但是TypeScript * 确实 * 让我们做到了这一点(见下文),这可以说是同样的事情,只是在语法上更加冗长:
function isErrorEvent( e: unknown ): e is ErrorEvent {
if( !e ) return;
const assume = e as ErrorEvent;
if(
typeof e.colno === 'number' &&
typeof e.error === 'object' &&
typeof e.lineno === 'number' &&
)
{
const message = assume.message as any;
return typeof message === 'string' && message.length > 0;
}
}
2条答案
按热度按时间efzxgjgh1#
类型保护是我发现
any
可以接受的少数地方之一。A | B | C
),并缩小并集(例如,B
)。在前一种情况下,您可以轻松地在类型系统的范围内工作以缩小范围。
在后一种情况下,你有不同数量的“shapeless”来处理,但在极端情况下(比如你的
unknown
),你没有类型支持,这会导致一些看起来有点丑陋的东西。请看这里:Playground链接
我想明确一点-这段代码所做的所有操作都是 * 正确的 *。如果
e
不是一个对象,你就不能检查它是否有一些任意的属性。如果不检查属性是否存在,检查一个任意的属性值是否是一个给定的类型是有点没用的。话虽如此,它是过于冗长,也有点迟钝。
e !== null
是无用的,因为它在一开始就已经被!e
处理了。因此,我个人很乐意将
e
键入为any
。Playground链接
对我来说,上面的代码更容易阅读和理解。它不像编译器那样严格检查,但它也是完全正确的。当使用
any
时,它的行为也完全相同,因此我不反对它。只要你做适当的检查,你有一个对象,不管是Record
还是any
都没有什么关系。无论哪种方式,你都不会从编译器获得任何类型支持。后者在类型方面稍微正确一些,但是否有区别取决于你。注1:你也可以使用类型Assert
e as Record<PropertyKey, unknown>
。这没什么关系,但额外的isObj
类型保护似乎更有可能被重用。注2:仅供记录,
hasProp
可以更改为应用于多个属性。它并没有解决我在类型保护中使用它的核心问题,但它可能在其他地方仍然有用:Playground链接
bwntbbo32#
//例如,TypeScript不会抱怨下面这行,即使我没有证明
e.message
实际存在,仅仅因为ErrorEvent.message
是在lib.dom.d.ts
中定义的在这种情况下,你不应该试图证明它是某个具体类的示例,而只需要定义一些特定的,狭窄的特性子集,你实际上有兴趣使用,然后对特定的形状进行类型检查。
例如,你并不真正感兴趣,如果
e
是一个实际的ErrorEvent
示例,你只关心它是否符合某种狭义的契约:现在你只需要为那个确切的合约设置类型保护。我想到的最好的解决方案是使用一种方法,类似于@VLAZ的方法,只需要在
isObj
助手中添加一个泛型来进行推理,以在编写缩小类型检查时防止打字错误: