Substituindo o tipo de propriedade de interface definido no arquivo Typescript d.ts

112

Existe uma maneira de alterar o tipo de propriedade de interface definida em um *.d.tsin typescript?

por exemplo: Uma interface em x.d.tsé definida como

interface A {
  property: number;
}

Eu quero mudar isso nos arquivos datilografados para os quais escrevo

interface A {
  property: Object;
}

ou mesmo isso funcionaria

interface B extends A {
  property: Object;
}

Essa abordagem funcionará? Não funcionou quando tentei no meu sistema. Só quer confirmar se isso é possível?

Abdul23
fonte

Respostas:

50

Você não pode alterar o tipo de uma propriedade existente.

Você pode adicionar uma propriedade:

interface A {
    newProperty: any;
}

Mas mudando um tipo de existente:

interface A {
    property: any;
}

Resulta em um erro:

As declarações de variáveis ​​subsequentes devem ter o mesmo tipo. A variável 'propriedade' deve ser do tipo 'número', mas aqui tem o tipo 'qualquer'

É claro que você pode ter sua própria interface que estende uma já existente. Nesse caso, você pode substituir um tipo apenas por um tipo compatível, por exemplo:

interface A {
    x: string | number;
}

interface B extends A {
    x: number;
}

A propósito, você provavelmente deve evitar usar Objectcomo um tipo, em vez disso, use o tipo any.

Nos documentos do anytipo, ele afirma:

O any type é uma maneira poderosa de trabalhar com o JavaScript existente, permitindo que você gradualmente aceite e desative a verificação de tipo durante a compilação. Você pode esperar que Object desempenhe uma função semelhante, como em outras linguagens. Mas variáveis ​​do tipo Object permitem apenas que você atribua qualquer valor a elas - você não pode chamar métodos arbitrários sobre elas, mesmo aqueles que realmente existem :

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'.
Nitzan Tomer
fonte
Com Typescript> = 1.1, para sobrescrever o tipo dos métodos estendendo a interface, você precisa incluir todos os métodos da interface original, caso contrário, você receberá um erro de que os tipos são incompatíveis, consulte github.com/Microsoft/TypeScript/issues/978
jcubic
você pode Omitir os valores que deseja sobrescrever primeiro e depois redefini-los. Podemos tornar a resposta de @ZSkycat a solução certa?
zeachco
Votar negativamente
@wvdz não que eu me importe muito com o downvote, mas do que você está falando? onde alguém se referiu a java? pesquisar a página por "java" tem apenas um achado e está no seu comentário.
Nitzan Tomer
Talvez eu estivesse um pouco mal-humorado, mas me irritou um pouco que você disse "outras linguagens" quando poderia apenas ter dito, como em Java. Ou existem muitas outras linguagens que têm Object como a classe base universal? Eu sei sobre C #, mas é claro que C # foi fortemente inspirado em Java.
wvdz
200

Eu uso um método que primeiro filtra os campos e depois os combina.

referência Excluir propriedade do tipo

interface A {
    x: string
}

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

para interface:

interface A {
    x: string
}

interface B extends Omit<A, 'x'> {
  x: number
}
ZSkycat
fonte
2
É ótimo saber disso. Mas o problema é que ainda não está modificando o existente.
Freewind
8
Isso era exatamente o que eu estava procurando. É como eu esperava que a digitação extendfuncionasse por padrão, mas, infelizmente, isso Omitcorrige tudo 🙌
Dawson B
1
Estender a interface era exatamente o que eu procurava, obrigado!
mhodges
1
Nota: Você precisará do typescript 3.5.3 acima para usar isso.
Vixson
89
type ModifiedType = Modify<OriginalType, {
  a: number;
  b: number;
}>

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

Inspirado pela solução ZSkycat extends Omit , eu vim com isto:

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

// before [email protected]
type Modify<T, R> = Pick<T, Exclude<keyof T, keyof R>> & R

Exemplo:

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

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

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

Passo a passo:

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; }

Tipos de utilitários TypeScript

Qwerty
fonte
8
Esta é uma otima soluçao.
Austin Brunkhorst
1
Noob aqui, mas você mudou de uma interface para um tipo em seu exemplo, não? Ou não há diferença?
Dominic
Niceeeeeeeeee: D
SaMiCoOo
1
@Dominic Bom argumento, atualizei a resposta. Duas interfaces com o mesmo nome podem ser mescladas. typescriptlang.org/docs/handbook/…
Qwerty
33

Estendendo um pouco a resposta de @zSkycat, você pode criar um genérico que aceita dois tipos de objeto e retorna um tipo mesclado com os membros do segundo substituindo os membros do primeiro.

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;
Ryanjduffy
fonte
1
Muito legal :-) Eu fiz isso antes com uma ou duas propriedades com Omit, mas isso é muito mais legal :-) Eu geralmente quero 'estender' um tipo de entidade de servidor e alterar algumas coisas para serem obrigatórias ou opcionais no cliente .
Simon_Weaver
1
Esta deve ser a solução aceita agora. A maneira mais limpa de "estender" uma interface.
manuhortet
7

Omit a propriedade ao estender a interface:

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

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

É engraçado eu passar o dia investigando a possibilidade de resolver o mesmo caso. Descobri que não é possível fazer desta forma:

// 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;
}

Causa Um módulo pode não saber sobre todos os tipos disponíveis em seu aplicativo. E é muito chato portar tudo de qualquer lugar e fazer código como este.

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

Você precisa definir o tipo mais tarde para que o preenchimento automático funcione bem.


Então você pode trapacear um pouco:

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

Deixou alguns tipos por padrão, que permitem trabalhos de preenchimento automático, quando as substituições não são necessárias.

Então

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

type SomeOtherType = {
  coolStuff: number
}

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

Desative a exceção estúpida aqui usando @ts-ignoreflag, dizendo que estamos fazendo algo errado. E o engraçado é que tudo funciona conforme o esperado.

No meu caso estou reduzindo a visão do escopo do tipo x, isso me permite fazer código mais restrito. Por exemplo, você tem uma lista de 100 propriedades e reduz para 10, para evitar situações estúpidas

Egor Malkevich
fonte
2

Para restringir o tipo da propriedade, o simples extendfunciona perfeitamente, como na resposta de Nitzan :

interface A {
    x: string | number;
}

interface B extends A {
    x: number;
}

Para ampliar, ou geralmente substituir o tipo, você pode fazer a solução do Zskycat :

interface A {
    x: string
}

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

Mas, se a sua interface Aestender uma interface geral, você perderá os tipos personalizados das Apropriedades restantes de ao usar Omit.

por exemplo

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! 

O motivo é que Omitinternamente apenas analisa as Exclude<keyof A, 'x'>chaves, o que será o geral string | numberem nosso caso. Portanto, Bse tornaria {x: number; }e aceitaria qualquer propriedade extra com o tipo de number | string | boolean.


Para corrigir isso, criei um OverridePropstipo de utilitário diferente, como segue:

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

Exemplo:

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!
Auxílio em
fonte
1

Se outra pessoa precisar de um tipo de utilitário genérico para fazer isso, eu encontrei a seguinte solução:

/**
 * 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 };

Eu precisava disso porque, no meu caso, a chave a ser substituída era o próprio genérico.

Se você não tiver Omitpronto, consulte Excluir propriedade do tipo .

Toni
fonte
1
Isso é exatamente o que eu estava procurando, não posso agradecer o suficiente: D: D: D
dwoodwardgb
@dwoodwardgb que bom que foi útil para outra pessoa :-)
Toni