覆盖Typescript d.ts文件中定义的接口属性类型

是否有一种方法可以改变typescript中*.d.ts中定义的接口属性类型?

例如< p >: x.d.ts中的接口定义为

interface A {
property: number;
}

我想在我写入的typescript文件中改变它

interface A {
property: Object;
}

甚至这个也可以

interface B extends A {
property: Object;
}

这种方法有效吗?当我试我的系统时,它不工作。只是想确认一下有没有可能?

327010 次浏览

不能更改现有属性的类型。

你可以添加一个属性:

interface A {
newProperty: any;
}

而是改变现有的一种类型:

interface A {
property: any;
}

导致一个错误:

后续变量声明必须具有相同的类型。变量 'property'必须是'number'类型,但这里有'any'类型

当然,您可以拥有自己的接口来扩展现有的接口。在这种情况下,你可以重写一个类型到一个兼容的类型,例如:

interface A {
x: string | number;
}


interface B extends A {
x: number;
}

顺便说一下,你可能应该避免使用Object作为类型,而是使用类型any

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'.
有趣的是,我花了一天的时间调查解决同一案件的可能性。 我发现这样做是不可能的:

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

默认保留some类型,当不需要重写时,允许自动完成工作。

然后

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


type SomeOtherType = {
coolStuff: number
}


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

在这里使用@ts-ignore标志禁用愚蠢的异常,告诉我们我们做错了什么。有趣的是,一切都按照预期进行。

在我的例子中,我减少了类型x的作用域,它允许我做更严格的代码。例如,你有一个100个属性的列表,你把它减少到10个,以避免愚蠢的情况

我使用一种方法,首先过滤字段,然后组合它们。

参考从类型中排除属性

interface A {
x: string
}


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

的接口:

interface A {
x: string
}


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

稍微扩展一下@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;
 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 Utility Types .


深度修改v3

interface Original {
a: {
a: string
b: { a: string }
c: string
d: string         // <- keep this one
}
}


interface Overrides {
a: {
a: { a: number }  // <- overwrite string with object
b: number         // <- overwrite object with number
c: number         // <- overwrite string with number
e: number         // <- new property
}
}


type ModifiedType = ModifyDeep<Original, Overrides>
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}
const example: ModifiedType = {
a: {
a: { a: number },
b: number,
c: number,
d: string,
e: number,
}
}

找到ModifyDeep 下面

如果其他人需要一个通用的实用程序类型来做到这一点,我提出了以下解决方案:

/**
* 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,请参阅从类型中排除属性

要缩小属性的类型,简单的extend可以完美地工作,如Nitzan的回答:

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扩展了一个通用接口,那么在使用Omit时,你将失去A剩余属性的自定义类型。

如。

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!

Omit扩展接口时的属性:

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


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

注意:不确定我在这个答案中使用的语法是否可用,当旧的答案被编写时,但是我认为这是一个更好的方法来解决这个问题中提到的例子。


我有一些与这个主题相关的问题(覆盖接口属性),这是我如何处理它:

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

< a href = " https://www.typescriptlang.org/play?代码/ C4TwDgpgBAyg8gWQKIH04CEBSSDCAVKAXigG8oAzAe0oC4oAiAIwEMAneqAXwChuBLAHbAIrcswDG0AJIA5PEgBKAMQCCOVCqgAeAhAAewgQBMAzlAEBXALaMRUAD6xEqDNnxFz126wB8pblBQYKyUkKygdHgA3Nw83KCQUCooMgCqCOiKHrLyymoaUYFFxSUlAPRlUKkwSDBQeAASSFBGEGIWADbAHpY2dngAmgAKSAB0sCrISXU5iqrqKCpavd4 + 8 edqyfdiafi4bmszeqtl2y57 + H5QFVAIKgDStaRBIWERTjuu + 1 y84pqcjm6zbkxhedgsaqywwijacrwcore7wajkjajdgli6kgabojfb1tsqzikwk-f6aqdmarwrgakwg4ma4jqz12bgo-mkcleidozhrlux2lx + zqbK + + DhpSotAYLHY0p + nCAA”rel="nofollow noreferrer">Typescript playground

对于像我这样的懒人来说,简单的答案是:

type Overrided = Omit<YourInterface, 'overrideField'> & { overrideField: <type> };
interface Overrided extends Omit<YourInterface, 'overrideField'> {
overrideField: <type>
}

我已经创建了这个类型,允许我轻松地覆盖嵌套接口:

export type DeepPartialAny<T> = {
[P in keyof T]?: T[P] extends Obj ? DeepPartialAny<T[P]> : any;
};


export type Override<A extends Obj, AOverride extends DeepPartialAny<A>> = { [K in keyof A]:
AOverride[K] extends never
? A[K]
: AOverride[K] extends Obj
? Override<A[K], AOverride[K]>
: AOverride[K]
};

然后你可以这样使用它:

interface Foo {
Bar: {
Baz: string;
};
}
type Foo2 = Override<Foo, { Bar: { Baz: number } }>;


const bar: Foo2['Bar']['Baz'] = 1; // number;
< p >日期:19/3/2021。 我认为最新的typescript(4.1.2)版本在d.ts文件中支持interface覆盖
// in test.d.ts


interface A {
a: string
}


export interface B extends A {
a: number
}


// in any ts file
import { B } from 'test.d.ts'


// this will work
const test: B = { a: 3 }


// this will not work
const test1: B = { a: "3" }


如果你只想修改一个现有属性的类型,而不是删除它,那么,就足够了:

// Style that accepts both number and percent(string)
type BoxStyle = {
height?: string | number,
width?: string | number,
padding?: string | number,
borderRadius?: string | number,
}


// These are both valid
const box1: BoxStyle = {height: '20%', width: '20%', padding: 0, borderRadius: 5}
const box2: BoxStyle = {height: 85, width: 85, padding: 0, borderRadius: 5}


// Override height and width to be only numbers
type BoxStyleNumeric = BoxStyle & {
height?: number,
width?: number,
}


// This is still valid
const box3: BoxStyleNumeric = {height: 85, width: 85, padding: 0, borderRadius: 5}


// This is not valid anymore
const box4: BoxStyleNumeric = {height: '20%', width: '20%', padding: 0, borderRadius: 5}

修改实用程序类型解决方案,将R的键限制为T中的键,并添加智能感知

export type Modify<T, R extends Partial<Record<keyof T, any>>> = Omit<T, keyof R> & R;

基于ZSkycat的回答非常棒,你可以创建一个抽象的Override泛型类型,使用起来很方便,并且清楚地解释了代码的意图。

type Override<T, K extends keyof T, N> = Omit<T, K> & { [K1 in K]: N };

地点:

  • T =现有类型
  • K =你想要覆盖的类型的键
  • N =要覆盖的现有类型的键的新类型

使用示例:

type GraphQLCodegenConfig = Override<CodegenConfig, 'schema', DocumentNode>;

创建修饰符类型

type Modify<T, R extends {[P in keyof T]:any} > = Omit<T, keyof R> & R;

你可以

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

它会给你一个类型自动完成

你可以使用这个类型的别名:

type Override<T, K extends { [P in keyof T]: any } | string> =
K extends string
? Omit<T, K>
: Omit<T, keyof K> & K;

在下面的语法中使用alike:

全球接口

interface IFirst {
username: string;
}

接口由override命名

interface ISecond extends Override<IFirst, 'username'> {
username: number;
}

类型别名override

type IThird = Override<IFirst, { username: boolean }>;

深度修改v3

* 注意,版本2是在历史这个答案

interface Original {
a: {
a: string
b: { a: string }
c: string
d: string         // <- keep this one
}
}


interface Overrides {
a: {
a: { a: number }  // <- overwrite string with object
b: number         // <- overwrite object with number
c: number         // <- overwrite string with number
e: number         // <- new property
}
}


type ModifiedType = ModifyDeep<Original, Overrides>
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}
结果
const example: ModifiedType = {
a: {
a: { a: number },
b: number,
c: number,
d: string,
e: number,
}
}
的代码
type ModifyDeep<A, B extends DeepPartialAny<A>> = {
[K in keyof A | keyof B]:          // For all keys in A and B:
K extends keyof A                // ───┐
? K extends keyof B            // ───┼─ key K exists in both A and B
? A[K] extends AnyObject     //    │  ┴──┐
? B[K] extends AnyObject   //    │  ───┼─ both A and B are objects
? ModifyDeep<A[K], B[K]> //    │     │  └─── We need to go deeper (recursively)
: B[K]                   //    │     ├─ B is a primitive 🠆 use B as the final type (new type)
: B[K]                     //    │     └─ A is a primitive 🠆 use B as the final type (new type)
: A[K]                       //    ├─ key only exists in A 🠆 use A as the final type (original type)
: B[K]                         //    └─ key only exists in B 🠆 use B as the final type (new type)
}


type AnyObject = Record<string, any>


// This type is here only for some intellisense for the overrides object
type DeepPartialAny<T> = {
/** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
[P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any
}

*注意,类型DeepPartialAny只是用于类型提示,但它是完美的。从技术上讲,ModifyDeep类型的逻辑允许用对象{a: {b: ... }}替换叶子节点{a: string},反之亦然,但当用一个扁平原语覆盖object时,DeepPartialAny会报错,出现这样的错误

Type 'number' has no properties in common with type 'DeepPartialAny<{ a: string; }>'

但是,你可以安全地忽略这个错误(使用/// @ts-ignore或完全删除extends DeepPartialAny约束)。结果类型的计算是正确的。

例子

<一个href = " https://www.typescriptlang.org/play?代码/ C4TwDgpgBAsg9gEwJYDMQBEITAHgIIA0UAQlBAB7AQB2CAzlJtgAoCGATsEqwDZ7Uh8APiFQAvFADeAKChQA2gGkoSalADWEEHBRQ8UAD4atOkgF0AXHOs2os68opVaDTdt37bXm-ZsB % 20 kedkgnpjdxjvw19balwlmzjglz0bahkaiwarcabjycjiuqdiemtnup4qdoy862jigphknczcomuzihko0xrvk26ewsk % 20 mwhs4ehrqct2ocmpr3gohdxpaf9pavbifkqs3pyjacvcuhyehdpgdluacyjwasetnehwtk5upgecabvrbizhiapqakjbsfymgyefyoqaflawow4jbocaogiuhbqlwoe8efbgabxdjuwhwpe8og6ffoiayltuybwakcr48hhwadu9ygkhoxpa0dgaddgbcebagolmxj2ki % 20 dq7uyqaa6kbgkH2eTMZlhUx-Mx % 20 kx-pujjwhbivaqhkabd4cli8sr-c2ikxpeablbsvrudgoehqvk3o6qfhaglwanyvhwa63fxrdkxmnqjp8zy2hlphozm7z % 20 w5qbpwvju72pnsdnzlvqtabf0sxnbnkhiwkpdshjrgtxjosdpuekaw3sjmbojbubwafosv2ebcqbx % 20 xzgei4ac8qmrkoj1piidzwul4yv0g1zuavln0jd5p2nea8py0 % 20 p % 20 sz3pf6wees1aqdyucikgpbevs6kgnecahuoz6fkev5qebieokgy7xkgeriqi0e % 20 nipzwbwbafuw07wmh55oviu75fbjiwc2p4aalahq85ihc1dnnayhipkpb8k8df3lad4egkpcspkyq8cs0dskwsb0dk7jkiqqq8fucakncsjbkpmokjwxanm8kgmdkva5kzskqesotnowhw8d6llnkgscox8irilaicobgwc4gg3grjwrcpf2uoyi8becvxpf8s2gbttaxkod5caajktkgizlnagxncf2a4ofez4lfuaxujvaijzbsmhfzsjiy5bwg - jgxhdjfopckmtarl9bhw6ycsw3crsqksec6j3nizkuvkwbwflov %20bsQL2AO0QDiOCFfgQ%206oSd7Bnbm%20aJkWKpEB%20l2Ntd1gkeRr3rEQ9jlvWDzVrd64Nl99gkcOZHPSDLXSjkMmuaBuLXBdR7sPBKPSDDcPQKtSN-XdlYY7kWMI2tUAktQ6i8Ty1BWOTlO8tQQA" rel="nofollow noreferrer">TypeScript Playground

type ModifyDeep<A, B extends DeepPartialAny<A>> = {
[K in keyof A | keyof B]:
K extends keyof A
? K extends keyof B
? A[K] extends AnyObject
? B[K] extends AnyObject
? ModifyDeep<A[K], B[K]>
: B[K]
: B[K]
: A[K]
: B[K]
}


type AnyObject = Record<string, any>


type DeepPartialAny<T> = {
/** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
[P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any
}




interface Original {
a: {
a: string
b: { a: string }
c: { a: string }
}
b: string
c: { a: string }
}


interface Overrides {
a: {
a: { a: number }  // <- overwrite string with object
b: number         // <- overwrite object with number
c: { b: number }  // <- add new child property
d: number         // <- new primitive property
}
d: { a: number }    // <- new object property
}


//@ts-ignore // overriding an object with a flat value raises an error although the resulting type is calculated correctly
type ModifiedType = ModifyDeep<Original, Overrides>
//@ts-ignore
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}




// Try modifying the properties here to prove that the type is working
const t: ModifiedType = {
a: {
a: { a: 0 },
b: 0,
c: { a: '', b: 0},
d: 0,
},
b: '',
c: { a: '' },
d: { a: 0 },
}

更好的解决方案是使用下面的答案的修改类型(双关)

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

这也将检查你覆盖的键是否也存在于原始接口中,从而确保如果原始接口更改了名称,那么你将得到编译时错误,你也必须更改名称。

解释:

举个例子。

interface OriginalInterface {
id: string
}

修改后的型号如下图所示

interface ModifiedInterface {
id: number
}

现在,假设将来,OriginalInterfaceid被重命名为uId,然后使用我的类型实用程序,你将得到如下错误

interface ModifiedInterface {
id: number // Type '{ geo_point1: GeoPoint | null; }' has no properties in common with type 'Partial<Address>'.ts(2559)
}