Typescript — как использовать интерфейс для создания нового объекта, соответствующего другой структуре

#typescript #typescript-generics

#typescript #typescript-generics

Вопрос:

Я хочу создать объект, используя значения по умолчанию для интерфейса, но в то же время изменяя структуру объекта. Например, для этого интерфейса:

 interface IPerson {
  name: string
  age: number
}
  

Я хочу создать объект, подобный этому:

 const person: IPerson = {
  name: {
    type: String,
    required: true
  },
  age: {
    type: Number,
    required: true
  }
}
  

Единственный способ, который я нашел, это добавить тип объекта в name и age из
IPerson интерфейс, вот так:

 interface IPerson {
  name: string | IProp
  age: number | IProp
}

interface IProp {
  type: any
  required?: boolean
  // ...
}
  

Однако я не хочу менять исходный интерфейс IPerson . Итак, я был
думая о чем-то подобном:

 const person: IProperties<IPerson> = {
  // use properties of IPerson to determine which key/value pairs are valid in this object
}
  

Комментарии:

1. Я почти уверен, что отвечал на это раньше.. но найти его сложно…

Ответ №1:

Вы можете использовать сопоставленный тип:

 interface IPerson {
  name: string
  age: number
}

type IProperties<T> = {
    [K in keyof T]: IProp
}

interface IProp {
  type: any
  required?: boolean
  // …
}

const person: IProperties<IPerson> = {
    // …
}
  

Комментарии:

1. Да, это именно то, что нужно! Спасибо!

2. @Johannes возможно, я неправильно понял вопрос, но разве это не было бы полезно для required и type быть отражением переданного типа actualul ?

3. @TitianCernicova-Драгомир Да, вы правы. Это всего лишь пример, на самом деле не имеет значения, что такое required и type … Мне просто нужен был способ создания объектов, соответствующих ключам IPerson интерфейса.

Ответ №2:

Вы можете сделать это, используя условные типы и сопоставленные типы.

 interface IPerson {
name: string
age: number
ocupation?: string
}

type PropertyType<T> = 
    [T] extends [string] ? typeof String :
    [T] extends [number] ? typeof Number :
    [T] extends [Date] ? typeof Date :
    [T] extends [Function] ? typeof Function : // add any other types here
    new (...a:any[]) => T // default must be a constructor for whatever T is 

type IfRequired<T, K extends keyof T, IfYes, IfNo> =
    Pick<T, K> extends Required<Pick<T, K>> ? IfYes : IfNo

type PropertyDefinition<T> = {
    [P in keyof T]-?: {
        type: PropertyType<Exclude<T[P], null | undefined>> // will not work for unions!
        required: IfRequired<T, P, true, false>
    }
}

let personDefinition: PropertyDefinition<IPerson> ={
    age : {
        required: true,
        type: Number
    },
    name: {
        required: true,
        type: String,
    },
    ocupation: {
        required: false,
        type: String
    }
} 
  

Комментарии:

1. Таким образом, это фактически проверяет, например, age: number использует type: Number ли отображенный тип?

2. Синтаксис TypeScript может быть ужасным, но это более строгий ответ, чем мой.

3. @Johannes да, для age единственно допустимого значения { type: Number, require: true } любое другое значение было бы ошибкой компилятора. Я согласен с палео, это может быть сложнее для чтения с точки зрения синтаксиса, его ответ тоже хорош, только менее строгий, полностью ваш вызов, который вы используете 🙂

4. @TitianCernicova-Dragomir Это может быть другой вопрос, но просто для полноты картины: можно ли также опустить required , если для него установлено значение false? Потому что undefined это уже ложное значение.

5. @Johannes это должно сработать % type PropertyDefinition<T> = { [P in keyof T]-?: { type: PropertyType<Exclude<T[P], null | undefined>> } amp; IfRequired<T, P, { required: true }, { required?: false }> }