Avol
Avol

Reputation: 218

Function with generics that returns class with generics in .d.ts

I try to modify code, that works in regular usage, to use with declarations generation (or manually write declarations). The code is like this:

function createClass<T>( data: T )
{
    abstract class Internal<T2>
    {
        public constructor( arg: T2 )
        {
            console.log( data, arg );
        }

        protected abstract method( arg: T ): void;
    }

    return Internal;
}

The problem is that i should to specify type of function result, but I don't know how to do this — I tried different options and none worked.

Added:

This small example may not be completely clear, so I add the original code with which the problem occurred:

Original code, that I'm trying to turn into a package: https://gist.github.com/Avol-V/1af4748f4b0774a999311c92b6dc1631

Code of small-redux you can find there: https://github.com/m18ru/small-redux/blob/master/src/createStore.ts

Code of preact you can find there: https://github.com/developit/preact/blob/master/src/preact.d.ts

The problem is that I don't want to specify the TState type to every usage of created class — it can't be changed on created class so it's incorrect.

Added:

So, I didn’t find any solution to not specify T explicitly in the generic of the created class (additionally to the T2class Internal<TInternal, T2>). My result code looks like: https://github.com/m18ru/preact-small-redux/blob/85c143e851b92c44861dc976ce6ef89bcda2c884/src/index.ts

If you just want to write a function that returns class with generic, you can do this as @baryo suggests (with some cleanup):

declare abstract class AbstractInternal<T>
{
    public abstract method( arg: T ): void;
}

function createClass(): typeof AbstractInternal
{
    abstract class Internal<T> implements AbstractInternal<T>
    {
        public abstract method( arg: T ): void;
    }

    return Internal;
}

const A = createClass();

class B extends A<string>
{
    public method( arg: string ): string
    {
        return 'Hello ' + arg;
    }
}

Upvotes: 0

Views: 1052

Answers (1)

baryo
baryo

Reputation: 1461

Everything you create inside the function is scoped to it, including types. So you need to at least define types out of it. Maybe the following will help you:

interface IInternal<T> {
    method(arg: T): void;
}

type InternalCtor<T> = new (param: T) => IInternal<T>;

function createClass<T>(data: T): InternalCtor<T>
{
    abstract class Internal<T> implements IInternal<T>
    {
        public constructor( arg: T )
        {
            console.log( data, arg );
        }

        public abstract method( arg: T ): void;
    }

    return Internal as InternalCtor<T>;
}

class A extends createClass<number>(1) {
    public method() {
        console.log('hello');
    }
}

const z = new A(2);  // 1 2

edit: you mentioned you're interested that a generic method will return a generic class - it becomes a bit tricky with typings but we can just let Typescript itself infer everything we need.

function createClass<T>(data: T)
{
    // directly return a new class with a different generic type.
    //  notice that you can't return an abstract class.
    return class Internal<U>
    {
        public constructor( arg: U )
        {
            console.log( data, arg );
        }

        public method(arg: T): void { console.log(arg); }
    }
}



// first generic type (number) is for the function, then the second generic type (string) is for the returned class.
class A extends createClass<number>(1)<string> {

}

const z = new A('zzz');   // 1 zzz
z.method(2); // 2

edit 2: it's a bit different from what you're looking but i think a better practice will be the following (similar to what @Diullei suggested):

abstract class AbstractInternal<T> {
    public abstract method(arg: T): void;
}

type InternalCtor<T> = new (param: T) => AbstractInternal<T>;

function createClass<T, U>(data: T): InternalCtor<U> {
    abstract class Internal<U> implements AbstractInternal<U>
    {
        public constructor(arg: T) {
            console.log(data, arg);
        }

        public abstract method(arg: U): void;
    }

    return Internal as any as InternalCtor<U>;
}

class A extends createClass<number, string>(1) {
    public method(arg: string) { // now I need to implement this to avoid a compilation error
        console.log(`hello ${arg}`);
    }
}

const z = new A('arg'); // 1 "arg""
z.method('arg2'); // "hello arg2""

Upvotes: 2

Related Questions