编写类型保护时,用“typeof”替代“any”的(正确)惯用方法是什么?

2024-02-04

  • 自从 TypeScript 3.0 引入unknown top type2018 年中期,使用any类型不鼓励。
  • TypeScript also had had long support for succint type-guards with the typeof operator, but typeof only works as a first-class type-guard for scalar values (i.e. single variables).
    • The major caveat being that it cannot be used with object properties or array elements without first using as any or as T.
      • Using as any有立即明显的问题。
      • 但使用as T也引入了其自身的问题。这不是这么大的问题在类型保护函数内,因为带有假定类型的变量的范围仅限于类型保护,但如果在普通函数内使用,则可能会引入错误。

我目前正在用 TypeScript 编写客户端错误处理代码,特别是,我正在编写一个事件监听器window.error,它接收到一个ErrorEvent对象又具有名为的成员属性error which 在实践中根据不同的情况,可以是任何东西。

在 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 );

我的问题是我应该如何做惯用地实施isErrorEventTypeScript 语言设计者想要的方式。我无法找到有关该主题的任何权威文档。

具体来说,我不知道应该如何使用运行时typeof检查以实施isErrorEvent不使用类型断言any或目的地类型ErrorEvent。据我所知,这两种技术都是必需的,因为 TypeScript 不允许您使用typeof x.y when y不属于x的静态类型 - 这让我觉得很奇怪,因为 TypeScriptdoes让你用typeof x when x是一个标量any类型,而不仅仅是其静态类型。

下面,使用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 那么allows解除对以下成员的引用e 没有事先typeof check!

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
    );
}

...但是 TypeScriptdoes让我们这样做(见下文),这可以说是同一件事,只是语法上更加冗长:

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是完全可以接受的。根据参数的不同,您基本上有两种类型的类型保护

  • 他们需要很多东西,通常是一个联合(例如,A | B | C)并缩小联合范围(例如,B).
  • 他们拿了一些不为人知的东西这是什么并赋予它形状。

在前一种情况下,您可以轻松地在类型系统的范围内工作以缩小范围。

在后一种情况下,您需要处理不同程度的“无形状”,但在极端情况下(例如您的unknown)你没有类型支持,这会导致看起来有点难看。看这里:

type HasProp<T extends object, K extends string> = T & {[P in K]: unknown};

/*
 * type guard to ensure that an arbitrary object has a given property
 */
function hasProp<T extends object, K extends string>(obj: T, prop: K): obj is HasProp<T, K> {
    return prop in obj;
}

function isErrorEvent( e: unknown ): e is ErrorEvent {
    if( !e ) return false;
    
    if (
        typeof e === "object" && //type guard determines `e` is `object | null`
        e !== null               //type guard to narrow down `e` to only `object`
    ) {
        if (
                hasProp(e, "colno") &&   //type guard to add `colno` to the properties known to be in `e`
                hasProp(e, "error") &&   //type guard to add `error` to the properties known to be in `e`
                hasProp(e, "lineno") &&  //type guard to add `lineno` to the properties known to be in `e`
                hasProp(e, "message")    //type guard to add `message` to the properties known to be in `e`
            ){
                return (
                    typeof e.colno  === 'number' &&
                    typeof e.error  === 'object' &&
                    typeof e.lineno === 'number' &&
                    
                    typeof e.message === 'string' &&
                    e.message.length > 0
                );
        }
    }
    return false;
}

我想清楚 - 这段代码所做的所有操作都是correct。您无法检查是否e如果它不是对象,则具有一些任意属性。如果不检查属性是否存在,则检查任意属性值是否为给定类型是没有用的。

话虽如此,它过于冗长而且有点迟钝。

  • The e !== null没有用,因为它已经被处理了!e在一开始的时候。
  • 检查属性是否存在以检查其值是否为数字与检查值是否为数字直接等效。通常没有区别 - 如果属性不存在,其值是不同的类型,那么最终都是相同的。

因此,我个人很乐意输入e as any。如果你想在两者之间达成妥协some类型安全并且编写的代码较少,那么您可以将其用作Record https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype

function isObj(obj: unknown): obj is Record<PropertyKey, unknown> {
    return typeof obj === "object" && obj !== null;
}

function isErrorEvent( e: unknown ): e is ErrorEvent {
    if ( isObj(e) ) {
        return (
            typeof e.colno  === 'number' &&
            typeof e.error  === 'object' &&
            typeof e.lineno === 'number' &&
            
            typeof e.message === 'string' &&
            e.message.length > 0
        );
    }

    return false;
}

游乐场链接 https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABDAzgeQEYCsAUdsBci4A1mHAO5gCUR+WyKiASgKYRwBOAJgDwAKnOAAdWnKAE8A0qwkAaYmDKUwAPkQBvAFCJdiTqyghOSSaLjBE9RAF47iAET12UB4gBk7q9kQBCe2AgADZBANxaAL5aWqCQsAiMAKKcQpyJAG6sYFA4iKxEpORUiLR5jIjJqRlZUJo6ejCWuaiYuKzUJXV63fqGxkg49T09ZqwWeQB0HEHkunY2iADkgQC2GGKLHu5Dw3qj46wTYqlz9ovO0JueO7uI+5aHQTBgWXC2Z6vrnFfbt903u3ukxWrBQKAAhgBzVjvBaLFBQTjPSE-AHDQ4gsFQx5ZSFQAAWiHUAAY0SVwt0ojsDEYTIhgOCgihWOEIkA

对我来说,上面的代码更容易阅读和理解。它没有经过编译器严格检查,但也是完全正确的。这也是使用时的行为完全相同any https://www.typescriptlang.org/play?#code/C4TwDgpgBA8gRgKwgY2AaQiKBeKBnYAJwEsA7AcygB8pSBXAWzgkOvxCYHsAbAbgCh+AMzqlUxTqSjE88BAApOiAFxRRAa1KcA7qQCUqpQml4oAQ1JYA3lAD0tgDwBaF09qdg5qACUUnQgAm-FAhUIQQwHSEUqCQnEJQRjjYuABERijAqVAAZDmJiFAAhCm0dNx8-AC+giJiwBJSMgCihIT+zQBuEKTA8lAQqhpaulAGAyZQre2EXT2eVsGhxAn9MnLyEHpjUIuh+2ERUVLySwcHsRDxAwB0yDxaISm4AOT0TCwvuTln56GX1wgNxYMyepReGVQXzyvz+UABCSB3DIPU4yVe72YhGhPzh+1hfwRtwYEDweDM5GgzygLwIJAoOIJ5yBJLJFKRPXIwAAFlAAHxQAAMTLGAn2NV+4Ui0SgQjM3DwEAEVSAA这就是为什么我不反对它。只要你对你拥有的对象进行适当的检查,它是否存在就无关紧要了Record or any。无论哪种方式,您都不会从编译器获得任何类型支持。后者在类型方面稍微正确一些,但这是否会产生影响取决于您。


注1:您还可以使用类型断言e as Record<PropertyKey, unknown> https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABDAzgUQE4bhtA3AUzCgApECAuRcAazDgHckBKKg5FRTbXQ4xAN4AoRKOTBEZdgDJpiKAE8ADgTgT2AXi2IARHABGAKwLQdiZoJFjrEBCiiIDhxBvKIAhpwBKJnABMAHgAFbBUMRQBpAgUAGmowOkYwAD4AbitrUQwCKBAMJBIMzOtFFTVHIwA6WwAbelEtVwByMBAAW30CDCbEWSLi0VLVCSdKrp4G7SanEygevoHMofLRmpgwIjgXKdaOrvnpfoGj4uWRqraCFBR3AHNNKfsMdduDk8zRy+u7gkqaoluUAAFohkogAAzvZjpawAXyERWyuXyiGA7hqKAI6VhQA。没什么大不了的,但是额外的isObj类型保护似乎更有可能被重用。


注 2:仅供记录,hasProp可以更改以应用于多个属性。它并没有解决我在类型保护中使用它所遇到的核心问题,但它在其他地方可能仍然有用:

/*
 * type guard to ensure that an arbitrary object has a given properties
 */
function hasProps<T extends object, K extends PropertyKey>(obj: T, ...props: K[]): obj is HasProp<T, K> {
    return props.every(prop => prop in obj);
}

/* ... */
if (hasProps(e, "colno", "error", "lineno", "message")) { //type guard to add `colno`, `error`, `lineno`, `message` to the properties known to be in `e`
/* ... */

游乐场链接 https://www.typescriptlang.org/play?#code/C4TwDgpgBAEghgZwAoCcD2YA8AVKEAewEAdgCYJRoBGAVhAMbAA0UA0noSeVKhhCqFYQQAPigBeKLgBkUAN4BtJFACWxNgF0AXFACuxANbE0Ad2IBfANwAoawHoAVNagOooSFADmuuClJu0PGIEXRRoYAALOGAoOHVfKhVgFF8QSloGGKiKOC8VADcSKDB0SAEQZwc7awAzfUYVNHVs3jAEHA4iMgpqOkYWdgIu7lb+QWERAApenWwWADpFkowEHVYFDQBKHV7VCnhkUpwBsTlnKAuw4FD1Zbb5iEKUEEm7iTE3tXSaTZtzWzqxAaTT2AFEUOgUKDCsRgJM8Dp9EZTOptng9lBwZDoSQYmcLhcVDV4QBCaCbKBXG5QGpwAA2CAgNgJUHOhJqUEmbJZbnAEDQHOg4mFUAARL1MqKoNJZHY7O5oN5fP5SBAiCgALZqCAUAAGEF1GN1EsYUAAPlBiLo6XTddyWdASSKrTaeW6WXKFV4fH4ApbfOgTFBSCioPrDcBAk06WljRlGHaWRT8e6iZyWqUEJMICxRfQ0HTjKLc-xIcWxXTtUXcxqdQg4J4IKLNsmoJ6+d7lX64KR-Lr84W0LqWPqIWgUMOw5XiCQhyPawh642I4FItA7mMVDqoMizH6qNAvuH7e6WVSUOouafrwSFQK8PMB8YLsLJAByK0ag8oN-S6Qnm8WTvQUHjHFAXxFN8TWAX8ZQAwCLmAh9p1nCRIM-b9YP-BDT3ghCkIgeYFyXIVIIQZI1E8LC8MAwjiIbQi6RITxIigMQAAYaLdX54P+Fk+IJc91FpBkmWscwgA

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

编写类型保护时,用“typeof”替代“any”的(正确)惯用方法是什么? 的相关文章

随机推荐