I have the following situation:
abstract class A {
obj;
constructor(obj:{[index:string]:number}) {
this.obj = obj;
}
}
class B extends A {
constructor() {
super({i:0})
}
method() {
//Here I would like intellisense to only suggest this.obj.i
}
}
class C extends A {
constructor() {
super({i:0,j:0})
}
method() {
//Here I would like intellisense to only suggest this.obj.i and this.obj.j
}
}
I would like in class B
to get autocompletion for the object passed in the constructor and likewise for class C
. Is there a way to do this, or am I doing something fundamentally wrong here?
CodePudding user response:
You can try using generic
A
class like this:
abstract class A<T extends Record<string, number> = Record<string, number>> {
obj: T;
constructor(obj:T) {
this.obj = obj;
}
}
class B extends A<{ i: number }> {
constructor() {
super({i:0})
}
method() {
//Here I would like intellisense to only suggest this.obj.i
}
}
class C extends A<{ i: number, j: number }> {
constructor() {
super({i:0,j:0})
}
method() {
//Here I would like intellisense to only suggest this.obj.i and this.obj.j
}
}
Play here
CodePudding user response:
UPDATED for a more comprehensive solution. The comments within the code showcase this in more detail, but essentially using (a) the generic defined in class A as well as (b) public readonly i: _type_def_
etc. is advisable because it allows you to key-into targeted key-val pairs of interest. In Class C you can now enter this.i
or this.j
. Additionally, you can also use the Object
constructor methods to deconstruct and reconstruct super.obj vs this.obj properties, testing along the way for inter-and-intra-object equality.
abstract class A<
T extends number extends { [index: string]: infer U } ? U : number
> {
obj;
constructor(obj: Record<string, T>) {
this.obj = obj;
}
// [inferred]
// constructor A<T extends number>(obj: Record<string, T>): A<T>
}
class B extends A<0> {
constructor(public readonly i: number extends infer U ? U : number) {
super({ i: 0 });
// [inferred]
// constructor A<0>(obj: Record<string, 0>): A<0>
}
stringify() {
return JSON.stringify(
{
i: this.i as number, // i: number
sup: super.obj as Record<string, 0> // sup: { [x: string]: 0; }
},
null,
2
);
}
objValueOfCompare(valOne: number, valTwo: number) {
return valOne === valTwo ? true : false;
}
truthyCheck() {
const extractValueFromSuper = Object.values(super.obj)[0].valueOf();
const iIsValueOfObjRecord = <
T extends typeof this.i extends Record<string, infer U>
? U
: typeof this.i
>(
objectCompare: T
) => {
const extractThisInjectedObjectValue =
Object.values(objectCompare)[0].valueOf();
return this.objValueOfCompare(
extractValueFromSuper,
extractThisInjectedObjectValue
);
};
return iIsValueOfObjRecord; // returns True
}
}
// By declaring each index of the super obj in the constructor
// you can input a unique comma-delimited Int/Float value on a
// per field basis -- `new C(1, 2)` etc.. This allows you to
// key into fields within the Record -- eg, `this.i` or `this.j`
class C extends A<3.14 | -7.28> {
constructor(
public readonly i: number extends infer U ? U : number,
public readonly j: number extends infer U ? U : number
) {
super({ i: 3.14, j: -7.28 });
// [inferred]:
// constructor A<3.14 | -7.28>(obj: Record<string, 3.14 | -7.28>): A<3.14 | -7.28>
}
// thisObj deconstruction/reconstruction helper
thisObjByIndex(thisIndex: number) {
const extractKeyFromThisObj = Object.keys(this.obj)[
thisIndex
].toString();
const extractValueFromThisObj = Object.values(this.obj)[
thisIndex
].valueOf();
return { extractKeyFromThisObj, extractValueFromThisObj };
}
// superObj deconstruction/reconstruction helper
superObjByIndex(superIndex: number) {
const extractKeyFromSuperObj = Object.keys(super.obj)[
superIndex
].toString();
const extractValueFromSuperObj = Object.values(super.obj)[
superIndex
].valueOf();
return { extractKeyFromSuperObj, extractValueFromSuperObj };
}
// intraObject equality cross-check
crossCompareIntraObjectEquality() {
// thisObj
//keys
const iKey = this.thisObjByIndex(0).extractKeyFromThisObj;
const jKey = this.thisObjByIndex(1).extractKeyFromThisObj;
// values
const iValue = this.thisObjByIndex(0).extractValueFromThisObj;
const jValue = this.thisObjByIndex(1).extractValueFromThisObj;
// reconstructed intraobject equality check
const thisTruthyObj = {
[iKey]: iValue === this.i ? true : false, // returns true
[jKey]: jValue === this.j ? true : false // returns true
};
// superObj
// keys
const iSuperKey = this.superObjByIndex(0).extractKeyFromSuperObj;
const jSuperKey = this.superObjByIndex(1).extractKeyFromSuperObj;
// values
const iSuperVal = this.superObjByIndex(0).extractValueFromSuperObj;
const jSuperVal = this.superObjByIndex(1).extractValueFromSuperObj;
// reconstructed intraobject equality check
const superTruthyObj = {
[iSuperKey]: iSuperVal === this.i ? true : false,
[jSuperKey]: jSuperVal === this.j ? true : false
};
// testing for intraobject key/value equality
return thisTruthyObj === superTruthyObj ? true : false; // returns True!
}
// thisObj
recreateSuperObjFromDeconstructedThisMethods() {
//keys
const iKey = this.thisObjByIndex(0).extractKeyFromThisObj;
const jKey = this.thisObjByIndex(1).extractKeyFromThisObj;
// values
const iValue = this.thisObjByIndex(0).extractValueFromThisObj;
const jValue = this.thisObjByIndex(1).extractValueFromThisObj;
// reconstructed intraobject equality check
return {
[iKey]: iValue,
[jKey]: jValue
};
// outputs expected shape & key/value combos
// [LOG]: {
// "i": 3.14,
// "j": -7.28
// }
}
// superObj
recreateSuperObjByIndex() {
// keys
const iSuperKey = this.superObjByIndex(0).extractKeyFromSuperObj;
const jSuperKey = this.superObjByIndex(1).extractKeyFromSuperObj;
// values
const iSuperValue = this.superObjByIndex(0).extractValueFromSuperObj;
const jSuperValue = this.superObjByIndex(1).extractValueFromSuperObj;
return {
[iSuperKey]: iSuperValue,
[jSuperKey]: jSuperValue
};
}
crossCompareRebuiltThisAndSuperObjects() {
return this.recreateSuperObjFromDeconstructedThisMethods() ===
this.recreateSuperObjByIndex()
? true
: false;
} // returns True!!
}
// Boolean constructor wrapper used since instantiating these
// methods outside of an initialized Class can throw errors.
console.log(Boolean(new B(0).truthyCheck)); // Returns True!
console.log(Boolean(new C(3.14, -7.28).crossCompareRebuiltThisAndSuperObjects)); // returns True!