'Pick' only refers to a type, but is being

2020-08-26 11:01发布

问题:

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?

回答1:

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

  • Optional parameters remain optional
  • Argument names are preserved
  • Works for any number of arguments


标签: typescript