【发布时间】:2021-05-19 00:14:31
【问题描述】:
- 由于 TypeScript 3.0 在 2018 年年中引入了
unknown顶级类型,因此不鼓励使用any类型。 - TypeScript 也长期支持带有
typeof运算符的简洁类型保护,但typeof仅作为标量值(即单个变量)的一流类型保护。- 主要的警告是它不能与对象属性或数组元素一起使用,除非先使用
as any或as T。- 使用
as any会立即出现明显的问题。 - 但是使用
as T也会引入它自己的问题。这在类型保护函数中并不是一个大问题,因为带假定类型的变量的范围仅限于类型保护,但如果在普通函数中使用它可以引入错误。
- 使用
- 主要的警告是它不能与对象属性或数组元素一起使用,除非先使用
我目前正在使用 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 );
我的问题是关于我打算如何惯用实现isErrorEvent 的TypeScript 语言设计者打算的方式。我还没有找到任何关于这个主题的权威文档。
具体来说,我不知道我应该如何使用运行时typeof 检查来实现isErrorEvent,而不使用any 或目标类型ErrorEvent 的类型断言。据我所知,这些技术中的任何一种都是必需的,因为当 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;
}
}
【问题讨论】:
-
嗯,这是
any还不错的地方之一。类型守卫经常收到 something 并且它必须确定它是否满足成为 something else 的标准。你通常不能静态地做到这一点,否则你并不需要类型保护。在某些情况下,您可以像A | B的联合一样执行此操作,并希望为A键入保护,但对于任意值,它本质上是不安全的。您可以键入 asserte as Record<string, any>如果它让您感觉更安全,但我不确定是否有一种方便的方法可以在这里完全安全。 -
@VLAZ 是的,但我很惊讶
typeof x.y(x是unknown)是 TypeScript 不允许的——所以我想知道我是否忽略了一些简单的东西。我想我可以检查typeof object以允许使用任意字符串属性索引器... -
您 可以 更加类型安全并满足编译器的要求,但 IMO 在类型保护中这样做是没有用的。它导致了比较丑陋的代码,这些代码只是为了满足编译器的需要。 See this example。您需要一个 typeguard 来告诉编译器您正在检查的属性可以被检查,然后检查该属性。我觉得它很奇怪而且很迂回。
标签: typescript type-safety typeguards