Перевизначення типу властивості інтерфейсу, визначеного у файлі Typescript d.ts


127

Чи є спосіб змінити тип властивості інтерфейсу, визначений у *.d.tsin typecript?

наприклад: Інтерфейс в x.d.tsвизначається як

interface A {
  property: number;
}

Я хочу змінити це у файлах машинопису, до яких я пишу

interface A {
  property: Object;
}

або навіть це спрацювало б

interface B extends A {
  property: Object;
}

Чи спрацює такий підхід? Це не спрацювало, коли я приміряв свою систему. Просто хочу підтвердити, чи можливо це взагалі?

Відповіді:


56

Ви не можете змінити тип існуючого властивості.

Ви можете додати властивість:

interface A {
    newProperty: any;
}

Але зміна типу існуючого:

interface A {
    property: any;
}

Результати помилки:

Подальші оголошення змінних повинні мати один і той же тип. Змінна 'властивість' повинна мати тип 'число', але тут є тип 'будь-яка'

Звичайно, ви можете мати власний інтерфейс, який розширює існуючий. У цьому випадку ви можете замінити тип лише на сумісний тип, наприклад:

interface A {
    x: string | number;
}

interface B extends A {
    x: number;
}

До речі, вам, мабуть, слід уникати використання Objectяк типу, замість цього використовуйте тип any.

У документації до anyтипу вказано:

Будь-який тип - це потужний спосіб роботи з існуючим JavaScript, що дозволяє вам поступово входити та вимикати перевірку типу під час компіляції. Ви можете очікувати, що Object зіграє подібну роль, як це робиться в інших мовах. Але змінні типу Object дозволяють лише призначати їм будь-яке значення - ви не можете викликати до них довільні методи, навіть ті, що насправді існують :

let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)

let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.

За допомогою Typescript> = 1.1 для перезапису типу методів за допомогою розширення інтерфейсу потрібно включити всі методи з оригінального інтерфейсу, інакше ви отримаєте помилку про несумісність типів див. Github.com/Microsoft/TypeScript/issues/978
jcubic

Ви можете опустити значення, які потрібно перезаписати, а потім перевизначити їх, чи можемо ми зробити відповідь @ZSkycat вирішувальною?
zeachco

«Проголосуйте проти» за посилання на Java як «інші мови»
wvdz,

@wvdz не те, що я дуже дбаю про голос проти, але про що ти говориш? де хтось навіть посилався на java? пошук сторінки "java" має лише одну знахідку, і це у вашому коментарі.
Nitzan Tomer

Можливо, я був трохи сварливим, але мене просто трохи дратувало те, що ти говорив "інші мови", коли міг просто сказати, як на Java. Або насправді багато інших мов, які мають Object універсальним базовим класом? Я знаю про C #, але, звичайно, C # був натхненний Java.
wvdz

227

Я використовую метод, який спочатку фільтрує поля, а потім поєднує їх.

посилання Виключити властивість із типу

interface A {
    x: string
}

export type B = Omit<A, 'x'> & { x: number };

для інтерфейсу:

interface A {
    x: string
}

interface B extends Omit<A, 'x'> {
  x: number
}

3
Це чудово це знати. Але проблема в тому, що вона все ще не модифікує існуючу.
Freewind

10
Це було саме те, що я шукав. Це те, як я очікував, що машинопис extendпрацюватиме за замовчуванням, але на жаль, це трохи Omitвсе виправляє 🙌
Доусон Б,

1
Розширення інтерфейсу було саме тим, що я шукав, дякую!
Mhodges

1
Примітка. Для використання вам знадобиться машинопис 3.5.3 вище.
Віксон

92
type ModifiedType = Modify<OriginalType, {
  a: number;
  b: number;
}>

interface ModifiedInterface extends Modify<OriginalType, {
  a: number;
  b: number;
}> {}

Натхненний рішенням ZSkycat extends Omit , я придумав таке:

type Modify<T, R> = Omit<T, keyof R> & R;

// before typescript@3.5
type Modify<T, R> = Pick<T, Exclude<keyof T, keyof R>> & R

Приклад:

interface OriginalInterface {
  a: string;
  b: boolean;
  c: number;
}

type ModifiedType  = Modify<OriginalInterface , {
  a: number;
  b: number;
}>

// ModifiedType = { a: number; b: number; c: number; }

Переходимо крок за кроком:

type R0 = Omit<OriginalType, 'a' | 'b'>        // { c: number; }
type R1 = R0 & {a: number, b: number }         // { a: number; b: number; c: number; }

type T0 = Exclude<'a' | 'b' | 'c' , 'a' | 'b'> // 'c'
type T1 = Pick<OriginalType, T0>               // { c: number; }
type T2 = T1 & {a: number, b: number }         // { a: number; b: number; c: number; }

Типи утиліт TypeScript


9
Це чудове рішення.
Остін Бранкхорст,

1
Noob тут, але ви переходите з інтерфейсу на тип у вашому прикладі ні? Або різниці немає?
Домінік

Niceeeeeeeeee: D
SaMiCoOo

1
@Dominic Хороший момент, я оновив відповідь. Два інтерфейси з однаковою назвою можуть об’єднуватися. typescriptlang.org/docs/handbook/…
Qwerty

34

Трохи розширивши відповідь @ zSkycat, ви можете створити загальний, який приймає два типи об'єктів і повертає об'єднаний тип з членами другого, замінюючи членами першого.

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type Merge<M, N> = Omit<M, Extract<keyof M, keyof N>> & N;

interface A {
    name: string;
    color?: string;
}

// redefine name to be string | number
type B = Merge<A, {
    name: string | number;
    favorite?: boolean;
}>;

let one: A = {
    name: 'asdf',
    color: 'blue'
};

// A can become B because the types are all compatible
let two: B = one;

let three: B = {
    name: 1
};

three.name = 'Bee';
three.favorite = true;
three.color = 'green';

// B cannot become A because the type of name (string | number) isn't compatible
// with A even though the value is a string
// Error: Type {...} is not assignable to type A
let four: A = three;

1
Дуже круто :-) Я робив це раніше з одним або двома властивостями за допомогою Omit, але це набагато крутіше :-) Я часто хочу "розширити" тип сутності сервера і змінити деякі речі, які є обов'язковими або необов'язковими для клієнта .
Simon_Weaver

1
Зараз це має бути прийнятим рішенням. Найпростіший спосіб "розширити" інтерфейс.
manuhortet

10

Omit властивість при розширенні інтерфейсу:

interface A {
  a: number;
  b: number;
}

interface B extends Omit<A, 'a'> {
  a: boolean;
}

3

Смішно, я витрачаю день на розслідування можливості вирішити одну і ту ж справу. Я виявив, що зробити це неможливо:

// a.ts - module
export interface A {
    x: string | any;
}

// b.ts - module
import {A} from './a';

type SomeOtherType = {
  coolStuff: number
}

interface B extends A {
    x: SomeOtherType;
}

Причина Модуль може не знати про всі доступні типи у вашій програмі. І це досить нудно портувати все звідусіль і робити такий код.

export interface A {
    x: A | B | C | D ... Million Types Later
}

Вам слід визначити тип пізніше, щоб автозаповнення працювало добре.


Тож можна трохи обдурити:

// a.ts - module
export interface A {
    x: string;
}

За замовчуванням залишено якийсь тип, який дозволяє автозавершення робіт, коли заміни не потрібні.

Тоді

// b.ts - module
import {A} from './a';

type SomeOtherType = {
  coolStuff: number
}

// @ts-ignore
interface B extends A {
    x: SomeOtherType;
}

Вимкніть дурний виняток тут, використовуючи @ts-ignoreпрапор, кажучи нам, що ми робимо щось не так. І дивно, що все працює, як очікувалося.

У моєму випадку я зменшую область бачення типу x, що дозволяє мені робити код більш суворим. Наприклад, у вас є список із 100 властивостей, і ви зменшуєте його до 10, щоб уникнути дурних ситуацій


3

Для звуження типу власності extendідеально підходить просте , як у відповіді Ніцана :

interface A {
    x: string | number;
}

interface B extends A {
    x: number;
}

Для розширення або взагалі перевизначення типу ви можете зробити рішення Zskycat :

interface A {
    x: string
}

export type B = Omit<A, 'x'> & { x: number };

Але якщо ваш інтерфейс Aрозширює загальний інтерфейс, ви втратите нестандартні типи властивостей A, що залишилися при використанні Omit.

напр

interface A extends Record<string | number, number | string | boolean> {
    x: string;
    y: boolean;
}

export type B = Omit<A, 'x'> & { x: number };

let b: B = { x: 2, y: "hi" }; // no error on b.y! 

Причина в тому, що Omitвнутрішньо переходить лише до Exclude<keyof A, 'x'>клавіш, які string | numberв нашому випадку будуть загальними . Отже, Bстане {x: number; }і приймає будь-яке зайве майно з типом number | string | boolean.


Щоб це виправити, я придумав інший OverridePropsтип утиліти:

type OverrideProps<M, N> = { [P in keyof M]: P extends keyof N ? N[P] : M[P] };

Приклад:

type OverrideProps<M, N> = { [P in keyof M]: P extends keyof N ? N[P] : M[P] };

interface A extends Record<string | number, number | string | boolean> {
    x: string;
    y: boolean;
}

export type B = OverrideProps<A, { x: number }>;

let b: B = { x: 2, y: "hi" }; // error: b.y should be boolean!

1

Якщо комусь іншому для цього потрібен загальний тип утиліти, я придумав таке рішення:

/**
 * Returns object T, but with T[K] overridden to type U.
 * @example
 * type MyObject = { a: number, b: string }
 * OverrideProperty<MyObject, "a", string> // returns { a: string, b: string }
 */
export type OverrideProperty<T, K extends keyof T, U> = Omit<T, K> & { [P in keyof Pick<T, K>]: U };

Мені це було потрібно, оскільки в моєму випадку ключовим фактором заміни було саме загальне значення.

Якщо у вас немає Omitготового, див. Виключення властивості з типу .


1
Це саме те, що я шукав, я не можу вам подякувати достатньо: D: D: D
dwoodwardgb

@dwoodwardgb рада, що це було корисно для когось іншого :-)
Тоні,

0

ПРИМІТКА: Не впевнений, що синтаксис, який я використовую у цій відповіді, був доступний під час написання старих відповідей, але я думаю, що це кращий підхід до того, як вирішити приклад, згаданий у цьому питанні.


У мене були деякі проблеми, пов’язані з цією темою (перезаписування властивостей інтерфейсу), і ось як я це обробляю:

  1. Спочатку створіть загальний інтерфейс з можливими типами, які ви хотіли б використовувати.

Ви навіть можете використовувати вибрати defaultзначення загального параметра, як ви можете бачити в<T extends number | SOME_OBJECT = number>

type SOME_OBJECT = { foo: "bar" }

interface INTERFACE_A <T extends number | SOME_OBJECT = number> {
  property: T;
}
  1. Тоді ви можете створити нові типи на основі цього контракту, передавши значення загальному параметру (або опустивши його та використовуючи значення за замовчуванням):
type A_NUMBER = INTERFACE_A;                   // USES THE default = number TYPE. SAME AS INTERFACE_A<number>
type A_SOME_OBJECT = INTERFACE_A<SOME_OBJECT>  // MAKES { property: SOME_OBJECT }

І ось результат:

const aNumber: A_NUMBER = {
    property: 111  // THIS EXPECTS A NUMBER
}

const anObject: A_SOME_OBJECT = {
    property: {   // THIS EXPECTS SOME_OBJECT
        foo: "bar"
    }
}

Машинописний майданчик

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.