我可以这样简化我的代码:

function myFn<
    T extends 'a' | 'b' = any
>(
    valueType: T,
    value: T extends 'a' ? '1' : '2',
) {
    if (valueType === 'a') {
        value // value: DbType extends "a" ? "1" : "2"
    }
}

Typescript playground link

在这里,我预计在if (valueType === 'a')内时,value将被内插为‘1’,因为我们确定在这种情况下,它将扩展‘a’,因此value类型的判断将输出T extends 'a' ? '1' : '2'=&gt;'1'

你能解释一下为什么它不是这样工作的吗?在这种情况下,您能提供一种替代方案来获得预期的类型吗?

推荐答案

Generics and case analysis do not currently play nicely together

目前,TypeScrip不能使用control flow analysis来影响generic类型参数,比如在myFn的正文中使用T.这样做的主要障碍是,当类型参数是constrainedunion type T extends "a" | "b"时,not意味着T要么是"a",要么是"b".T也有可能是完整的联盟"a" | "b".目前还没有办法说T必须是一个unions 的one个成员.因此,myFn()可以这样调用:

myFn(Math.random() < 0.999 ? "a" : "b", "2"); // okay
// function myFn<"a" | "b">(valueType: "a" | "b", value: "1" | "2"): void

其中T被推断为"a" | "b",而T extends "a" ? "1" : "2"distributive conditional type,这意味着valueTypevalue都是不相关的联合类型.因此,没有什么能阻止某人用valueType"a"value"2"拨打myFn(),事实上,上面的例子有99.9%的机会这样做.

microsoft/TypeScript#27808有一个公开的功能请求,要求T必须恰好是unions 的一个成员.也许在将来的Typescript 版本中,您可以写成这样的内容

// NOT VALID TYPESCRIPT, DON'T TRY THIS
function myFn<T oneof 'a' | 'b'>(valueType: T, value: T extends 'a' ? '1' : '2') {
    if (valueType === 'a') {
        value // value: "1"
    }
}

myFn(Math.random() < 0.999 ? "a" : "b", "2"); // not allowed

但现在,它不是语言的一部分.除非这种情况发生变化,否则如果您判断像T这样的泛型类型的值valueType,它可能会对valueType的类型产生影响,但对T不会有任何影响.


Case analysis and discriminated unions do play nicely together

我们不能将泛型用于控制流.

如果您想要使用控制流分析来判断一个变量的值,并使其影响另一个变量的表观类型,您将需要第一个变量是discriminated union type的判别式,并且两个变量都需要是destructured.

在不 destruct 的情况下,它看起来可能像是一个相当标准的歧视联盟:

type AcceptableArgs =
    { valueType: "a", value: "1" } |
    { valueType: "b", value: "2" };

function myFn(arg: AcceptableArgs) {
    if (arg.valueType === 'a') {
        arg.value // "1"
    }
}

在这里你可以清楚地看到valueTypevalue之间的关系.或者valueType"a",value"1",或者valueType"b",value"2".

但想必您不想将对象传递给myFn.我们可以将AcceptableArgs设置为对应于myFn的参数listtuple type,而不是将其设置为普通对象类型.也就是说,我们将使AcceptableArgs成为myFn‘S rest parameter的类型:

type AcceptableArgs =
    [valueType: "a", value: "1"] |
    [valueType: "b", value: "2"];

function myFn(...[valueType, value]: AcceptableArgs) {
    if (valueType === 'a') {
        value // value: "1"
    }
}

我们已经将REST参数分解为valueTypevalue变量,因此它的行为完全符合您的要求.或者,它可以写成:

type MyFn = (...args: AcceptableArgs) => void;

const myFn: MyFn = (valueType, value) => {
    if (valueType === 'a') {
        value // value: "1"
    }
}

无论哪种方式,编译器都能够遵循valueTypevalue之间的关系,根据valueType的值将value缩小到"1""2".

请注意,当您调用该函数时,您的IDE会将其表示为overloaded,如下所示:

myFn(
//   ^-- 1/2 myFn(valueType: "a", value: "1"): void
//       2/2 myFn(valueType: "b", value: "2"): void

因此,从调用者的Angular 来看,它也应该是简单易用的.

Playground link to code

Typescript相关问答推荐

如何传递基于TypScript中可变类型的类型?

TS 2339:属性切片不存在于类型DeliverableSignal产品[]上>'

TypeScript将键传递给新对象错误

我们过go 不能从TypeScript中的联合中同时访问所有属性?

如何使用泛型类型访问对象

在映射类型中用作索引时,TypeScrip泛型类型参数无法正常工作

有条件地删除区分的联合类型中的属性的可选属性

使用打字Angular 中的通用数据创建状态管理

为什么有条件渲染会导致material 自动完成中出现奇怪的动画?

在数据加载后,如何使用NgFor在使用异步管道的可观测对象上生成子组件?

如何使用Geojson模块和@Types/Geojson类型解析TypeScrip中的GeoJSON?

扩展参数必须具有元组类型或传递给REST参数

为什么ESLint会抱怨函数调用返回的隐式`any`?

TypeScrip中的类型安全包装类

TypeScrip:从对象中提取和处理特定类型的键

React router 6(数据路由)使用数据重定向

对有效枚举值的常量字符串进行常规判断

如何在Nextjs路由处理程序中指定响应正文的类型

映射类型不是数组类型

Typescript,如何从对象的对象中获取分离的类型