搜索词:
代码
一个玩具示例是这样的。
let foo: string = "";
foo = "bar" ?? 123;
这个显然没问题,因为 "bar"
总是为真。
然而,当你考虑在使用 Record
对象之前检查它们的真值时,这就变得有点问题了。
const elts = ["foo", "bar", "spam", "spam", "foo", "eggs"];
const counts: Record<string, number>;
for (const elt of elts) {
// This really **should** raise an error.
counts[elt] = counts[elt] ?? "zero";
counts[elt] += 1;
}
预期行为:
应该引发错误。
实际行为:
奇怪的是,在严格模式下没有引发错误,但在非严格模式下引发了错误。
$ ./node_modules/.bin/tsc ./foo.ts
foo.ts:5:3 - error TS2322: Type 'number | "zero"' is not assignable to type 'number'.
Type '"zero"' is not assignable to type 'number'.
5 counts[elt] = counts[elt] ?? "zero";
~~~~~~~~~~~
Found 1 error.
$ ./node_modules/.bin/tsc --strict ./foo.ts
# No error, exits 0 and emits JS.
Playground链接:
Playground链接
切换 strictNullChecks
配置选项将显示问题。
相关问题:
8条答案
按热度按时间qxsslcnc1#
这可能与或重复的 #13778 有关。
当
--strictNullChecks
为真时,编译器假定counts[elt]
必须是类型number
,而它永远不会是null
或undefined
。如果你想让编译器注意到counts[elt]
可能为undefined
,那么你需要(目前)手动将undefined
包含在属性类型中:Playground
当
--strictNullChecks
为假时,编译器从不假定空合并会短路,因为它永远无法消除null
和undefined
。请参阅此 PR 中的评论。hk8txs482#
看起来这种事情可能存在一些“右结合性”(我不确定正确的术语来描述我的意思)。
我的意思基本上是这样的:
我认为算法大致如下:
A ?? B ?? C ?? ...
,取第一个对(空合并是左结合的,对吧?)A
和B
的类型A ?? B
的类型为NonNull<A> | B
w8biq8rn3#
&&
,||
和!
运算符也存在类似的问题。omqzjyyz4#
我刚刚遇到了这个问题,但我认为这是按照预期工作的。重新表述一下@jcalz发现的问题,问题描述中的示例展示了一个过于宽松的
Record
类型:在严格模式下,@typescript-eslint/no-unnecessary-condition 可以捕获 TS 在计算类型时的短路行为,并会告诉你:
如果你明确地将类型扩大以包含
null | undefined
,你将看到松散模式中的错误:考虑到一个 linter 能够指出潜在的意外行为,我不知道TS端是否有什么需要修复的地方。
1u4esq0p5#
我尊重地不同意(显然 - 我创建了这个问题😄)。在TypeScript中使用过于宽松的
Record
类型是一种常见的习惯用法,我认为改变这种行为使得这种习惯用法更容易使用。drnojrws6#
我认为以下几点可能与此问题有关:
这在你的系统依赖于备用值时可能是重要的。在当前的
??
类型下,系统会强制你定义一个 T2 备用值,因为如果你不定义一个,那么它的unknown
类型从可选变为非可选,即使在 T1 永远不可能是null
或undefined
的情况下,也会污染最终的返回类型。从我的Angular 来看,似乎
T1 ?? T2
的类型可以更精确地表示为T1 extends undefined | null ? NonNullable<T1> | T2 : T1
,而不是它当前的NonNullable<T1> | T2
值,但我猜想我可能在理解上有所遗漏?mfuanj7w7#
为了记录,这个:
具有"预期"的行为。
62lalag48#
似乎通过打开noUncheckedIndexedAccess配置来解决。