获取Typescript接口的键作为字符串数组


101

我在Lovefield中有很多表,以及它们各自具有哪些列的接口。
例:

export interface IMyTable {
  id: number;
  title: string;
  createdAt: Date;
  isDeleted: boolean;
}

我想在一个这样的数组中包含此接口的属性名称:

const IMyTable = ["id", "title", "createdAt", "isDeleted"];

我不能IMyTable直接基于该接口来创建对象/数组,而这应该可以解决问题,因为我会动态获取表的接口名称。因此,我需要遍历接口中的这​​些属性并从中获取一个数组。

我如何达到这个结果?

Answers:


52

TypeScript 2.3开始(或者我应该说2.4,如2.3所示,此功能包含一个已在typescript@2.4-dev中修复的错误),您可以创建一个自定义转换器来实现您想要的操作。

实际上,我已经创建了这样的自定义转换器,可以实现以下功能。

https://github.com/kimamula/ts-transformer-keys

import { keys } from 'ts-transformer-keys';

interface Props {
  id: string;
  name: string;
  age: number;
}
const keysOfProps = keys<Props>();

console.log(keysOfProps); // ['id', 'name', 'age']

不幸的是,定制变压器目前还不太容易使用。您必须将它们与TypeScript转换API 一起使用,而不是执行tsc命令。还有一个问题,请求定制变压器插件的支持。


感谢您的回复,我昨天已经看过并安装了此自定义转换器,但是由于它使用的是打字稿2.4,因此到目前为止对我来说这没有用。
Tushar Shukla

16
嗨,这个库也完全符合我的要求,但是,ts_transformer_keys_1.keys is not a function当我按照文档中的确切步骤进行操作时,我就会明白。有解决方法吗?
Hasitha Shan

整齐!您认为它可以扩展为采用动态类型参数(自述文件中的注释2)吗?
kenshin

@HasithaShan仔细研究文档-您必须使用TypeScript编译器API才能使程序包正常工作
Yaroslav Bai

2
不幸的是,包装袋坏了,无论我做什么,我总是得到ts_transformer_keys_1.keys is not a function
fr1sk

16

以下要求您自己列出密钥,但是至少TypeScript将强制实施IUserProfileIUserProfileKeys具有完全相同的密钥(Required<T>已在TypeScript 2.8中添加):

export interface IUserProfile  {
  id: string;
  name: string;
};
type KeysEnum<T> = { [P in keyof Required<T>]: true };
const IUserProfileKeys: KeysEnum<IUserProfile> = {
  id: true,
  name: true,
};

很酷的把戏。现在,很容易实施所有的键,IUserProfile也很容易从const中提取它们IUserProfileKeys。这正是我一直在寻找的东西。现在无需将所有接口转换为类。
Anddo

12

我有一个类似的问题,我有一个庞大的属性列表,我想同时拥有一个接口和一个对象。

注意:我不想两次写(用键盘键入)属性! 干吧。


这里要注意的一件事是,接口在编译时是强制类型,而对象通常在运行时。(来源

正如@derek在另一个答案中提到的那样,接口对象的公分母可以是同时提供类型值的类

因此,TL; DR,以下代码应满足需求:

class MyTableClass {
    // list the propeties here, ONLY WRITTEN ONCE
    id = "";
    title = "";
    isDeleted = false;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// This is the pure interface version, to be used/exported
interface IMyTable extends MyTableClass { };

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Props type as an array, to be exported
type MyTablePropsArray = Array<keyof IMyTable>;

// Props array itself!
const propsArray: MyTablePropsArray =
    Object.keys(new MyTableClass()) as MyTablePropsArray;

console.log(propsArray); // prints out  ["id", "title", "isDeleted"]


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Example of creating a pure instance as an object
const tableInstance: MyTableClass = { // works properly!
    id: "3",
    title: "hi",
    isDeleted: false,
};

是上面在Typescript Playground中的代码,可以播放更多内容)

PS。如果您不想为类中的属性分配初始值,而是希望与类型保持一致,则可以执行构造函数:

class MyTableClass {
    // list the propeties here, ONLY WRITTEN ONCE
    constructor(
        readonly id?: string,
        readonly title?: string,
        readonly isDeleted?: boolean,
    ) {}
}

console.log(Object.keys(new MyTableClass()));  // prints out  ["id", "title", "isDeleted"] 

TypeScript游乐场中的构造技巧


不过,propsArray只有在初始化密钥时才能访问。
denkquer

我不明白“初始化” @denkquer的意思。在第一个示例中,如果您要propsArray使用的是tableInstanceif ,则可以使用它,因此在实例初始化之前可以很清楚地使用它。但是,如果您参考中分配的伪造的值MyTableClass,那么这些伪造的值仅表示这些属性的“类型”。如果您不想要它们,可以使用PS示例中的构造器技巧。
艾丁

1
以我的理解,值在具有任何值时都会被初始化。您的“构造器技巧”具有误导性,因为您不能仅将MyTableClass后者替换为后者,并期望propsArray在运行时剥离未初始化的var和类型时接收密钥。您始终必须为它们提供某种默认值。我发现用它们初始化undefined是最好的方法。
denkquer

@denkquer对不起,我的构造器技巧不见了,readonly而且很奇怪?。我刚刚更新了。感谢您指出。现在,我认为它以您所说的方式引起误解并且无法工作。:)
艾丁

@Aidin谢谢您的解决方案。我也想知道是否可以避免参数的初始化。如果我使用构造函数的技巧,我将无法再创建扩展MyTableClass的接口..尽管您在打字稿游乐场链接中的构造函数的技巧为空
Flion

9

这应该工作

var IMyTable: Array<keyof IMyTable> = ["id", "title", "createdAt", "isDeleted"];

要么

var IMyTable: (keyof IMyTable)[] = ["id", "title", "createdAt", "isDeleted"];

10
并不是说它是错误的,而是要在此处明确说明您只是在“强制执行数组的值”才能正确。开发人员仍然需要手动将它们写下两次。

虽然爱丁所说的可能是正确的,但在某些情况下,这正是我在寻找的案例。谢谢。
Daniel

4
这不会防止密钥重复或密钥丢失。像var IMyTable: Array<keyof IMyTable> = ["id", "createdAt", "id"];
ford04

对我来说,这也是我一直在寻找的内容,因为我想选择性地接受键,但无非就是界面中定义的键。没想到上面的代码会将其作为默认设置。我想我们仍然需要一种通用的TS方式。无论如何都感谢上面的代码!
nicoes

6

与其定义IMyTable为in接口,不如尝试将其定义为类。在打字稿中,您可以使用接口之类的类。

因此,对于您的示例,按如下方式定义/生成您的类:

export class IMyTable {
    constructor(
        public id = '',
        public title = '',
        public createdAt: Date = null,
        public isDeleted = false
    )
}

用作接口:

export class SomeTable implements IMyTable {
    ...
}

获取密钥:

const keys = Object.keys(new IMyTable());

6

也许为时已晚,但是在2.1版打字稿中,您可以这样使用key of

interface Person {
    name: string;
    age: number;
    location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string

Doc:https : //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types


感谢您的回答,但不确定是否可以帮助某人从界面使用静态创建的Types。恕我直言,我们可以在大多数情况下互换使用接口/类型。另外,这将需要手动创建多个接口的类型。但是,如果有人只需要从接口中获取类型,则解决方案看起来不错。
Tushar Shukla

5

您将需要创建一个实现您的接口的类,将其实例化,然后用于Object.keys(yourObject)获取属性。

export class YourClass implements IMyTable {
    ...
}

然后

let yourObject:YourClass = new YourClass();
Object.keys(yourObject).forEach((...) => { ... });

在我的情况下不起作用,我必须列出接口的那些属性,但这不是我想要的?接口的名称动态变化,然后我必须确定其属性
Tushar Shukla

这将产生一个错误(v2.8.3):Cannot extend an interface […]. Did you mean 'implements'?但是,使用implements替代方法需要手动复制该接口,而这正是我所不希望的。
雅各布

@jacob抱歉,应该已经implements,我已经更新了答案。正如@basarat所说,接口在运行时不存在,因此唯一的方法是将其实现为类。
丹德夫

您的意思是使用类而不是接口?遗憾的是,我不能这样做,因为该界面来自第三方(@types/react)。我手动复制了它们,但这很难适应将来的需求😪我试图动态绑定非生命周期方法(已绑定),但未在React.Component(类)上声明它们。
雅各布

不,我的意思是创建一个实现您的第三方接口的类,并在运行时获取该类的属性。
丹德夫

4

不行 接口在运行时不存在。

解决方法

创建类型的变量并Object.keys在其上使用use


1
您的意思是这样的:var abc: IMyTable = {}; Object.keys(abc).forEach((key) => {console.log(key)});
Tushar Shukla

4
不,因为该对象上没有键。接口是TypeScript使用但在JavaScript中消失的东西,因此没有信息可用于通知任何“反射”或“交叉检查”。所有JavaScript都知道有一个空的对象常量。您唯一的希望是等待(或请求)TypeScript包括一种使用接口中所有键生成源代码的数组或对象的方法。或者,如Dan Def所说,如果您可以使用一个类,则将在每个实例中以属性的形式定义键
。– Jesper

1
您还会在此行上通过TypeScript遇到错误:var abc: IMyTable = {}因为空对象文字不符合该接口的形状。
杰斯珀(Jesper)

16
如果这不起作用,为什么对这个答案有反对意见?
dawez


3

没有简单的方法可以从接口创建键数组。类型在运行时被擦除,并且对象类型(无序,命名)在没有某种黑客手段的情况下无法转换为元组类型(有序,无命名)。


选项1:手动方式

// Record type ensures, we have no double or missing keys, values can be neglected
function createKeys(keyRecord: Record<keyof IMyTable, any>): (keyof IMyTable)[] {
  return Object.keys(keyRecord) as any
}

const keys = createKeys({ isDeleted: 1, createdAt: 1, title: 1, id: 1 })
// const keys: ("id" | "title" | "createdAt" | "isDeleted")[]

(+)简单(-)数组返回类型,无元组(+-)带有自动完成功能的手动写入

扩展:我们可以尝试花哨并使用递归类型来生成元组。这对我来说仅适用于几个道具(〜5,6),直到性能大幅下降。TS也未正式支持深度嵌套的递归类型- 为了完整起见,在此列出此示例


选项2:基于TS编译器API(ts-morph)的代码生成器

// ./src/mybuildstep.ts
import {Project, VariableDeclarationKind, InterfaceDeclaration } from "ts-morph";

const project = new Project();
// source file with IMyTable interface
const sourceFile = project.addSourceFileAtPath("./src/IMyTable.ts"); 
// target file to write the keys string array to
const destFile = project.createSourceFile("./src/generated/IMyTable-keys.ts", "", {
  overwrite: true // overwrite if exists
}); 

function createKeys(node: InterfaceDeclaration) {
  const allKeys = node.getProperties().map(p => p.getName());
  destFile.addVariableStatement({
    declarationKind: VariableDeclarationKind.Const,
    declarations: [{
        name: "keys",
        initializer: writer =>
          writer.write(`${JSON.stringify(allKeys)} as const`)
    }]
  });
}

createKeys(sourceFile.getInterface("IMyTable")!);
destFile.saveSync(); // flush all changes and write to disk

使用编译并运行此文件后tsc && node dist/mybuildstep.js,将./src/generated/IMyTable-keys.ts生成具有以下内容的文件:

// ./src/generated/IMyTable-keys.ts
const keys = ["id","title","createdAt","isDeleted"] as const;

(+)自动解决方案(+)确切的元组类型(-)需要构建步骤


PS:我选择了ts-morph,因为它是原始TS编译器API的简单替代方案。


-1

你做不到 接口在运行时不存在(如@basarat所说)。

现在,我正在处理以下内容:

const IMyTable_id = 'id';
const IMyTable_title = 'title';
const IMyTable_createdAt = 'createdAt';
const IMyTable_isDeleted = 'isDeleted';

export const IMyTable_keys = [
  IMyTable_id,
  IMyTable_title,
  IMyTable_createdAt,
  IMyTable_isDeleted,
];

export interface IMyTable {
  [IMyTable_id]: number;
  [IMyTable_title]: string;
  [IMyTable_createdAt]: Date;
  [IMyTable_isDeleted]: boolean;
}

想象一下,有很多模型,并且为每个人都做...这是非常昂贵的时间。
William Cuervo,

-6
// declarations.d.ts
export interface IMyTable {
      id: number;
      title: string;
      createdAt: Date;
      isDeleted: boolean
}
declare var Tes: IMyTable;
// call in annother page
console.log(Tes.id);

1
由于打字稿语法在运行时不可用,因此此代码不起作用。如果您在打字稿操场上检查此代码,则您会注意到,唯一编译为JavaScript的东西console.log(Tes.id)当然是错误“未捕获的ReferenceError:未定义Tes”
Tushar Shukla
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.