是否可以使用泛型类型来确定运行时功能



我有一种情况,我可能有这样的代码:

const data = fetchSomeData(); //Can be anything 
const checkedData = checkDataIs<SomeSpecificType>(data); 
// now I can treat checkedData as SomeSpecificType

也就是说,在编译时,我知道数据应该是什么形状,并且希望以此方式编写代码,具有类型检查的所有优点,但在运行时,我需要首先运行一个函数来检查这一点。(如果数据不匹配,它会抛出一个错误(。

我知道我可以通过为我想要检查的每种类型编写单独的函数来实现这个功能,例如:

type Foo = {
id: number; 
}
type Bar = {
name: string; 
}
function checkFoo(data: any) : Foo {
try {
if (typeof data.id === 'number'){
return data as Foo; 
}
else {
throw new Error(); 
}
}catch (e) {
throw new ("Failed validation")
}
}
function checkBar(data : any) : Bar {
try {
if (typeof data.name === 'string'){
return data as Bar; 
}
else {
throw new Error(); 
}
}catch (e) {
throw new ("Failed validation")
}
}

这会很好用的。

问题是,我可以创建一个使用通用参数T的函数,以确定哪些功能在运行时运行吗?

例如:

function checkItem<T extends Foo|Bar>(data:any) : T {
// if T is Foo
return checkFoo(data); 
//If T is Bar
return checkBar(data); 
}

我认为从技术上讲,这应该是可能的,因为我们不要求运行时类型意识——这将根据泛型参数来编译不同的代码。

这个问题的规范答案是一个直率的"你不能这样做;。TypeScript到不是目标

在程序中添加或依赖运行时类型信息,或者根据类型系统的结果发出不同的代码。相反,鼓励使用不需要运行时元数据的编程模式。


答案可以到此为止,但了解如何在不需要TypeScript为不同类型发出不同代码的情况下获得类似的行为可能会有所帮助。在这种情况下,我会编写一些用户定义的类型保护函数,根据输入参数是否为保护类型来返回boolean值。这样说:

namespace Type {
export function Foo(x: any): x is Foo {
return x && "id" in x && typeof x.id === "number";
}
export function Bar(x: any): x is Bar {
return x && "name" in x && typeof x.name === "string";
}
}

这里,Type.FooType.BarcheckFoo()checkBar()函数中的逻辑相似。注意,Type.FooType.Bar是运行时确实存在的函数,尽管我们在类型系统中赋予了它们可以用于将any值缩小为FooBar的特性。

然后我们可以这样写你的checkItem()函数:

function checkItem<T>(guard: (x: any) => x is T, data: any): T {
if (!guard(data)) {
throw "Failed validation";
} else {
return data;
}
}

您可以将checkItem视为不采用将在运行时消失的类型参数(尽管它是通用的(,而是采用将存在的type保护函数

如果你有可能是或可能不是Foo的东西

const maybeFoo = JSON.parse(Math.random() < 0.5 ? '{"id": 123}' : '{"di": 321}');

然后你可以这样调用checkItem()

const foo: Foo = checkItem(Type.Foo, maybeFoo); // could be Failed validation here
console.log(foo.id.toFixed(1)); // 123.0 if it reaches here

我们已经从checkItem<Foo>(maybeFoo)变成了checkItem(Type.Foo, maybeFoo),如果你仔细看的话,这是一样的。我们以两种方式指定Foo,但后一种方式是使用运行时存在的东西。

游乐场链接到代码

最新更新