假设您有一个函数抛出一个专门配置的错误:

function throwMyError(): never {
    throw new Error("My Custom error")
}

你有一个nullable value:

declare const str: string | null

您可以将该值缩小为不可为空的值,如下所示:

if(!str) throwMyError()
str.toLocaleLowerCase() // fine

现在把这个函数调用放在一个对象字面量中,它就不能再做同样的收缩了:

const foo = { throwMyError }
if (!str) foo.throwMyError()
str.toLocaleLowerCase() // 'obj' is possibly 'null'.(18047)

或者:

const bar = {
    throwMyError: (): never => {
        throw new Error()
    }
}
if (!str) bar.throwMyError()
str.toLocaleLowerCase() // 'obj' is possibly 'null'.(18047)

当从对象类型调用抛出函数时,为什么范围不起作用?

是否可以在不创建类型谓词函数的情况下解决此问题?

See Typescript Playground


相关问题:Type narrowing & never functions

但这似乎并没有解决这个问题,因为在所有情况下,函数类型is都显式注释为返回never

推荐答案

在实施PR,microsoft/TypeScript#32695中阐明了a never-returning function is treated as an assertion that affects control flow analysis的条件.它看起来是这样的:

在以下情况下,函数调用被分析为断言调用或永不返回调用

  • 该调用以顶级表达式语句的形式出现,并且

  • 该调用为函数名指定单个标识符或标识符点序列,并且

  • 函数名中的每个标识符都引用一个具有显式类型的实体,并且

  • 函数名解析为具有asserts返回类型或显式never返回类型批注的函数类型.

当实体被声明为函数、方法、类或命名空间,或者声明为具有显式类型批注的变量、参数或属性时,该实体被视为具有显式类型.(此特定规则的存在是为了使对潜在断言调用的控制流分析不会循环触发进一步的分析.)

foobar的问题是它们都是变量,没有显式的type annotation.您已经允许编译器推断它们的类型,这在几乎所有其他情况下都是完全合理的.但显然,它很难支持断言函数和never返回函数;据说它会导致类型判断器速度变慢和循环警告.


如果您希望foobar正常工作,则需要显式地对它们进行注释,如下所示:

declare const str: string | null;
const foo: { throwMyErr或(): never } = { throwMyErr或 };
if (!str) foo.throwMyErr或();
str.toLocaleLowerCase(); // okay

declare const str: string | null; 
const bar: { throwMyErr或(): never } = {
    throwMyErr或: (): never => { throw new Err或() }
};
if (!str) bar.throwMyErr或();
str.toLocaleLowerCase(); // okay

That's the behavi或 you wanted, and f或 the example code here it's not too much extra w或k. But in practice this restriction on type annotations makes using never-returning and assertion methods is a significant burden, as you find yourself needing to come up with explicit names f或 types that would otherwise be conveniently anonymous and inferred f或 you. Depending on the use case this can range from a min或 annoyance (const foo: Foo = ⋯) to a complete dealbreaker (const baz: Baz<Map<string, boolean>, "qux", [number, "hello"], Baz<Map<string, Date>, "quux", [boolean, "goodbye"], ⋯>> = ⋯). So proceed with caution.

Playground link to code

Typescript相关问答推荐

类型脚本中的Gnomeshell 扩展.如何导入.ts文件

如何在类型脚本中声明对象其键名称不同但类型相同?

如何将联合文字类型限制为文字类型之一

使用动态主体初始化变量

是否使用非显式名称隔离在对象属性上声明的接口的内部类型?

带switch 的函数的返回类型的类型缩小

在Typescript 中,有没有`index=unfined的速记?未定义:某个数组[索引]`?

推断从其他类型派生的类型

在Mac和Windows上运行的Web应用程序出现这种对齐差异的原因是什么?(ReactNative)

如何在使用条件类型时使用void

基于泛型的条件接口键

类型推断对React.ForwardRef()的引用参数无效

react 路由不响应参数

使用Cypress测试从Reaction受控列表中删除项目

基于属性值的条件类型

完全在类型系统中构建的东西意味着什么?

Svelte+EsBuild+Deno-未捕获类型错误:无法读取未定义的属性(读取';$$';)

如何将元素推入对象中的类型化数组

将对象数组传递给 Typescript 中的导入函数

递归地排除/省略两种类型之间的公共属性