New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: typeof of generic type #204
Comments
Your workarounds are actually the correct design because they model the constraint you have -- a zero-argument constructor function for the given type. |
The link was very informative. Thank you. |
The first workaround actually causes problems with statics: class Model {
static doStuff() { }
}
class Collection<TModel extends Model> {
// With this workaround, you also have to hack in doStuff() here which causes a lot of duplication.
model: { new(): TModel; };
models: TModel[];
doStuffToModel() {
// This is okay because we hacked in "new(): TModel"
var model = new this.model();
// This is not okay because we didn't hack in "doStuff(): void"
this.model.doStuff();
}
} The second workaround is a lot better but also problematic: class Model { }
class Collection<TModelConstructor extends typeof Model, TModel extends Model> {
model: TModelConstructor;
models: TModel[];
static createCoolInstance<TModelConstructor extends typeof Model, TModel extends Model>(ctor: TModelConstructor): TModel {
// "Type 'Model' is not assignable to type 'TModel'." so you have to "<TModel>new ctor();" which seems pretty unnecessary
return new ctor();
}
} Let me explain what I think about this with some pseudo code: class MyClass {
static foo() {};
bar() {};
}
// That does something like:
type MyClassInstanceType = { bar: () => void };
type MyClassConstructorType = { new(): MyClassInstanceType; foo(): void };
var MyClassValue = function() {};
MyClassValue.prototype.bar = function () {};
MyClassValue.foo = function () {};
// So if typeof was a function, it would be something like:
function typeof(classValue: Value): Type {
return ClassValueToConstructorTypeMap[classValue];
} My question is, why does var inst: MyClass = new MyClass();
var ctor: typeof MyClass = MyClass; The first "MyClass" refers to |
And some examples would be: autoConstructor<T>(ctor: constructorof T): T {
return new ctor();
}
var inst;
/* Works */
class MyClass { }
inst = autoConstructor(MyClass);
/* Should these work? */
inst = autoConstructor<number>(Number);
inst = autoConstructor(Number);
inst = autoConstructor<string>(String);
inst = autoConstructor(String);
inst = autoConstructor(Date);
/* Maybe: */
// Ok
autoConstructor<T extends MyClass>(ctor: constructorof T)......
// Error: Types given to 'constructorof' must extend a class type
autoConstructor<T>(ctor: constructorof T)...... |
Note that You can already write this: interface constructorof<T> {
new (): T;
}
function autoConstructor<T>(ctor: constructorof<T>): T {
return new ctor();
}
var inst;
/* Works */
class MyClass { }
let c = autoConstructor(MyClass); // c: MyClass
/* Should these work? */
inst = autoConstructor<Number>(Number);
inst = autoConstructor(Number);
inst = autoConstructor<String>(String);
inst = autoConstructor(String);
inst = autoConstructor(Date); |
That doesn't provide the actual type for the constructor: class MyClass {
static foo() {};
}
interface constructorof<T> {
new (): T;
}
function autoConstructor<T extends MyClass>(ctor: constructorof<T>): T {
// No foo here
ctor.foo();
return new ctor();
} What is the reason for having |
There can be more than one constructor function for any given type. interface SomeObj { ... }
declare var ctor1: {
new(): SomeObj;
foo;
}
declare var ctor2: {
new(): SomeObj;
bar;
}
function magic<T extends SomeObj>(ctor: constructorof<T>) {
// can I use ctor.foo here? ctor.bar?
}
|
Can there be more than one constructor function for any given class type? If not then a class MyClass {
static foo() {};
}
/*
MyClass was declared with the `class` keyword.
We can be sure that it has one constructor.
We allow constructorof to be used with it.
*/
var ctor: constructorof MyClass = MyClass; |
Yes. class Foo { }
declare var factory: {
new(): Foo;
} |
Yes, but class Foo {
static bar() {};
}
declare var factory: {
new(): Foo;
foo();
}
var ctor: typeof Foo = Foo;
ctor.bar; // Here
ctor.foo; // Not here The |
I'm still not sure what you're proposing that is meaningfully different from |
Enum don't have constructors so |
And what problem is this presenting you with? Of course enum's don't have constructors. This was (originally) an issue about how to reference the constructor of a generic, which really turned into how to describe a generic that extends a constructor. |
I was making a method that took an enum and one of its values and
|
This is a different problem... While the the intellisense lists an enumeration type as a |
I have the same problem, while it is understandable to have to pass the constructor as an argument to reference it later (although ironic since we already have the type parameter), the Note, the |
I found that a quite weird syntax may replace "typeof T" syntax.
Possible usage:
Well, it seems to "abuse" the TypeScript interpreter but it works, and makes sense. Someone have an opinion over this and especially someone maybe knows if this would not work in the next version of TS? |
@SalvatorePreviti that's basically the same as the recommended solution in the FAQ, so I'd say it's well-supported 😉 |
I'm sorry to piggy back onto this, but I think my question is very much related to this question, but I couldn't quite figure out if it was so. http://stackoverflow.com/questions/43000982/how-to-create-a-factory-that-returns-anonymous-classes-that-implement-a-generic I'm trying to do the following to remove boiler plate code of creating concrete classes for a payload type.
But I get an error because what I'm returning is the class that implements TypedAction, not the instance. The error says:
Is it impossible to return a dynamic implementation of a generic class from TypeScript? |
@juanmendes your return type should be |
Thanks Ryan, it mostly works, the problem there is that the return of |
I don't understand why the typeof operator should not be applied on type arguments. The language specification states in section 4.18.6: The 'typeof' operator takes an operand of any type and produces a value of the String primitive type. In So why is something like the followin not allowed?
|
|
@RyanCavanaugh: But if so, why do the specs state that typeof can be used in a type query?
|
class MyClass {}
let x: typeof MyClass; // MyClass is a value here
|
Well okay, MyClass is a value then. But how do I get the type of the constructor function of the type MyClass? |
@markusmauch by writing |
Is there a reason I'd like to get the type of the class with a specific type, but without having to create a class that inherits from it: const MySpecificClass: typeof MyClass<SpecificType> = MyClass; |
@leoasis the type argument belongs to the instance side of the class. |
Yeah, I know, that's what I wanted to show in the example. In my particular use case, I'm using React, and I have an The problem there is that when I define the component with a generic type, I can no longer use it in JSX directly, since I cannot write return (
<InfiniteScrollList<Item> {...props} />
); So I tried doing something like this: const ItemInfiniteScrollList: typeof InfiniteScrollList<Item> = InfiniteScrollList; but that didn't work. Couldn't find another way to make that work without creating an empty class that inherits from the parametrized one. That works, but I would like to avoid creating a class (which will be created at runtime) just to enforce type safety. |
The workaround mentioned by @SalvatorePreviti and the FAQ only works on classes that don't have any static methods, as the return type is only a constructor. I can't seem to come up with a decent workaround that would even allow for an identity function that accepts and returns a constructor. abstract class Animal<T> {
static identity<T>(T: typeof Animal<T>) : typeof T; // this doesn't work
}
class Dog extends Animal<Dog> {
}
Animal.identity(Dog) // => Dog A more concrete (and realistic) use-case would be a static method that creates a new copy of the class/constructor (possibly with a different set of static properties, or extra static methods). This is a pattern often seen in ORMs, notably Sequelize's If anybody has ideas about similar ways to achieve the same result whilst working within the boundaries of TypeScript's existing type system, let's get them documented! |
class Animal<T> {
static identity<T extends new (...args: any[]) => Animal<any>>(C: T): T {
return C;
}
}
class Dog extends Animal<Dog> {}
Animal.identity(Dog); // => typeof Dog Or even class Animal<T> {
static identity<T extends new (...args: any[]) => Animal<any>>(this: T) {
return this;
}
static create<T extends Animal<any>>(this: new (...args: any[]) => T) {
return new this();
}
}
class Dog extends Animal<Dog> {}
Dog.identity(); // => typeof Dog
Dog.create(); // => Dog |
I'm having the same problem, and basically ended up chain casting to // This class is in a 3rd party library:
abstract class Model<T> {
static count(): number;
}
interface Options<T extends Model<T>> {
target: new () => T;
}
// `options` type is `Options<T>`
const model = options.target as any as typeof Model;
return model.count(); |
Below, the
Collection.model
field is supposed to accept type ofTModel
rather than instance ofTModel
:Use of
typeof
keyword seems to be appropriate here, but compiler doesn't understandtypeof
of generic type. If we saidmodel: typeof Model;
it would compile but that is not exactly what we want.Some of the workarounds we found are the followings:
and
Feature suggestion is to allow
typeof
generic type.The text was updated successfully, but these errors were encountered: