"Pick" относится только к типу, но используется здесь как значение при попытке расширить Pick<

Я пытаюсь показать только некоторые свойства предка на моем потомке. Я пытаюсь добиться этого через Pick

export class Base {
    public a;
    public b;
    public c;
}

export class PartialDescendant extends Pick<Base, 'a' |'b'> {
   public y;
}

но я получаю две ошибки -

Ошибка: TS2693: "Выбор" относится только к типу, но здесь используется в качестве значения.

а также

Ошибка:TS4020: предложение "extends" экспортируемого класса "PartialDescendant" имеет или использует закрытое имя "Pick".

Я делаю что-то не так, и есть ли другой способ выставить только выбранные свойства базового класса?

2 ответа

Решение

Смотрите ниже для решения 3.0

Pick это всего лишь тип, это не класс, а класс является как типом, так и конструктором объекта. Типы существуют только во время компиляции, поэтому вы получаете ошибку.

Вы можете создать функцию, которая принимает конструктор и возвращает новый конструктор, который будет создавать экземпляр объекта с меньшим количеством полей (или, по крайней мере, объявить, что он это делает):

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

Для параметров конструкторов вы потеряете такие вещи, как имена параметров, необязательные параметры и несколько подписей.

редактировать

Поскольку на первоначальный вопрос был дан ответ, машинопись улучшила возможное решение этой проблемы. С добавлением кортежей в параметрах отдыха и выражениях распределений нам теперь не нужно иметь все перегрузки для 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 (... args: infer U ) => any ? U: never;
type ReplaceInstanceType<T, TNewInstance> = T extends new (...args: any[])=> infer R ? new (...a: ArgumentTypes<T>) => TNewInstance : never;

Это не только короче, но и решает ряд проблем

  • Необязательные параметры остаются необязательными
  • Имена аргументов сохраняются
  • Работает для любого количества аргументов

Я немного опоздал с игрой, но есть альтернативный и более короткий способ сделать это, если вы в основном заинтересованы в работе intellisense.

Вы можете расширить базовый класс, а затем повторно объявить элементы, которые вы хотите опустить, как частные. Это вызовет ошибку машинописного текста, но добавление //@ts-ignore устранит ее и не повлияет на компиляцию.

Я предпочитаю это делать, когда все просто. Никаких реальных накладных расходов или сложного синтаксиса типов. Единственным реальным недостатком здесь является то, что добавление //@ts-ignore над расширяющимся классом может помешать вам получать другие сообщения об ошибках, связанные с неправильным расширением базового класса.

Единственное преимущество этого подхода по сравнению с принятым подходом «pickConstructor» состоит в том, что этот метод не генерирует никакого дополнительного кода. В то время как pickConstructor буквально существует как функция после компиляции, которая выполняется во время определения класса.

      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
Другие вопросы по тегам