I'm trying to have only some properties of ancestor exposed on my descendant. I try to achieve it through Pick
export class Base {
public a;
public b;
public c;
}
export class PartialDescendant extends Pick<Base, 'a' |'b'> {
public y;
}
but I receive two errors -
Error: TS2693: 'Pick' only refers to a type, but is being used as a value here.
and
Error:TS4020: 'extends' clause of exported class 'PartialDescendant' has or is using private name 'Pick'.
Am I doing something wrong, and is there another way to expose only chosen properties of the base class?
See below for 3.0 solution
Pick is only a type it is not a class, a class is both a type and an object constructor. Types only exist at compile time, this is why you get the error.
You can create a function which takes in a constructor, and returns a new constructor that will instantiate an object with less fields (or at least declare it does):
export class Base {
public c: number = 0;
constructor(public a: number, public b: number) {
}
}
function pickConstructor<T extends { new (...args: any[]) : any, prototype: any }>(ctor: T)
: <TKeys extends keyof InstanceType<T>>(...keys: TKeys[]) => ReplaceInstanceType<T, Pick<InstanceType<T>, TKeys>> & { [P in keyof Omit<T, 'prototype'>] : T[P] } {
return function (keys: string) { return ctor as any };
}
export class PartialDescendant extends pickConstructor(Base)("a", "b") {
public constructor(a: number, b: number) {
super(a, b)
}
}
var r = new PartialDescendant(0,1);
type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;
type ReplaceInstanceType<T, TNewInstance> = T extends new (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => infer R ? (
IsValidArg<J> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => TNewInstance :
IsValidArg<I> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => TNewInstance :
IsValidArg<H> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => TNewInstance :
IsValidArg<G> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => TNewInstance :
IsValidArg<F> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F) => TNewInstance :
IsValidArg<E> extends true ? new (a: A, b: B, c: C, d: D, e: E) => TNewInstance :
IsValidArg<D> extends true ? new (a: A, b: B, c: C, d: D) => TNewInstance :
IsValidArg<C> extends true ? new (a: A, b: B, c: C) => TNewInstance :
IsValidArg<B> extends true ? new (a: A, b: B) => TNewInstance :
IsValidArg<A> extends true ? new (a: A) => TNewInstance :
new () => TNewInstance
) : never
For constructors parameters you will loose things like parameter names, optional parameters and multiple signatures.
Edit
Since the original question was answered typescript has improved the possible solution to this problem. With the addition of Tuples in rest parameters and spread expressions we now don't need to have all the overloads for ReplaceReturnType:
export class Base {
public c: number = 0;
constructor(public a: number, public b: number) {
}
}
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
function pickConstructor<T extends { new (...args: any[]) : any, prototype: any }>(ctor: T)
: <TKeys extends keyof InstanceType<T>>(...keys: TKeys[]) => ReplaceInstanceType<T, Pick<InstanceType<T>, TKeys>> & { [P in keyof Omit<T, 'prototype'>] : T[P] } {
return function (keys: string| symbol | number) { return ctor as any };
}
export class PartialDescendant extends pickConstructor(Base)("a", "b") {
public constructor(a: number, b: number) {
super(a, b)
}
}
var r = new PartialDescendant(0,1);
type ArgumentTypes<T> = T extends new (... args: infer U ) => any ? U: never;
type ReplaceInstanceType<T, TNewInstance> = T extends new (...args: any[])=> any ? new (...a: ArgumentTypes<T>) => TNewInstance : never;
Not only is this shorter but it solves a number of problems
I am a little late to the game here, but there is an alternative and shorter way to do it if you're mainly interested in making intellisense work.
You can extend the base class and then redeclare the members you want to omit as private. This will generate a typescript error, but adding //@ts-ignore will clear it up and shouldn't affect compilation.
This is my preferred way to do it when things are simple. No real overhead here or challenging type syntax. The only real downside here is that adding //@ts-ignore above the extending class could prevent you from receiving other error messages related to incorrectly extending the Base class.
The one advantage to this approach over the accepted "pickConstructor" approach is that this method doesn't generate any extra code. Whereas "pickConstructor" literally exists as a function after compilation that runs during class definition.
class Base
{
public name:string;
}
// @ts-ignore
class Ext extends Base
{
private readonly name:undefined; // re-declare
}
let thing:Ext = new Ext();
// The line below...
// Doesn't show up in intellisense
// complains about privacy
// can't be set to anything
// can't be used as an object
thing.name = "test"; // ERROR
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With