Typescript 在函数中使用条件类型

Typescript 在函数中使用条件类型,typescript,conditional-types,Typescript,Conditional Types,我试图使用条件类型链接函数中的两个参数,但在定义函数时,我无法排序链接两个参数。下面是简化的示例或示例 type Circle={type:“Circle”;数据:{radius:number}; 类型Rect={type:“Rect”;数据:{width:number;height:number}; 类型定义形状=圆形|矩形; type FilterShapeByType=形状扩展{type:type} ? 形状 :从不; 类型DrawShape=( 类型:T, 数据:FilterShapeB

我试图使用条件类型链接函数中的两个参数,但在定义函数时,我无法排序链接两个参数。下面是简化的示例或示例

type Circle={type:“Circle”;数据:{radius:number};
类型Rect={type:“Rect”;数据:{width:number;height:number};
类型定义形状=圆形|矩形;
type FilterShapeByType=形状扩展{type:type}
? 形状
:从不;
类型DrawShape=(
类型:T,
数据:FilterShapeByType[“数据”]
)=>无效;
设drawShape1:DrawShape=()=>{};
drawShape1(“rect”,{高度:12,宽度:22});//通过
drawShape1(“rect”{高度:12,宽度:22,半径:23});//失败,未定义半径
drawShape1(“圆”{半径:12});//通过
drawShape1(“圆”{半径:12,长度:23});//失败,未定义len
常量drawShape2:DrawShape=(类型、数据)=>{
开关(类型){
案例“圆圈”:
console.log(data.radius);//失败,类型“(FilterShapeByType | FilterShapeByType)[“数据”]”上不存在属性“radius”。
打破
案例“rect”:
console.log(data.height,data.width);
打破
}
};
最初的目标是链接
DrawShape
函数中的两个参数,这意味着当
类型
时,
数据
应该是
{radius:number}
。我使用条件类型
FilterShapeByType
对其进行排序

在我尝试定义函数之前一切都很好
drawShape2
。我希望当我为
类型使用开关时,对于
圆圈
,类型脚本应该足够聪明,可以假设
数据
应该是
圆圈['data']
,但它不是。想知道有没有容易自动完成的


有一个窍门。不要告诉任何人:D

只需使用命名元组


函数drawShape2(…参数:[类型:'circle',数据:circle['data']]]|[类型:'rect',数据:rect['data']]{
开关(参数[0]){
案例“圆圈”:
const[type1,data1]=args;
打破
案例“rect”:
常量[type2,data2]=args
打破
}
};

我很好奇为什么你的代码不起作用。看起来应该行得通,但不行!我可能遗漏了什么,或者是打字脚本的限制

无论哪种方式,我都喜欢(使用)以下变通方法:

类型形状={
圆:{radius:number},
rect:{宽度:数字;高度:数字}
}
类型ShapeTypes=keyof形状;
类型DrawShape=(类型:T,数据:形状[T])=>void;
常量isShapeOf=(应为:T,类型:string,数据:any):数据是形状[T]=>{
预期返回===类型;
}
const drawShape:drawShape=(类型、数据)=>{
if(isShapeOf(“圆圈”、类型、数据)){
data.radius;//通过
}else if(isShapeOf(“rect”,类型,数据)){
data.radius;//半径不存在
data.width;//通过
data.height;//通过
}
};
drawShape(“rect”,{高度:12,宽度:22});//通过
drawShape(“rect”,{高度:12,宽度:22,半径:23});//失败,未定义半径
drawShape(“圆”{半径:12});//通过
绘图形状(“圆”{半径:12,透镜:23});//失败,未定义len

原因

强制类型脚本不是这样工作的

让我们仔细看看
drawShape2
签名中的
数据类型:

(参数)数据:(FilterShapeByType | FilterShapeByType)[“数据”]
它告诉我们什么?
数据的类型取决于
T
解析为什么。在您的情况下,
T
被限制为
DefinedShape[“type”]
,或者,内联的
“circle”|“rect”
(注意:除非您想混淆人们,请将
type
更改为更常见的
种类

在使用
“circle”
“rect”
作为
类型
调用函数之前,
T
保持未解析状态。话虽如此,让我们测试一下,给定union
“circle”|“rect”
,数据的实际类型是什么:

type testFilter=FilterShapeByType[“数据”];
//类型testFilter={
//半径:数字;
// } | {
//宽度:数字;
//高度:数字;
// }
很明显,为什么不能访问这两个属性:因为只有在联合的所有组成部分上都存在类型保护的成员时,才能访问它们(请参阅)
switch
语句在这里并不重要,因为编译器无法缩小联合,因为未解析的
T

如果调用该函数,您将看到没有错误:

drawShape2(“圆”{radius:5})//好的,没有错误

什么

现在,我们可以做些什么来解决这个问题呢

有几种方法,大多数在其他答案中概述,我只想提及,如果您控制公共合同,请直接使用
定义形状
,因为缩小所需的信息包含在
类型
属性中:

const drawShape3=(形状:DefinedShape)=>{
开关(形状.类型){
案例“圆圈”:{
console.log(shape.data.radius);//确定
打破
}
案例“rect”:{
console.log(shape.data.height,shape.data.width);//确定
打破
}
}
};

在我的例子中,有很多类型,我认为它会很冗长和重复。无论如何,这是一个很好的技巧,很有学问,谢谢你!看来我们必须在那里安装铅字防护装置,它能完成任务。然而,这种方法有一个缺陷,当它们有许多类型时,
if-else,else…
语句变得非常烦人,如果我再添加一个类型,它不会警告我。
type Shapes = {
  circle: { radius: number },
  rect: { width: number; height: number }
}

type ShapeTypes = keyof Shapes;

type DrawShape = <T extends ShapeTypes>(type: T, data: Shapes[T]) => void;

const isShapeOf = <T extends ShapeTypes>(expected: T, type: string, data: any): data is Shapes[T] => {
  return expected === type;
}

const drawShape: DrawShape = (type, data) => {
  if (isShapeOf("circle", type, data)) {
    data.radius; // pass
  } else if (isShapeOf("rect", type, data)) {
    data.radius; // radius does not exist
    data.width; // pass
    data.height; // pass
  }
};

drawShape("rect", { height: 12, width: 22 }); // pass
drawShape("rect", { height: 12, width: 22, radius: 23 }); // failed, radius is not defined
drawShape("circle", { radius: 12 }); // pass
drawShape("circle", { radius: 12, len: 23 }); // failed, len is not defined