繁体   English   中英

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

[英]Overriding interface property type defined in Typescript d.ts file

有没有办法改变typescript中*.d.ts中定义的接口属性类型?

例如: xdts中的接口定义为

interface A {
  property: number;
}

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

interface A {
  property: Object;
}

甚至这也行得通

interface B extends A {
  property: Object;
}

这种方法行得通吗? 当我在我的系统上尝试时它不起作用。 只是想确认它是否可能?

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

参考从类型中排除属性

interface A {
    x: string
}

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

对于接口:

interface A {
    x: string
}

interface B extends Omit<A, 'x'> {
  x: number
}
 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 实用程序类型


v2.0 深度修改

interface Original {
  a: {
    b: string
    d: {
      e: string // <- will be changed
    }
  }
  f: number
}

interface Overrides {
  a: {
    d: {
      e: number
      f: number // <- new key
    }
  }
  b: {         // <- new key
    c: number
  }
}

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

在下面找到ModifyDeep

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

您可以添加一个属性:

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'.

像我这样懒惰的人的简短回答:

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

稍微扩展@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;

扩展接口时Omit属性:

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

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

我创建了这种类型,允许我轻松覆盖嵌套接口:

type ModifyDeep<A extends AnyObject, B extends DeepPartialAny<A>> = {
  [K in keyof A]: B[K] extends never
    ? A[K]
    : B[K] extends AnyObject
      ? ModifyDeep<A[K], B[K]>
      : B[K]
} & (A extends AnyObject ? Omit<B, keyof A> : A)

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

type AnyObject = Record<string, any>

然后你可以像这样使用它:

interface Original {
  a: {
    b: string
    d: {
      e: string // <- will be changed
    }
  }
  f: number
}

interface Overrides {
  a: {
    d: {
      e: number
      f: number // <- new key
    }
  }
  b: {         // <- new key
    c: number
  }
}

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

为了缩小属性的类型,简单的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 在我们的例子中是string | number 所以, B会变成{x: number; } {x: number; }并接受任何类型为number | string | boolean的额外属性number | string | boolean 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!

有趣的是,我花了一天时间调查解决同一案件的可能性。 我发现不可能这样做:

// 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 个,以避免出现愚蠢的情况

日期: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" }

作为别名类型覆盖

您可以使用此类型别名:

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

并使用类似下面的语法:

全局接口

interface IFirst {
    username: string;
}

通过override刚刚的名称接口

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

类型别名override

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

编辑:

我试图通过将问题作为提案发送到Typescript Repo来将此别名类型添加为 TypeScript 中的内置类型

如果其他人需要通用实用程序类型来执行此操作,我想出了以下解决方案:

/**
 * 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 ,请参阅Exclude property from type

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

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

覆盖接口的两个多个属性的解决方案:

 interface Original { a: string; b: string; c: string; } interface Modified extends Omit<Original, 'a' | 'b'> { a?: string; // make it optional b: boolean; // make it boolean d: number; // add another proeprty }

来自TypeScript 文档

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


我遇到了一些与此主题相关的问题(覆盖接口属性),这就是我处理它的方式:

  1. 首先创建一个通用接口,其中包含您想要使用的可能类型。

您甚至可以使用为通用参数选择default值,如<T extends number | SOME_OBJECT = number> <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"
    }
}

打字稿游乐场

扩展Qwerty 的Modify 实用程序类型解决方案以将R的键限制为T中存在的键并添加 IntelliSense

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

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

深度修改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} ,反之亦然,但是DeepPartialAny在用平面图元覆盖object时会报错比如这个

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

但是,您可以安全地忽略错误(使用/// @ts-ignore或完全删除extends DeepPartialAny约束。结果类型无论如何都会正确计算。

例子

打字游乐场

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

尝试这个:

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

用法:

type TransformedArticle = Override<Article, { id: string }>;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM