【问题标题】:Cannot declare generic function possibly returns null无法声明泛型函数可能返回 null
【发布时间】:2021-11-28 10:51:32
【问题描述】:

我有一个泛型函数,它将返回一个泛型类型的对象或 null(可能未定义),所以我在返回类型中添加了 | null | undefined,但它似乎没有得到应用:

const test = <T>(obj: TypeA<T>): T | undefined | null => {
  return someObject[obj];
};

当我查看生成的函数定义时,我得到const test: &lt;T&gt;(obj: TypeA&lt;T&gt;) =&gt; T(没有 null 或未定义)。这也意味着如果我这样称呼它:

test<number>(someNullArg).toPrecision()

TypeScript 不会抱怨,即使它应该抱怨,即使代码在运行时抛出 Cannot read properties of null 错误。如果我替换泛型类型,则返回类型正确生成 (const test: (obj: TypeA&lt;any&gt;) =&gt; any | undefined | null)。

更新

这是实际实现的简化版本:

let value;

const get = <T>(obj: Promise<T>): T | undefined | null => {
  obj.then(val => value = val);
  return value;
}

它接受一个 Promise 并返回它的值(如果它被解决了)。它被设计用于a StencilJS component 以在同步渲染方法中输出异步值。基本上我正在尝试重新创建Angular's async pipe 的功能。

第二次更新

我已尝试进一步调试,我认为这可能是 TypeScript 中的错误。

我把它简化为一个更简单的例子:

function test<T>(): T | undefined | null {
  return (window as any).foo;
}

const x = test(); // type is "unknown"
const y = test<number>(); // type is "number"
y.toFixed(); // should throw a type error but doesn't

显然,一旦解析了泛型类型,| undefined | null 就会被完全忽略,我不知道为什么,因为返回类型非常具体。

Here's the entire source(重要的部分是async函数)。

【问题讨论】:

  • 什么是TypeA
  • TypeA 做什么?
  • 如果你使用--strictNullChecks,那么nullundefined会自动被吸收到任何联合中,它们本质上是never的同义词,参见@987654324 @。如果你想使用nullundefined 作为有意义的类型,你真的应该使用--strictNullChecks。如果启用它会给您带来一堆错误,那么您应该单独清除它们(但这超出了所问问题的范围)。如果您可能接受,我很乐意将其写成答案。否则,请告诉我哪些问题仍未得到解答。
  • @fedonev 你是对的(我实际上对此感到惊讶,我认为已经解决的承诺会同步调用 then() 回调,但事实并非如此)但就我而言,我实际上是强制一旦 Promise 解决,Stencil 组件将重新渲染,以便它获取已解决的值,因此它再次调用该函数。如果你有兴趣这里的代码:gist.github.com/tricki/e897f3e40c15fd4dfa089a0c942acf18
  • 任何关心nullundefined 的人都应该启用--strictNullChecks,因为这就是该功能的重点。我真的不知道如果您将其关闭会发生什么,但包含您的代码的人会打开它,反之亦然。我的建议是始终打开它......通常是整个--strict suite of compiler features 以获得“标准”数量的类型安全。然后nullundefined 将完全按照您的预期进行。不过,我仍在等待 github 完全活跃以找到相关问题。

标签: typescript typescript-generics


【解决方案1】:

为了正确处理这个问题,我建议您启用the --strictNullChecks compiler option 并处理它标记的任何随后出现的错误。 (我会更进一步说启用the entire --strict suite of compiler features 以从语言中获得“标准”数量的类型安全。)确实,TypeScript 手册says“如果可行,我们总是建议人们打开strictNullChecks所以在他们的代码库中。”

关闭--strictNullChecksthe null and undefined types 是所有其他类型的子类型;任何类型T 都将接受nullundefined 类型的值,因此形成Tunionnullundefined 仅相当于T。所以nullundefined 的行为与the never type 相同,并被吸收到所有联合中。这被称为“子类型减少”,TypeScript 做了很多事情。

只有打开--strictNullChecks,编译器才有机会跟踪nullundefined


您可以这样说,如果您使用the --declaration compiler option 发出.d.ts 声明文件以供其他人使用,包括您的代码作为模块,如果编译器急切减少@987654354,则发出的声明可能不准确@ 到T,因为消费应用程序可能已启用--strictNullChecks 启用,然后他们会误以为get() 总是从Promise&lt;T&gt; 返回T 而不是Promise&lt;T&gt;可能是undefinednull

你是对的。这被认为是 TypeScript 的设计限制,如 microsoft/TypeScript#18773 中所述。正如mentioned there,“当我们发出.d.ts 文件时,任何给定类型的nullness 早已不复存在。我们基本上不得不假装[--strictNullChecks] 是完全独立的编译阶段,因为它可能会改变任何类型的结果。”因为当--strictNullChecks 关闭而--declaration 开启时,这会导致编译时间加倍,所以他们不会这样做。

由于他们无法解决此问题,如果您正在为您的代码生成 .d.ts 文件,您真的应该打开 --strictNullChecks。不使用--strictNullChecks 的消费者不会在意任何一种方式,但那些使用的人会喜欢它。


无论如何,当--strictNullChecks 被禁用时,nullundefined 被吸收到联合中是按预期工作而不是错误。您可能想要提交功能请求以不那么急切地执行此操作,以便在更多情况下保留 nullundefined 类型,但我不希望 TS 团队实现它,即使如果没有立即拒绝。

本质上类似的问题是microsoft/TypeScript#29729,其中包含string 和其他字符串literal types(如"a" | "b" | string)的联合被缩减为仅string。有些人希望"a" | "b" | string 保持原样以用于自动完成和文档目的,但编译器将"a" | "b" | string 视为"a fancy way of writing string"。所以我希望任何形式的“使T | null | undefined保持原样用于文档目的”的请求都会被拒绝或关闭作为设计限制,并注意T | null | undefined只是“一种奇特的写作方式@987654383 @"。 ?‍♂️

【讨论】:

  • 感谢您的广泛回答!我仍然不完全理解它,因为直到现在我一直在相当随意地使用 TS,并且从未真正考虑过编译器配置。但这是阅读它的一个很好的理由。
【解决方案2】:

这个怎么样...

let value;

function get<T>(obj: Promise<T>): T | undefined | null {
  obj.then(val => value = val);
  return value;
}

【讨论】:

  • 谢谢,这实际上修复了返回类型!你知道为什么吗?在我的代码中,函数实际上是对象的属性(如const x = { get: &lt;T&gt;(obj: Promise&lt;T&gt;): T | undefined | null =&gt; { ... } }),但使用function 并没有帮助。
  • 我评论得太早了。悬停在 VSCode 中时的类型定义现在看起来是正确的,但是如果我实际使用该函数(例如,参数为 declare const x: Promise&lt;number&gt; | undefined | null;)并且泛型实际上已解析(而不是隐式 unknown)TS 假定总会有返回价值。
猜你喜欢
  • 2011-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-11
  • 1970-01-01
  • 1970-01-01
  • 2017-12-29
  • 1970-01-01
相关资源
最近更新 更多