TypeScript高级类型-泛型函数中存在无法访问错误的问题



我找不到为什么泛型函数中的switch不在分支内强制转换我的类型的答案。

我的代码是:

interface Id { id: number; }
enum Kind { square = "square", circle = "circle" }
interface Circle { kind: Kind.circle; radius: number; }
interface Square { kind: Kind.square; size: number; }
type Data = Circle | Square;
type ShapeModel<TData> = Id & TData;
class UnreachableError extends Error { public constructor(guard: never) { super(`Unsupported kind: ${JSON.stringify(guard)}`); } }
function myFunctionGeneric<TData extends Data>(data: TData): ShapeModel<TData> {
switch (data.kind) {
case Kind.circle:
return { ...data, id: 1 };
case Kind.square:
return { ...data, id: 2 };
default:
throw new UnreachableError(data); // <-- UNEXPECTED
// Argument of type 'TData' is not assignable to parameter of type 'never'.
// Type 'Data' is not assignable to type 'never'.
// Type 'Circle' is not assignable to type 'never'.
// ts(2345)
}
}
const myCircleData: Circle = { kind: Kind.circle, radius: 42 };
const mySquareData: Square = { kind: Kind.square, size: 42 };
// I want this. I'm passing Circle and want receive ShapeModel<Circle>
const myCircleModel: ShapeModel<Circle> = myFunctionGeneric(myCircleData);
// I want this. I'm passing Square and want receive ShapeModel<Square>
const mySquareModel: ShapeModel<Square> = myFunctionGeneric(mySquareData);

这种方法在没有通用TData的情况下正常工作。

有人能解释一下,为什么TypeScript不能决定分支中的类型吗?

对于要缩小的类型,它需要是并集,这里没有并集,它是泛型类型参数TData。当然,这延伸了联盟。但TS不会试图缩小这一范围。如果你仔细想想,缩小范围会很困难,TData可以是Circle的一个子类型,所以在切换的情况下你不能缩小到Circle,它可能是一些条件类型。

最简单的解决方案是有一个带有泛型的公共签名,和一个带有更简单并集的私有签名,这些并集可以通过typescript缩小。


function myFunctionGeneric<TData extends Data>(data: TData): ShapeModel<TData>
function myFunctionGeneric(data: Data): ShapeModel<Data> {
switch (data.kind) {
case Kind.circle:
return { ...data, id: 1 };
case Kind.square:
return { ...data, id: 2 };
default:
throw new UnreachableError(data); 
}
}

游乐场链接

Typescript只能在变量已知为并集的情况下执行有区别的并集。对吗?然后您可以通过使参数类型为TData & Data来强制它,这意味着typescript静态地知道您的变量可以精确地缩小到TData & CircleTData & Square,并且保留了泛型行为。(游乐场链接(

function myFunctionGeneric<TData extends Data>(data: Data & TData): ShapeModel<TData>
//  ^ this fixes it.

为了让事情更清楚,请特别注意UnreachableError的新签名。还要注意的是,出于演示目的,我在并集类型中添加了一个不受支持的类型。现在它进行编译,如果将与添加到Union类型中的非法值类型相同的值传递给您的函数,它仍然会抛出错误:

interface Id { id: number; }
enum Kind { square = "square", circle = "circle" , anyData = 'anyData'}
interface Circle { kind: Kind.circle; radius: number; }
interface Square { kind: Kind.square; size: number; }
interface AnyData { kind: Kind.anyData; prop: string; }
type Data = Circle | Square | AnyData;
type ShapeModel<TData> = Id & TData;
class UnreachableError extends Error { public constructor(guard: any) { super(`Unsupported kind: ${JSON.stringify(guard)}`); } }
function myFunctionGeneric<TData extends Data>(data: TData): ShapeModel<TData> {
switch (data.kind) {
case Kind.circle:
return { ...data, id: 1 };
case Kind.square:
return { ...data, id: 2 };
default:
throw new UnreachableError(data); 
}
}
const myCircleData: Circle = { kind: Kind.circle, radius: 42 };
const mySquareData: Square = { kind: Kind.square, size: 42 };
const myAnyData : AnyData = {kind: Kind.anyData , prop: 'aProp'}
// I want this. I'm passing Circle and want receive ShapeModel<Circle>
const myCircleModel: ShapeModel<Circle> = myFunctionGeneric(myCircleData);
// I want this. I'm passing Square and want receive ShapeModel<Square>
const mySquareModel: ShapeModel<Square> = myFunctionGeneric(mySquareData);

现在,如果你尝试这个:

const myAnyDataModel: ShapeModel<AnyData> = myFunctionGeneric(myAnyData)

您将得到预期的错误:

Error: Unsupported kind: {"kind":"anyData","prop":"aProp"}
at myFunctionGeneric

在typescript中,never是一个任何实际值都不能接受的类型。您的代码编辑器,例如vscode,会立即告诉您。在UnreachableError:的定义中将never更改为any

class UnreachableError extends Error { public constructor(guard: any) { super(`Unsupported kind: ${JSON.stringify(guard)}`); } }

最新更新