Создайте производный тип из класса, но опустите конструктор (машинопись)

У меня есть интерфейс и класс, определенные следующим образом:

interface Foo {
  constructor: typeof Foo;
}

class Foo {
  static bar = 'bar';

  constructor(data: Partial<Foo>) {
    Object.assign(this, data);
  }

  someMethod() {
    return this.constructor.bar;
  }

  prop1: string;
  prop2: number;
}

Интерфейс нужен, чтобы this.constructor был строго типизирован. Однако это лишает меня возможности передавать простой объект в конструктор класса:

const foo = new Foo({ prop1: 'asdf', prop2: 1234 });

// Argument of type '{ prop1: string; prop2: number; }' is not assignable to parameter of type 'Partial<Foo>'.
//  Types of property 'constructor' are incompatible.
//    Type 'Function' is not assignable to type 'typeof Foo'.
//      Type 'Function' provides no match for the signature 'new (data: Partial<Foo>): Foo'.

Я понимаю сообщение об ошибке, но не знаю, как его обойти. Есть ли способ Partial<Foo>, который позволяет мне передать простой объект? Вот детская площадка:

Playground


person Ryan Wheale    schedule 25.10.2019    source источник
comment
Не уверен, каково ваше намерение, похоже, вы пытаетесь получить доступ к статическому свойству из экземпляра, что невозможно, также интерфейс избыточен, checkout this   -  person Medet Tleukabiluly    schedule 25.10.2019
comment
Спасибо, Эрик. Вы можете получить доступ к статическим свойствам из экземпляра через this.constructor, и это не редкость. Резервный интерфейс также необходим, потому что вы не можете ввести метод constructor в определении класса. При этом используется слияние интерфейсов, которое является функцией TypeScript и также не редкость: github .com/microsoft/TypeScript/issues/   -  person Ryan Wheale    schedule 25.10.2019


Ответы (3)


Вот фактический тип, создающий производный тип из класса без конструктора (как в заголовке вопроса) и сохраняющий обычные методы:

type NonConstructorKeys<T> = ({[P in keyof T]: T[P] extends new () => any ? never : P })[keyof T];
type NonConstructor<T> = Pick<T, NonConstructorKeys<T>>;

Использование с Foo из вопроса:

type FooNonConstructorKeys = NonConstructorKeys<Foo>; // "prop1" | "prop2" | "someMethod"
type FooNonConstructor = NonConstructor<Foo>;
person Terite    schedule 28.09.2020

В итоге я нашел то, что мне нужно, в этом замечательном ответе:

как удалить свойства с помощью сопоставленного типа в TypeScript

Код в этом ответе создает производный тип, содержащий только методы. Мне нужно было сделать обратное этому. Следующий помощник NonMethods<T> создает производный тип со всеми удаленными методами.

type NonMethodKeys<T> = ({[P in keyof T]: T[P] extends Function ? never : P })[keyof T];  
type NonMethods<T> = Pick<T, NonMethodKeys<T>>; 

Here's the Playground

person Ryan Wheale    schedule 25.10.2019

Похоже, вы хотите определить интерфейс, а затем использовать его, тогда вам нужно определить свойства в самом интерфейсе, а не в классе.

interface Foo {
  prop1: string; // define your properties here
  prop2: number;
}

class Foo {
  static bar = 'bar';

  constructor(data: Partial<Foo>) {
    Object.assign(this, data);
  }

  someMethod() {
    return Foo.bar; // notice how I access static variables now
  }

}

const foo = new Foo({ prop1: 'asdf', prop2: 1234 });

Playground

person weegee    schedule 25.10.2019
comment
Спасибо. Мне нужен this.constructor, потому что это базовый класс (например, абстрактный), который расширяется другими классами. Каждый расширенный класс будет определять свое собственное значение бара. - person Ryan Wheale; 26.10.2019