TypeScript:使用映射类型删除索引签名

Lau*_*ers 8 typescript mapped-types

给定一个接口(来自现有的.d.ts文件,无法更改):

interface Foo {
  [key: string]: any;
  bar(): void;
}
Run Code Online (Sandbox Code Playgroud)

有没有办法使用映射类型(或其他方法)派生没有索引签名的新类型?即它只有方法"bar():void;"

Ole*_*ter 14

使用 TypeScript v4.1键重映射会产生一个非常简洁的解决方案。

在其核心,它使用了Mihail 回答中稍微修改的逻辑:虽然已知键是stringor的子类型number,但后者不是相应文字的子类型。另一方面,string是所有可能字符串的并集(同样适用于number),因此是自反的(type res = string extends string ? true : false; //truehold)。

这意味着您可以解析为never每次类型stringnumber可分配给键的类型,有效地将其过滤掉:

interface Foo {
  [key: string]: any;
  [key: number]: any;
  bar(): void;
}

type RemoveIndex<T> = {
  [ P in keyof T as string extends P ? never : number extends P ? never : P ] : T[P]
};

type FooWithOnlyBar = RemoveIndex<Foo>; //{ bar: () => void; }
Run Code Online (Sandbox Code Playgroud)

操场

  • @MihailMaostanidis - 你是说类型?是的,当然 - 希望有一天我们能有更好的方法来解决这个问题 (4认同)

Mih*_*dis 10

有一种方法,需要TypeScript 2.8的条件类型.

它基于事实'a' extends stringstring不是extends 'a'

interface Foo {
  [key: string]: any;
  bar(): void;
}

type KnownKeys<T> = {
  [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;


type FooWithOnlyBar = Pick<Foo, KnownKeys<Foo>>;
Run Code Online (Sandbox Code Playgroud)

您可以通过以下方式制作通用:

// Generic !!!
type RequiredOnly<T extends Record<any,any>> = Pick<T, KnownKeys<T>>;

type FooWithOnlyBar = RequiredOnly<Foo>;
Run Code Online (Sandbox Code Playgroud)

有关确切原因的解释KnownKeys<T>,请参阅以下问题:

如何在不移除TypeScript中的子类型的情况下从联合类型中删除更宽的类型?

  • 辉煌!谢谢!事实证明,您永远不应怀疑TypeScript的强大功能。:D这似乎是该技术的起源的GitHub注释:https://github.com/Microsoft/TypeScript/issues/25987#issuecomment-408339599 (2认同)

Ger*_*it0 5

TypeScript 4.4 中,该语言获得了对更复杂索引签名的支持。

interface FancyIndices {
  [x: symbol]: number;
  [x: `data-${string}`]: string
}
Run Code Online (Sandbox Code Playgroud)

symbol可以通过在先前发布的类型中为其添加一个案例来轻松捕获该键,但是这种检查方式无法检测到无限的模板文字。1

但是,我们可以通过修改检查以查看使用每个键构造的对象是否可分配给空对象来实现相同的目标。这是有效的,因为“真实”键将要求构造的对象Record<K, 1>具有属性,因此不可分配,而作为索引签名的键将导致可能仅包含空对象的类型。

type RemoveIndex<T> = {
  [K in keyof T as {} extends Record<K, 1> ? never : K]: T[K]
}
Run Code Online (Sandbox Code Playgroud)

操场上试一试

测试:

class X {
  [x: string]: any
  [x: number]: any
  [x: symbol]: any
  [x: `head-${string}`]: string
  [x: `${string}-tail`]: string
  [x: `head-${string}-tail`]: string
  [x: `${bigint}`]: string
  [x: `embedded-${number}`]: string

  normal = 123
  optional?: string
}

type RemoveIndex<T> = {
  [K in keyof T as {} extends Record<K, 1> ? never : K]: T[K]
}

type Result = RemoveIndex<X>
//   ^? - { normal: number, optional?: string  }
Run Code Online (Sandbox Code Playgroud)

1您可以通过使用一次处理一个字符的递归类型来检测一些无限模板文字,但这不适用于长键。