Как получить тип объекта, который клонируется из экземпляра класса?

Предположим, у меня есть этот примерный класс, но на самом деле у него гораздо больше свойств

class Foo {
  name: string
  dob: number

  constructor(name: string, dob: number) {
    this.name = name;
    this.dob = dob;
  }

  get age() {
     return new Date().getTime() - this.dob
  }
}

Теперь Typescript умен, и когда я создаю экземпляр класса, он предоставит мне все нужные свойства:

var classInstance = new Foo('name', new Date().getTime())

classInstance.name // ok
classInstance.dob // ok
classInstance.age // ok

Где-то в моем коде класс клонируется с помощью оператора распространения, я не уверен, что TS делает за сценой, но он действительно умен и дает мне все нужные свойства

var classJSON = {...classInstance};

classJSON.name // ok
classJSON.dob // ok
classJSON.age // missing

tsplayground

Это здорово, однако иногда мне нужно использовать тип classJSON .. Единственный способ, которым я могу его извлечь, - это сделать следующее:

var classJSON  = {...new Foo('', 0)}
type ClassJSONType = typeof classJSON; 

Есть ли способ извлечь тип прямо из Foo без необходимости создания экземпляра Javascript?


person lonewarrior556    schedule 17.03.2020    source источник
comment
Что вы имеете в виду под вторым типом? Форма экземпляра класса? Если так, то это просто Foo   -  person Aleksey L.    schedule 17.03.2020
comment
Обновлено, чтобы было понятнее.   -  person lonewarrior556    schedule 17.03.2020
comment
Помогает ли это?   -  person mahalde    schedule 17.03.2020
comment
Я чувствую, что мой вопрос может помочь ответить на этот вопрос, но тег - ts2.0 ... с тех пор многое изменилось   -  person lonewarrior556    schedule 19.03.2020
comment
@ lonewarrior556 ответ, указанный выше, дает правильное решение. Он по-прежнему работает в ts3.x.   -  person hackape    schedule 27.03.2020
comment
@hackape проблема, являющаяся принятым ответом, не является той, которая имеет решение.   -  person lonewarrior556    schedule 27.03.2020


Ответы (4)


Отказ от ответственности: этот пост представляет собой мод, основанный на ответе Мэтта Маккатчена. Это надежное решение, чистый TS без каких-либо эффектов времени выполнения JS.

type IfEquals<X, Y, T> =
    (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? T : never;

type JSONify<T> = Pick<T, {
  [P in keyof T]: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>
}[keyof T]>;

Вышеупомянутый трюк исключает все readonly поля. Для дальнейшего исключения методов используйте следующее:

type JSONify<T> = Pick<T, {
  [P in keyof T]: IfEquals<{ [Q in P]: T[P] extends Function ? never : T[P] }, { -readonly [Q in P]: T[P] }, P>
}[keyof T]>;

Детская площадка

person hackape    schedule 26.03.2020
comment
Вопрос, вероятно, следует закрыть как дублированный, но награда препятствует действию, поэтому я публикую этот. - person hackape; 27.03.2020
comment
Имейте в виду, что это решение не может исключать унаследованные методы и исключает readonly поля. - person Shlang; 28.03.2020
comment
@Shlang Исходный ответ исключает readonly поля, что касается методов, см. Мое редактирование. - person hackape; 31.03.2020
comment
Спасибо, но у него все еще есть некоторые ограничения. Посетите эту площадку - person Shlang; 31.03.2020
comment
@Shlang Теперь я понял вашу точку зрения. Да вы правы, это решение не охватывает все аспекты. Система типов просто слепа к собственному свойству объекта. - person hackape; 31.03.2020

Если все, что вы пытаетесь избежать, - это инициализировать Foo, вы можете сделать что-то вроде этого:

var funRetClassJSON = () => ({ ...Foo.prototype });
type TypeOfClassJSON = ReturnType<typeof funRetClassJSON>;

or

var nullClassJSON = false as any ? null : ({ ...Foo.prototype });
type AltTypeOfClassJSON = NonNullable<typeof nullClassJSON>;

Конечно, оба этих примера все еще влекут за собой некоторые штрафы во время выполнения, так как некоторые JS все еще генерируются.

person artcorpse    schedule 26.03.2020
comment
Мне очень нравится этот, хотя и не чистый TS, он позволяет избежать инициализации Foo, который на самом деле имел сложный конструктор. - person lonewarrior556; 27.03.2020

На данный момент это невозможно, потому что система типов TS не позволяет отслеживать чужие свойства. Существует предложение именно для того, что вы хотите. В этом комментарии также описаны возможные способы выражения оператора Spread без удаления не- собственная недвижимость.

person Shlang    schedule 24.03.2020

Если вы хотите, чтобы тип сохранялся при его клонировании, вы можете использовать утверждение типа.

var classJSON = {...classInstance} as Foo;

Это напрямую проинформирует Typescript о предполагаемом типе объекта. Без этой аннотации определение типа с помощью оператора распространения может быть невозможно.

person SpaceKatt    schedule 27.03.2020