最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Passing a generic type to a function in typescript - Stack Overflow

programmeradmin3浏览0评论

Consider example:

interface A {
   foo: string;
}

interface B {
   foo: string;
}

function a<T>() {
   function b(arg: T) {
      return arg.foo;
            ^^^^^^ Property 'foo' does not exist on type 'T'.
   }

   return b;
}

a<A>();
a<B>();

How to make it work so I dont have to hardcore the type like:

function b(arg: A | B)

So I will be able to pass the type as a generic so if there's like 5 or even more different types I will not have to do it like A | B | C | D | E... but just as a generic?

Consider example:

interface A {
   foo: string;
}

interface B {
   foo: string;
}

function a<T>() {
   function b(arg: T) {
      return arg.foo;
            ^^^^^^ Property 'foo' does not exist on type 'T'.
   }

   return b;
}

a<A>();
a<B>();

How to make it work so I dont have to hardcore the type like:

function b(arg: A | B)

So I will be able to pass the type as a generic so if there's like 5 or even more different types I will not have to do it like A | B | C | D | E... but just as a generic?

Share Improve this question asked Oct 2, 2021 at 23:26 underfrankenwoodunderfrankenwood 9132 gold badges12 silver badges32 bronze badges 5
  • what will change if you use function a<T extends A>() { and use A as A base class. – DraganS Commented Oct 2, 2021 at 23:37
  • @DraganS cant since the A or B interfaces may have a lot of different (or even conflicting) fields – underfrankenwood Commented Oct 2, 2021 at 23:40
  • Will this approach work for your needs? You need to tell the piler that T will have a foo property. If that approach works for you I can write up an answer; otherwise please edit the example code to demonstrate failed use cases. – jcalz Commented Oct 3, 2021 at 0:23
  • As it shown currently, there's no point to use generics at all and it could be just function b(arg: { foo: string }) { ... }. More realistic example would help – Aleksey L. Commented Oct 3, 2021 at 7:35
  • @jcalz Yes I thought about something similar, you can make an answer I will mark it – underfrankenwood Commented Oct 3, 2021 at 10:31
Add a ment  | 

2 Answers 2

Reset to default 2

In order for you to be able to access arg.foo, the piler must be convinced that, at the bare minimum, arg might actually have a property named foo. So the type of arg must be assignable to something like {foo?: unknown}, where foo is an optional property and the type of the property is the unknown type.

It's not clear from your example if you want to make something stricter than this (say that it definitely has a property named foo, or that the value at that property must be a string). For now, I'm only going to assume that you want to access arg.foo without an error.

If you are using the generic type parameter T as the type of arg, then T must be constrained to something assignable to {foo?: unknown}.

For example:

function a<T extends { foo?: unknown }>() {
    function b(arg: T) {
        return arg.foo; // okay, no error
    }    
    return b;
}

This saves you from having to write T extends A | B | C | ..., assuming that all of the A, B, C, etc... types are themselves assignable to { foo?: unknown }. Your given A and B types are assignable, so the following works:

a<A>();
a<B>();

Do note that your A and B types from the example are actually identical. TypeScript's type system is structural. Because types A and B have the same structure, they are the same type. It doesn't matter that there are two declarations and two different names:

let aVal: A = { foo: "a" };
let bVal: B = { foo: "b" };
aVal = bVal; // okay
bVal = aVal; // okay

So writing T extends A would be fine if you want the piler to enforce that foo exists and is a string; it wouldn't stop B from working. A structural type system frees you from having to anticipate all possible named types:

function a2<T extends A>() {
    function b(arg: T) {
        return arg.foo.toUpperCase();
    }
    return b;
}
    
a2<A>(); // okay
a2<B>(); // okay

interface C {
    foo: string;
    bar: number;
}
a2<C>(); // okay

a2<Date>(); // error
// ~~~~
// Property 'foo' is missing in type 'Date' but required in type 'A'.

If you care about "hardcoding" the name A, you can use an anonymous type like {foo: string} as in T extends {foo: string}, but that doesn't really change much from the type system's perspective, since A and {foo: string} are the same type.

Playground link to code

you can try something like

interface A {
   foo: string;
}

interface B extends A {
   
}

function a<T extends A>() {
   function b(arg: T) {
      return arg.foo;
           
   }

   return b;
}

a<A>();
a<B>();
发布评论

评论列表(0)

  1. 暂无评论