假设我有一个要传递参数的函数。我自己的条件类型是基于参数的类型,但是当我想基于接口返回值时,函数给我一个错误。
这是实际代码:
interface IdLabel {
id: number;
}
interface NameLabel {
name: string;
}
type NameOrId<T extends string | number> = T extends number ? IdLabel : NameLabel;
function createLabel<T extends string | number>(idOrName: T): NameOrId<T> {
if (typeof idOrName === "number") {
return {
id: idOrName
};
} else {
return {
name: idOrName
};
}
}
const a = createLabel(1);
const b = createLabel("something");
您可以在这张图片中看到错误:
这是文本错误:
Type '{ id: T & number; }' is not assignable to type 'NameOrId<T>'.
Type '{ name: T; }' is not assignable to type 'NameOrId<T>'.
答案 0 :(得分:2)
当 NameOrId<T>
这样的条件类型依赖于 T
主体内的 createLabel()
这样的未解析类型参数时,编译器推迟对其进行评估。这样的类型对编译器来说本质上是不透明的,因此它通常无法验证像 {id: idOrName}
这样的特定值是否可以分配给它。
当您选中 typeof idOrName
时,control flow analysis 会缩小 idOrName
的明显类型,但这不会缩小类型参数 T
本身.如果 typeof idOrName === "string"
,idOrName
现在被称为 string
(或 T & string
)类型,但 T
仍然可能是 string | number
,并且是仍然是未解析的泛型类型参数。
这是 TypeScript 的一个已知痛点。有一个悬而未决的问题 microsoft/TypeScript#33912,要求某种方法来允许控制流分析验证未解析条件类型的可分配性,尤其是作为您想要的函数的返回值。除非该问题得到解决,否则您将不得不解决它。
解决方法:
每当您确定表达式 expr
的类型为 Type
而编译器不是类型时,您始终可以使用 type assertion 告诉编译器: expr as Type
,或者,在编译器认为 expr
类型与 Type
完全无关的情况下,您可能必须编写 expr as any as Type
或使用其他一些中间断言。>
那会给你这个:
function createLabelAssert<T extends string | number>(idOrName: T): NameOrId<T> {
if (typeof idOrName === "number") {
return {
id: idOrName
} as unknown as NameOrId<T>;
} else {
return {
name: idOrName
} as unknown as NameOrId<T>;
}
}
这解决了错误。请注意,通过进行断言,您对类型安全负责。如果您将 typeof idOrName === "number"
检查修改为 typeof idOrName !== "number"
,仍然不会出现编译器错误。但是你会在运行时不开心。
当您有一个无法在实现中验证其返回类型的函数时,您可以执行类型断言的“道德等效”:具有单个调用签名的 overload。重载实现的检查比常规函数更松散,因此您可以获得与类型断言相同的行为,而无需在每个 return
行分别进行断言:
// call signature
function createLabel<T extends string | number>(idOrName: T): NameOrId<T>;
// implementation
function createLabel(idOrName: string | number): NameOrId<string | number> {
if (typeof idOrName === "number") {
return {
id: idOrName
};
} else {
return {
name: idOrName
};
}
}
调用签名是相同的,但现在实现签名只是从 string | number
到 IdLabel | NameLabel
的映射。同样,编译器不会发现您不小心检查 typeof idOrName !== "number"
的问题,因此您在这里也需要小心。