typescript - 如何通知 Typescript 编译器已检查并定义了对象的每个值?
问题描述
我有以下对象,其值是动态检索的。每个函数调用(getFoo
、getBar
等)都可以返回一个值或undefined
.
let ExampleObj = {
foo: getFoo(),
bar: getBar(),
baz: getBaz(),
...
}
const getFoo:()=>Foo|undefined = () => {...}
...
根据对这个问题的回答here,我Array.every
用来检查对象中的每个值是否已定义,如果甚至有一个未定义的值,则返回未定义,即 -
type ExampleObj = {
foo: Foo
bar: Bar
baz: Baz
}
const returnsExampleObj:ExampleObj|undefined = () => {
let exampleObj = {
foo: getFoo(),
bar: getBar(),
baz: getBaz(),
}
return Object.values(exampleObj).every(val => val != undefined) ? exampleObj : undefined;
};
但是,Typescript linter 无法推断我已经执行了此检查,因此它抱怨我返回的类型无效(因为 ExampleObj 值永远不能未定义)。我只需要这样做//@ts-ignore
吗,还是有更好的方法来解决这个问题?
解决方案
编译器将无法查看您的实现并得出exampleObj
没有undefined
属性的结论。
虽然您作为人类理解类型保护出来的数组 Object.values(obj)
对 的类型有影响obj
,但没有办法在 TypeScript 中表达这种关系。一般来说,TypeScript 中的一个值的类型保护只能对该值本身的表观类型产生影响(或者,如果您正在检查类型为可 区分联合的对象的判别属性,它可能会影响该对象的明显类型)。虽然能够通过其他操作传播类型保护会很好,但如果您尝试实际实现它,它将对编译器性能产生毁灭性影响。如对microsoft/TypeScript#12185的评论中所述, 类似功能的请求,
这将要求我们跟踪一个变量的特定值对其他变量的影响,这将增加控制流分析器的大量复杂性(以及相关的性能损失)。
因此,如果编译器自己无法弄清楚,我们必须告诉它。
如果您只打算Object.values(obj).every(...)
在代码库中编写一次测试,那么您能做的最好的事情就是使用类型断言:
const returnsExampleObjAssert = (): ExampleObj | undefined => {
let exampleObj = {
foo: getFoo(),
bar: getBar(),
baz: getBaz(),
}
return Object.values(exampleObj).every(val => val != undefined) ?
exampleObj as ExampleObj : undefined;
};
通过编写exampleObj as ExampleObj
,我们是在对编译器说“请把exampleObj
它当作 type 的值来对待ExampleObj
”。编译器只是相信你,因为它无法以一种或另一种方式找出真相。所以要小心不要对编译器撒谎(例如,`Object.values(exampleObj).some(val => val != undefined) ? exampleObj as ExampleObj : undefined)。
如果您可能对不同的对象多次执行此测试,那么编写一个用户定义的类型保护函数,其返回类型是表单的类型谓词arg is Type
可能是有意义的。当您调用这样的函数时,编译器将理解true
结果意味着arg
可以缩小到Type
,并且false
结果意味着不能发生这种缩小(有时可能会发生不包括 Type
的不同缩小)。这是我可以为您的测试做的方法:
function allPropsDefined<T extends object>(
obj: T
): obj is { [K in keyof T]: Exclude<T[K], undefined> } {
return Object.values(obj).every(v => typeof v !== "undefined");
}
该函数allPropsDefined()
接受一个名为obj
类通用对象类型的参数T
。实现返回一个boolean
值;要么定义true
了所有obj
的属性,要么false
不正确。返回类型obj is { [K in keyof T]: Exclude<T[K], undefined> }
是一个类型谓词,可分配给boolean
。该类型{ [K in keyof T]: Exclude<T[K], undefined> }
是映射类型。它具有与 相同的键T
,但修改了属性;对于每个属性键K
,该键的属性类型T[K]
, 已通过实用程序类型从其中undefined
排除。所以如果是,那么是。Exclude
T[K]
string | number | undefined
Exclude<T[K], undefined>
string | number
让我们测试一下:
const returnsExampleObjTypePredFunc = (): ExampleObj | undefined => {
let exampleObj = {
foo: getFoo(),
bar: getBar(),
baz: getBaz(),
}
return allPropsDefined(exampleObj) ?
exampleObj // let exampleObj: { foo: Foo; bar: Bar; baz: Baz; }
: undefined;
};
现在编译没有错误。您可以看到,在三元条件运算符的 true 子句中,exampleObj
已从{foo: Foo | undefined, bar: Bar | undefined, baz: Baz | undefined}
to缩小{foo: Foo, bar: Bar, baz: Baz}
,可以根据需要分配 to ExampleObj
。
同样,如果您只进行一次甚至两次测试,类型谓词函数的开销可能不值得。但是,如果您可能在代码库中多次执行此操作,则类型谓词函数可能会收回成本。
同样,编译器无法验证您的类型保护功能是否正确实现。我可以更改every()
为some()
,编译器也会很高兴。编译器真正能检查的是返回类型是否匹配boolean
。所以我们还是要小心不要对编译器撒谎。
推荐阅读
- oracle-apex-19.1 - Oracle Apex 20.1 自动 DML 处理无法保存更新
- php - 当我知道一个条件依赖于另一个条件时,是否应该对 if 子句使用多个条件?
- javascript - 用于计算正方形面积的 HTML 中的 JS 代码。所有形状都包括面积,公式 = 2 * (a*b + a*c + b*c)
- oracle - 如何使用 oracle/postgres 查询从合格的类名中获取简单的类名?
- flutter - Flutter:如何在容器内的 ListView 上方添加文本行
- python - dict 包含不在字段名中的字段:无法将数据写入 CSV
- java - 向 sql 查询提供带有通配符的列值列表
- ios - FetchEvent.respondWith 收到错误:返回的响应为空
- python - C socket recv 函数返回未定义的错误(errno)
- android - 回收站视图中未正确显示非英语(乌尔都语)文本