Typescript object wrapping, typings and generics [duplicate]

…衆ロ難τιáo~ 提交于 2019-12-11 06:13:47

问题


This is somewhat the transformation of a question I posted somewhere else yesterday.

My objective is not to get a working result but to have a better understanding of the kind of design I can obtain while returning the correct types. This by using here a minimalist example, so please don't tell me it's useless or does nothing.

Code sample (Try it in Typescript playground):

interface TestInterface { 
    test: () => {}
}

class Obj implements TestInterface{
    test() { 
        return { test: 'test' }
    }
}

class Wrapper<T extends  TestInterface> {   
    constructor(public obj: T) {
    }

    test<T>() { 
        return this.obj.test();
    }

    test2<T extends TestInterface>(obj: T) { 
        return obj.test();
    }
}

let w = new Wrapper(new Obj());
let r1 = w.obj.test();
let r2 = w.test();
let r3 = w.test2(new Obj);

Here:
whas the type Wrapper<Obj>
r1 has the type {test: string}
w.test has the type Wrapper<Obj>.test: () => {}
r2 has the type {}
r3 has the type {}

This case is a class that stores an object in a property. It proxies the calls to this object.

What interest me is the return type of r2. And specifically the fact that I would like to find a way to return the same type as for r1 without specifying the type in the three bottom lines. r3is another test, by passing directly the generic type. The result is the same.

I have some questions about that:

  • From my understanding the Wrapper.test return type is resolved before the OBJ generic has any influence. So its base value is used (in this case {} which is the result of TestInterface.Test). Is it right?
  • Is it something done on purpose, some limitation, some upcoming feature in future TS versions? (didn't see anything about that)
  • And mainly, how to be able to forward embedded object methods return types (or compose a new object with them), considering that in such a proxy, it should be possible to plug anything that respect the TestInterface. And without putting generics everywhere in the caller (at the bottom lines), that I know how to do.

I've seen some object builder in this post: https://github.com/Microsoft/TypeScript/pull/14141. Maybe that's the direction I'll take.


回答1:


I think this is what you want:

interface TestInterface<T1,T2> { 
    test: () => T1
    test2: () => T2
}

class Obj implements TestInterface<{ test: string }, { test2: number }>{
    test() { 
        return { test: 'test' }
    }

    test2() { 
        return { test2: 2 }
    }
}

class Wrapper<T1,T2> implements TestInterface<T1,T2>  {   
    public obj :TestInterface<T1,T2>;
    constructor(obj: { new (): TestInterface<T1,T2> }) {
        this.obj = new obj();
    }

    test():T1 { 
        return this.obj.test();
    }

    test2():T2 { 
        return this.obj.test2();
    }
}

let w = new Wrapper(Obj);
let r = w.test();
let s = w.test2();



回答2:


I answer to myself, because I like to use Stackoverflow as my personal notebook. If someone has better answers to provide, I'm a buyer!

  • Apparently Typescript is a big couchpotato and doesn't follow the type inheritance. Maybe for performance reasons, maybe because the type could be affected by other means and wouldn't be reliable, maybe because it's tricky to do, maybe because it's not yet implemented, maybe because nobody asked for that because they don't use the model object, or Intellisense.
  • Didn't see anything clear in the docs/upcoming features of Typescript 2.3 about that. There's a mention about strongly typing this here when using the --noImplicitThis compilation option, and the ThisType<T> function to declare this explicitely. But apparently it's more about a function being aware of its embedding structure type, than following the object model flow. And it doesn't help in my case.
  • That, I can answer!

Here is a code sample (Test it in Typescript playground):

interface TestInterface { 
    test: () => {}
}

class Obj implements TestInterface{
    test() { 
        return { test: 'test' }
    }
}

class Wrapper<T extends TestInterface, TEST> {   
    public obj;
    constructor(obj: { new (): T; prototype: { test: TEST } }) {
        this.obj = new obj();
    }

    test():TEST { 
        return this.obj.test();
    }
}

let w = new Wrapper(Obj);
let r = w.test();

In this case, r type is {test: string}. This solution consists in not instantiating Obj but getting its prototype to deduce the type.

While searching, I also fell on a simpler way to write it by there (see update 2).

It gives the possibility to keep clever typings and mainly code completion when using some dynamic patterns. Now, it's still a choice to do to choose between completion or a smarter/clearer design...

This sample is based on an example from the Typescript handbook, at the bottom of this page.



来源:https://stackoverflow.com/questions/43505888/typescript-object-wrapping-typings-and-generics

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