Is it possible to use the generic type to determine runtime functionality?

情到浓时终转凉″ 提交于 2021-02-05 09:38:33

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!