【问题标题】:Typescript conditional is null打字稿条件为空
【发布时间】:2020-10-05 13:26:21
【问题描述】:

我想创建一个函数,该函数接受一个通常是对象但可能为空/未定义的参数,如果它是非空的,则有条件地返回类型,否则为空。

function objOrNull<T>(t: T): T extends null ? null: T {
   if (t == null) return null;
   
   return t;
}

这个函数签名似乎是正确的,我可以用objOrNull({})objOrNull({a:42})objOrNull(null)objOrNull(undefined) 调用它,并且返回的变量具有预期的类型。 但是函数体无法编译,因为return nullreturn t 都不能分配给声明的返回类型。 如果我将返回类型声明为T | null,则该函数将编译,但每当我调用它时,我返回的类型可能为 null,这显然不是我想要的 - 应该可以在运行时确定如果 @987654330 @为null返回值也为null,否则不是?

没有添加as any 或类似的丑陋类型转换,是否有“正确”的方式来实现这样的功能?对于一个值,条件类型extends null ? 的等价物是什么?为什么== null 不告诉编译器我处于T extends null 条件中?

(我最初的用例是一个接受对象或 null 的函数,如果对象为 null,则返回 null,否则返回对象的变体,其中添加了一些属性并删除了一些属性,但将问题归结为我的核心问题。) 我原来的用例

【问题讨论】:

  • 你试过=== null吗?我怀疑== 可能会欺骗类型检查器,因为0undefined 和其他虚假事物的条件也是如此。
  • 对于为什么编译器无法验证依赖于未指定泛型类型参数的条件类型的可分配性,但您的函数没有按照它所说的那样做,有一个规范的答案。 undefined 不会扩展 null(假设您使用的是 --strictNullChecks)。也许您想要类似function objOrNull&lt;T&gt;(t: T): T extends undefined ? null : T { return (typeof t === "undefined") ? null : t; } 的东西?该代码有同样的问题,但至少实际上做了你所说的那样。

标签: typescript typescript-generics


【解决方案1】:

这基本上是 TypeScript 中当前的设计限制。编译器不使用control flow analysis 来缩小未指定的泛型类型参数 的类型(如您的函数实现中的T),也不使用它来缩小值的类型 依赖于此类类型参数(如函数实现中的t)。

这意味着编译器无法验证一个值是否可以分配给像T extends undefined ? null : T 这样的条件类型。即使您检查(typeof t === "undefined"),通常会将tstring | undefined 之类的具体内容缩小到undefined 的控制流分析对于T 之类的类型也不起作用。即使它确实将tT 缩小到(比如说)T &amp; undefined,它也不会将T 本身 缩小到undefined,这是必须发生的编译器来实现null 可分配给T extends undefined ? null : T

microsoft/TypeScript#33912 有一个规范问题,要求以某种方式让编译器使用控制流分析来验证返回值对通用条件类型的可分配性。但我不知道这是否或何时会得到解决(尽管这至少是一个好兆头,表明它是由 TS 团队的核心成员之一提出的)。


这意味着你现在能做的最好的事情就是使用type assertion(你称之为“丑陋的演员”):

function objOrNull<T>(t: T): T extends undefined ? null : T {
    return (typeof t === "undefined") ? null : t as any;
}

或道德等价物,例如单个呼号overload

function objOrNull<T>(t: T): T extends undefined ? null : T;
function objOrNull<T>(t: T): T | null {
    return (typeof t === "undefined") ? null : t;
}

这个重载不需要你使用类型断言,但是它是不安全的,就像使用类型断言一样。含义:无论哪种方式都应该可以使编译器平静下来,尽管它实际上并没有验证这里的安全性。这是你的工作,因为编译器做不到:

function badObjOrNull<T>(t: T): T extends undefined ? null : T {
    return (typeof t !== "undefined") ? null : t as any; // oops
}

function badObjOrNull<T>(t: T): T extends undefined ? null : T;
function badObjOrNull<T>(t: T): T | null {
    return (typeof t !== "undefined") ? null : t; // oops
}

但是,假设您正确实施它,它的行为仍应与调用方的预期一样:

const x = objOrNull(undefined); // null
console.log(x); // null

const y = objOrNull(Math.random() < 0.5 ? undefined : "hello"); // "hello" | null
console.log(y); // sometimes null, sometimes "hello"

Playground link to code

【讨论】:

    【解决方案2】:

    您可以使用重载来获得相同的结果

    function objOrNull(t:null|undefined):null
    function objOrNull<T>(t: T):T
    function objOrNull<T>(t: T|null):T|null {
       return t == null ? null : t;
    }
    

    Playground Link

    【讨论】:

    • 那么objOrNull(Math.random()&lt;0.5 ? "hello" : undefined);返回什么类型?
    • 它返回string | undefined
    • 但它不应该那样做,对吧?应该是string | null。它说它可以返回 undefined 而不是 null 的事实是一个问题。
    猜你喜欢
    • 2019-05-03
    • 2020-08-14
    • 2017-05-08
    • 2021-04-26
    • 1970-01-01
    • 2021-12-04
    • 2020-06-25
    • 2021-04-30
    • 2021-09-18
    相关资源
    最近更新 更多