问题
I have a situation where I might have some code like this:
const data = fetchSomeData(); //Can be anything
const checkedData = checkDataIs<SomeSpecificType>(data);
// now I can treat checkedData as SomeSpecificType
That is, at compile time I know what shape the data should be, and want to write my code as such, have all the advantages of type checking, but at runtime I need to run a function first to check that. (And it'll throw an error if the data doesn't match).
I know that I can achieve this functionality by writing individual functions for each type I want to check, eg:
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")
}
}
This would work fine.
The question is - could I create a function that takes a generic param T, to determine which functionality is run at runtime?
eg something like:
function checkItem<T extends Foo|Bar>(data:any) : T {
// if T is Foo
return checkFoo(data);
//If T is Bar
return checkBar(data);
}
I think technically this should be possible in the sense that we're not asking for runtime type awareness - what this would be doing is compiling different code depending on what the generic parameter is.
回答1:
The canonical answer to this question is a blunt "you can't do this". It's a non-goal of TypeScript to
add or rely on run-time type information in programs, or emit different code based on the results of the type system. Instead, encourage programming patterns that do not require run-time metadata.
The answer could just stop there, but it might be helpful to see how to get similar behavior to this without requiring TypeScript to emit different code for different types. In this case, I'd write some user-defined type guard functions which return boolean
values based on whether or not the input argument is of the guarded type. Say like this:
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";
}
}
Here, Type.Foo
and Type.Bar
are similar to the logic in your checkFoo()
and checkBar()
functions. And note that Type.Foo
and Type.Bar
are functions that do exist at runtime, although we have imbued them in the type system with the property that they can be used to narrow an any
value to a Foo
or a Bar
.
Then we can write your checkItem()
function like this:
function checkItem<T>(guard: (x: any) => x is T, data: any): T {
if (!guard(data)) {
throw "Failed validation";
} else {
return data;
}
}
You can think of checkItem
as not taking a type parameter (although it is generic) which will be gone at runtime, but as taking a type guard function which will exist.
If you have something which might or might not be a Foo
const maybeFoo = JSON.parse(Math.random() < 0.5 ? '{"id": 123}' : '{"di": 321}');
Then you can call checkItem()
like this:
const foo: Foo = checkItem(Type.Foo, maybeFoo); // could be Failed validation here
console.log(foo.id.toFixed(1)); // 123.0 if it reaches here
We've changed from checkItem<Foo>(maybeFoo)
to checkItem(Type.Foo, maybeFoo)
, which, if you squint at it enough, is sort of the same thing. We are specifying Foo
both ways, but the latter way is doing so with a thing that exists at runtime.
Playground link to code
来源:https://stackoverflow.com/questions/64236240/is-it-possible-to-use-the-generic-type-to-determine-runtime-functionality