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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43909566/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 02:14:19  来源:igfitidea点击:

Get keys of a Typescript interface as array of strings

javascripttypescript

提问by Tushar Shukla

I've a lot of tables in Lovefieldand their respective Interfaces for what columns they have.
Example:

我在Lovefield 中有很多表格以及它们各自的接口,用于显示它们具有的列。
例子:

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

I'd like to have the property names of this interface in an array like this:

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

I cannot make an object/array based on the interface IMyTabledirectly which should do the trick because i'd be getting the interface names of the tables dynamically. Hence I need to iterate over these properties in the interface and get an array out of it.

How do i achieve this result?

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

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

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

我如何达到这个结果?

回答by kimamula

As of TypeScript 2.3(or should I say 2.4, as in 2.3this feature contains a bugwhich has been fixed in [email protected]), you can create a custom transformer to achieve what you want to do.

TypeScript 2.3 开始(或者我应该说2.4,因为在2.3 中,此功能包含已在[email protected]修复的错误),您可以创建自定义转换器来实现您想要做的事情。

Actually, I have already created such a custom transformer, which enables the following.

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

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

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

Unfortunately, custom transformers are currently not so easy to use. You have to use them with the TypeScripttransformation API instead of executing tsc command. There is an issuerequesting a plugin support for custom transformers.

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

回答by Maciek Wawro

The following requires you to list the keys on your own, but at least TypeScript will enforce IUserProfileand IUserProfileKeyshave the exact same keys (Required<T>was added in TypeScript 2.8):

以下要求您自己列出键,但至少 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,
};

回答by Damathryx

This should work

这应该工作

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

or

或者

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

回答by Derek

Instead of defining IMyTableas in interface, try defining it as a class. In typescript you can use a class like an interface.

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

So for your example, define/generate your class like this:

因此,对于您的示例,请像这样定义/生成您的类:

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

Use it as an interface:

将其用作接口:

export class SomeTable implements IMyTable {
    ...
}

Get keys:

获取钥匙:

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

回答by basarat

Can't. Interfaces don't exist at runtime.

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

workaround

解决方法

Create a variable of the type and use Object.keyson it

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

回答by Dan Def

You will need to make a class that implements your interface, instantiate it and then use Object.keys(yourObject)to get the properties.

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

export class YourClass implements IMyTable {
    ...
}

then

然后

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

回答by Aidin

I had a similar problem that I had a giant list of properties that I wanted to have both an interface, and an object out of it.

我有一个类似的问题,我有一个巨大的属性列表,我想要一个接口和一个对象。

NOTE: I didn't want to write (type with keyboard) the properties twice!Just DRY.

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



One thing to note here is, interfaces are enforced types at compile-time, while objects are mostly run-time. (Source)

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

As @derek mentioned in another answer, the common denominator of interfaceand objectcan be a class that serves both a typeand a value.

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

So, TL;DR, the following piece of code should satisfy the needs:

所以,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,
};

(Hereis the above code in Typescript Playground to play more)

这里是上面Typescript Playground中的代码,可以多玩一些)

PS. If you don't want to assign initial values to the properties in the class, and stay with the type, you can do the constructor trick:

附注。如果您不想为类中的属性分配初始值,而不想保留类型,则可以使用构造函数技巧:

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

Constructor Trick in TypeScript Playground.

TypeScript Playground 中的构造函数技巧

回答by ford04

There is no easy way to create a keys array from an interface. Types are erased at run-time and object types (unordered, named) cannot be converted to tuple types (ordered, unnamed) without some sort of hack.

没有简单的方法可以从接口创建键数组。类型在运行时被擦除,并且对象类型(无序、命名)无法转换为元组类型(有序、未命名)而无需某种 hack。



Option 1: Manual approach

选项 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")[]

(+) simple   (-) array return type, no tuple   (+-) manual writing with auto-completion

(+) 简单 (-) 数组返回类型,无需元组 (+-) 手动写入自动完成

Extension:We could try to be fancy and use recursive types to generate a tuple. This only worked for me for a couple of props (~5,6) until performance degraded massively. Deeply nested recursive type also aren't officially supported by TS - I list this examplehere for the sake of completeness.

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



Option 2: Code generator based on TS compiler API (ts-morph)

选项 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

After we compile and run this file with tsc && node dist/mybuildstep.js, a file ./src/generated/IMyTable-keys.tswith following content is generated:

我们用 编译并运行这个文件后tsc && node dist/mybuildstep.js./src/generated/IMyTable-keys.ts会生成一个包含以下内容的文件:

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

(+) automatic solution   (+) exact tuple type   (-) requires build-step

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



PS: I have chosen ts-morph, as it is a simple alternative to the original TS compiler API.

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

回答by Eduardo Cuomo

You can't do it. Interfaces don't exist at runtime (like @basaratsaid).

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

Now, I am working with following:

现在,我正在处理以下问题:

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

回答by Brillian Andrie Nugroho Wiguno

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