英文:
Why does control flow analysis fail when using generics in typescript
问题
以下是您要求的翻译:
我有一个通用类型,允许对象中的任一属性为 `undefined`,但不允许同时都为 `undefined`:
```typescript
type RequireOne<T> =
| {
a: NonNullable<T>
b: NonNullable<T>
}
| {
a: NonNullable<T>
b: undefined
}
| {
a: undefined
b: NonNullable<T>
}
现在,如果我在声明函数时使用这个类型,它与具体类型一起工作得很好:
const test1 = ({ a, b }: RequireOne<string>): string => a ?? b
但如果我尝试将相同的函数也变成泛型,它会失败:
const test2 = <T>({ a, b }: RequireOne<T>): T => a ?? b
// 错误: 类型 'NonNullable<T> | undefined' 无法赋值给类型 'T'。
这是否是 TypeScript 的预期行为,如果是的话,为什么呢?
<details>
<summary>英文:</summary>
I have a generic type that allows for either of the properties in an object to be `undefined`, but never both:
type RequireOne<T> =
| {
a: NonNullable<T>
b: NonNullable<T>
}
| {
a: NonNullable<T>
b: undefined
}
| {
a: undefined
b: NonNullable<T>
}
Now, if I use this type when declaring functions, it works beautifully with concrete types:
const test1 = ({ a, b }: RequireOne<string>): string => a ?? b
But it fails if I try to make the same function generic as well:
const test2 = <T>({ a, b }: RequireOne<T>): T => a ?? b
// ERROR: Type 'NonNullable<T> | undefined' is not assignable to type 'T'.
Is this expected behavior from TypeScript, and if so, why?
</details>
# 答案1
**得分**: 2
一般来说,[泛型](https://www.typescriptlang.org/docs/handbook/2/generics.html)和[控制流分析](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#control-flow-analysis)不太兼容。在这种特殊情况下,看起来您遇到了[microsoft/TypeScript#50652](https://github.com/microsoft/TypeScript/issues/50652)中报告的问题,其中[联合类型](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types)在通用判别属性时不被视为[可判别联合类型](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)。
在这里,您试图通过检查`RequireOne<T>`的`a`属性是否为[nullish](https://developer.mozilla.org/en-US/docs/Glossary/Nullish)来进行判别,但类型检查器不会这样做。
由于已经有一个关于这个问题的问题报告,因此至少可以说这个行为是“已知的”,但这并不一定意味着它是“预期的”,因为该问题在[Backlog](https://github.com/microsoft/TypeScript/milestone/29)上,并标记为“需要调查”(截至2023年08/04)。所以它可能不会在一段时间内或永远解决。
但总的来说,TypeScript 实际上不能对泛型类型执行任意的高阶分析。[`NonNullable<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#nonnullabletype)在[TypeScript 4.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#improved-intersection-reduction-union-compatibility-and-narrowing)中等效于[交叉类型](https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types)`T & {}`。对于任何*特定*类型`T`,编译器会理解`T & {}`可以与`undefined`区分开来。但它不能将这种情况抽象为一般的语句:“对于所有类型`T`,`T & {}`可以与`undefined`区分开来”。除非有人将其硬编码到编译器中,否则您将遇到这个问题。
<details>
<summary>英文:</summary>
Generally speaking [generics](https://www.typescriptlang.org/docs/handbook/2/generics.html) and [control flow analysis](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#control-flow-analysis) don't play nicely together. In this particular case it looks like you've run into the issue reported in [microsoft/TypeScript#50652](https://github.com/microsoft/TypeScript/issues/50652) in which a [union](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types) is not seen as a [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions) when the intended discriminant property is generic.
Here you are trying to discriminate `RequireOne<T>` by checking the `a` property for [nullishness](https://developer.mozilla.org/en-US/docs/Glossary/Nullish), but the type checker doesn't do it.
Since there's an issue about it then the behavior is at least *known*, but that doesn't *necessarily* mean it's "expected" given that the issue is on the [Backlog](https://github.com/microsoft/TypeScript/milestone/29) and marked as "Needs Investigation" (as of today, 2023/08/04). So it might not get resolved for a while, if ever.
But again, in general, TypeScript really can't perform arbitrary higher order analysis on generic types. [`NonNullable<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#nonnullabletype) is [as of TypeScript 4.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#improved-intersection-reduction-union-compatibility-and-narrowing) equivalent to the [intersection type](https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types) `T & {}`. For any *specific* type `T`, the compiler will understand that `T & {}` is distinguishable from `undefined`. But it can't abstract such things to the general statement "for all types `T`, `T & {}` is distinguishable from `undefined`". Until and unless that is hardcoded into the compiler by someone, you'll have this problem.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论