Home > Blockchain >  TS how use generic condition in class constructor
TS how use generic condition in class constructor

Time:10-19

why TS wont allow me do this? From my test everything seem work ! no ?

    constructor(public readonly vtype: T) {
        // if KEYS.buffer ? is ValurBuffer or Valur ! why this no pass ?
        if (vtype === KEYS.buffer) {
            this.valur = new ValurBuffer(vtype);
        } else {
            this.valur = new Valur(vtype);
        }
    }

Type 'Valur<T>' is not assignable to type 'T extends VKEY.buffer ? ValurBuffer<T> : Valur<T>'.ts(2322)

here a context code.



const enum KEYS {
    string = 'string',
    number = 'number',
    boolean = 'boolean',
    array = 'array',
    buffer = 'buffer',
}

type VALUES = {
    readonly [KEYS.string]: string;
    readonly [KEYS.number]: number;
    readonly [KEYS.boolean]: boolean;
    readonly [KEYS.array]: Array<string | number | boolean>;
    readonly [KEYS.buffer]: ReadonlyArray<Token<any>>;
}


export class Token<T extends KEYS> {
    readonly valur: T extends KEYS.buffer ? ValurBuffer<T> : Valur<T>;
    constructor(public readonly vtype: T) {
        // if KEYS.buffer ? is ValurBuffer or Valur ! why this no pass ?
        if (vtype === KEYS.buffer) {
            this.valur = new ValurBuffer(vtype);
        } else {
            this.valur = new Valur(vtype);
        }
    }
}


class Valur<T extends KEYS> {
    readonly Token: Token<T>;
    protected _value: VALUES[T];
    get value() {
        return this._value;
    }
    set value(value) {
        this._value = value;
    }
    constructor(public readonly vtype: T, shemas?: {}) {}
}
class ValurBuffer<T extends KEYS.buffer> {
    readonly Token: Token<T>;
    protected readonly _value: VALUES[T];
    get value() {
        return this._value;
    }
    add(v:VALUES[T]){}
    constructor(public readonly vtype: T, shemas?: {}) {}
}


const test1 = new Token(KEYS.array)
test1.valur.value;
test1.valur.value = [5];
const test2 = new Token(KEYS.buffer);
test2.valur.value
test2.valur.value=[];
test2.valur.add([new Token(KEYS.boolean)])

TSPLAYGROUND

CodePudding user response:

Because valur property is a black box. It is not evaluated yet in Token class declaration. It is still represents conditional type T extends KEYS.buffer ? ValurBuffer<T> : Valur<T> instead of ValurBuffer<T> | Valur<T>

It is better to create static init method and overload it:


const enum KEYS {
    string = 'string',
    number = 'number',
    boolean = 'boolean',
    array = 'array',
    buffer = 'buffer',
}

type VALUES = {
    readonly [KEYS.string]: string;
    readonly [KEYS.number]: number;
    readonly [KEYS.boolean]: boolean;
    readonly [KEYS.array]: Array<string | number | boolean>;
    readonly [KEYS.buffer]: ReadonlyArray<Token>;
}



export class Token{
    static init<T extends KEYS.buffer>(vtype: T): ValurBuffer<T>
    static init<T extends Exclude<KEYS, KEYS.buffer>>(vtype: T): Valur<T>
    static init<T extends KEYS.buffer>(vtype: T) {
        if (vtype === KEYS.buffer) {
            return new ValurBuffer(vtype);
        } else {
            return new Valur(vtype);
        }
    }
}


class Valur<T extends KEYS> {
    readonly Token: Token;
    protected _value: VALUES[T];
    get value() {
        return this._value;
    }
    set value(value) {
        this._value = value;
    }
    constructor(public readonly vtype: T, shemas?: {}) { }
}
class ValurBuffer<T extends KEYS.buffer> {
    readonly Token: Token;
    protected readonly _value: VALUES[T];
    get value() {
        return this._value;
    }
    add(v: VALUES[T]) { }
    constructor(public readonly vtype: T, shemas?: {}) { }
}


// Valur<KEYS.string | KEYS.number | KEYS.boolean | KEYS.array>
const test1 = Token.init(KEYS.array)
//  ValurBuffer<KEYS.buffer>
const test2 = Token.init(KEYS.buffer)

Playground

If you want to stick with current Token implementation, try this:

export class Token<T extends KEYS> {
    readonly valur: ValurBuffer<KEYS.buffer> | Valur<T>;
    constructor(public readonly vtype: T) {
        if (vtype === KEYS.buffer) {
            this.valur = new ValurBuffer(vtype);
        } else {
            this.valur = new Valur(vtype);
        }
    }
}

The whole code with second approach:


const enum KEYS {
    string = 'string',
    number = 'number',
    boolean = 'boolean',
    array = 'array',
    buffer = 'buffer',
}

type VALUES = {
    readonly [KEYS.string]: string;
    readonly [KEYS.number]: number;
    readonly [KEYS.boolean]: boolean;
    readonly [KEYS.array]: Array<string | number | boolean>;
    readonly [KEYS.buffer]: ReadonlyArray<Token<any>>;
}


export class Token<T extends KEYS> {
    readonly valur: ValurBuffer<KEYS.buffer> | Valur<T>;
    constructor(public readonly vtype: T) {
        if (vtype === KEYS.buffer) {
            this.valur = new ValurBuffer(vtype);
        } else {
            this.valur = new Valur(vtype);
        }
    }
}


class Valur<T extends KEYS> {
    readonly Token: Token<T>;
    protected _value: VALUES[T];
    get value() {
        return this._value;
    }
    set value(value) {
        this._value = value;
    }
    constructor(public readonly vtype: T, shemas?: {}) { }
}

class ValurBuffer<T extends KEYS.buffer> {
    readonly Token: Token<T>;
    protected readonly _value: VALUES[T];
    get value() {
        return this._value;
    }
    add(v: VALUES[T]) { }
    constructor(public readonly vtype: T, shemas?: {}) { }
}


const test1 = new Token(KEYS.array)
test1.valur.value;
test1.valur.value = [5];
const test2 = new Token(KEYS.buffer);
test2.valur.value
test2.valur.value = [];
test2.valur.add([new Token(KEYS.boolean)])


Playground

CodePudding user response:

Found decent way so because the conditional type is not considered evaluated. assertion by wrapped the conditional type into another type seem ok and ts pass for constructor.


const enum KEYS {
    string = 'string',
    number = 'number',
    boolean = 'boolean',
    array = 'array',
    buffer = 'buffer',
}

type VALUES = {
    readonly [KEYS.string]: string;
    readonly [KEYS.number]: number;
    readonly [KEYS.boolean]: boolean;
    readonly [KEYS.array]: Array<string | number | boolean>;
    readonly [KEYS.buffer]: ReadonlyArray<Token<any>>;
}

type Helper<T extends KEYS> = T extends KEYS.buffer ? ValurBuffer<T> : Valur<T>;

export class Token<T extends KEYS> {
    // wrapped the conditional and ts say is ok now !
    readonly valur: Helper<T>;
    constructor(public readonly vtype: T) {
        // if KEYS.buffer ? is ValurBuffer or Valur ! why this no pass ?
        if (vtype === KEYS.buffer) {
            this.valur = new ValurBuffer(vtype) as Helper<T>;
        } else {
            this.valur = new Valur(vtype) as Helper<T>;
        }
    }
}


class Valur<T extends KEYS> {
    readonly Token: Token<T>;
    protected _value: VALUES[T];
    get value() {
        return this._value;
    }
    set value(value) {
        this._value = value;
    }
    constructor(public readonly vtype: T, shemas?: {}) {}
}
class ValurBuffer<T extends KEYS.buffer> {
    readonly Token: Token<T>;
    protected readonly _value: VALUES[T];
    get value() {
        return this._value;
    }
    add(v:VALUES[T]){}
    constructor(public readonly vtype: T, shemas?: {}) {}
}


const test1 = new Token(KEYS.array)
test1.valur.value;
test1.valur.value = [5];
const test2 = new Token(KEYS.buffer);
test2.valur.value
test2.valur.value=[];
test2.valur.add([new Token(KEYS.boolean)])

PLAYGROUND

  • Related