首页 > 解决方案 > 尝试使用 Typescript 条件类型来缩小可以预期的值的可能组合

问题描述

我试图告诉 TypeScript,第一个函数参数中值的类型决定了第二个函数参数可以具有的类型。基本上,我想缩小可能的类型组合。

在特定示例中,null如果 1sr 参数是原始类型,我希望第二个参数是,Set如果第一个是对象,我希望它是(对象)。

对于那些想知道我为什么要这样做的人的背景故事:这是对递归stringify函数的圆检测的优化。seenObjects仅当对象是对象时才需要为不同的分支克隆集合,以允许跨实际上不形成圆圈的分支进行复制。

这一切都适用于外部的函数,即调用函数的代码。在下面的示例中,四个调用stringify完全按预期工作。

但是,在函数内部,条件类型似乎没有效果。尽管检查了第一个参数,但TS 抱怨第二个参数可能存在null,并且该检查与条件类型相结合 - 如果在那里使用 - 将第二个参数的类型限制为Set,而没有null.

代码

TS PlayGround Link(您必须启用 strictNullChecks)

function isObject (thing: unknown): thing is Record<string, any> {
    return typeof thing === 'object' && thing !== null;
}

function stringify<T extends unknown>(
    obj: T,
    seenObjects: T extends Record<string, any> ? Set<Record<string, any>> : null
): string {
    if (isObject(obj)) {
        seenObjects.add(obj);  // ERROR (bad)
    }

    return 'The End';
}

// No errors (good)
stringify(42, null);
stringify([], new Set());

// ERRORS (good)
stringify([], null);
stringify(42, new Set());

问题

我可以添加额外的类型细化检查并完成它。

但是,我很好奇是否有人知道在不添加代码的情况下实现预期结果的方法。在我的实际代码中,第二个参数的值是硬编码的,取决于第一个参数,所以我似乎可以通过静态类型分析来解决这个问题,而不是通过在运行时完全不必要地运行的附加代码。


PS:有趣的是,由于我只是将这个代码库从 Flow 切换到 TypeScript,在 Flow 中我可以通过给它注释代码来“破解”Flow,例如类型细化检查,/*: …. */其中 Flow 将解释为“实时”代码。所以我可以通过给它它想要查看类型检查的代码来让 Flow 安静下来,而无需实际将它放入运行时,因为它在注释中。


更新:TS错误?

当我将if条件从检查obj更改if (seenObjects !== null)为错误时seenObjects(“可能为空”)仍然存在!尽管该代码现在正在显式检查null?

标签: typescriptconditional-types

解决方案


TypeScript 的控制流分析无法理解不同变量何时具有相关类型。如果你缩小一个变量的类型,它不会影响另一个变量的类型。此外,TypeScript 很难对未解析的条件类型进行类型分析,因此即使您更改代码来分析seenObjectsintead 的类型obj,您仍然可能会遇到一些类型错误。

而且您似乎对添加额外的运行时检查以使编译器相信一切都是安全的并不感兴趣。

好吧:当您有疑问并且比编译器更聪明时,您始终可以使用类型断言来安抚编译器,而无需更改发出的 JavaScript:

function stringify<T extends unknown>(
    obj: T,
    seenObjects: T extends Record<string, any> ? Set<Record<string, any>> : null
): string {
    if (isObject(obj)) {
        (seenObjects as Set<Record<string,any>>).add(obj); // type assertion 
    }    
    return 'The End';
}

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


推荐阅读