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

例子:

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

我想有这个接口的属性名在这样的数组中:

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

我不能直接基于接口IMyTable创建一个对象/数组,因为我将动态地获得表的接口名称。因此,我需要在接口中迭代这些属性,并从中获得一个数组。

我如何实现这个结果?


当前回答

正如其他人已经说过的,这些类型在运行时不可用,因此您需要生成它们或手工编写它们。

如果您手动编写它们,那么简单的Array<keyof IMyTable>不会验证缺少的键。

这里有一个非常棒的答案,它回答了使用类型安全声明键数组的问题。(功劳归于他。)相关代码:

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

type Invalid<T> = ['Needs to be all of', T];
const arrayOfAll =
    <T>() =>
    <U extends T[]>(
        ...array: U & ([T] extends [U[number]] ? unknown : Invalid<T>[])
    ) =>
        array;

const fields = arrayOfAll<keyof IMyTable>()(
    'id',
    'createdAt',
    'title',
    'isDeleted',
);

如果缺少字段,则会显示错误。

其他回答

问题是类型在运行时不可用。我最终定义了一个对象,并从该对象派生了类型。您仍然可以获得类型支持,并获得从单个位置获得所有键的列表的能力。

const myTable =  {
  id: 0,
  title: '',
  createdAt: null as Date,
  isDeleted: false,
};

export type TableType = typeof myTable;
export type TableTypeKeys = (keyof TableType)[];
export const tableKeys: TableTypeKeys = Object.keys(
  myTable
) as TableTypeKeys;

由上面产生的隐式类型将是(你可以用VSCode或其他高质量IDE检查):

type TableType = {
    id: number;
    title: string;
    createdAt: Date;
    isDeleted: boolean;
}

type TableTypeKeys = ("id" | "title" | "createdAt" | "isDeleted")[]

在运行时,你将能够访问tableKeys数组

console.log(tableKeys);
// output: ["id" , "title" , "createdAt" , "isDeleted"]

下面需要你自己列出键,但至少TypeScript会强制IUserProfile和IUserProfileKeys拥有完全相同的键(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,
};

不要将IMyTable定义为在接口中,而是尝试将它定义为一个类。在typescript中,你可以像接口一样使用类。

对于你的例子,像这样定义/生成你的类:

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

如果您不能使用自定义转换器(或不愿意使用),我认为最好的方法就是我将要展示的方法,它具有以下优点:

它允许“半自动”填充数组(至少在VS Code中); 它会生成一个数组,TypeScript会将其识别为包含接口键的并集元素; 它不涉及降低性能的递归技巧。

方法如下:

interface Foo {
  fizz?: string;
  buzz: number;
}

const FooKeysEnum: { [K in keyof Required<Foo>]: K } = {
  fizz: 'fizz',
  buzz: 'buzz',
};

const FooKeys = Object.values(FooKeysEnum);

VS Code中“半自动”填充数组的原因是,当FooKeysEnum因为缺少属性而出现红色下划线时,你可以将鼠标悬停在它上面,从“快速修复”菜单中选择“添加缺少属性”。(这个好处已经在这篇文章中展示过了,但我认为还没有人提到过。埃塔:我弄错了;自动补全已经在线程的其他地方提到了。)

最后,通过使用Object.values()而不是Object.keys()来创建数组,你可以让TypeScript识别出FooKeys的类型是("fizz" | "buzz")[]。它不知道FooKeys[0]是“fizz”,FooKeys[1]是“buzz”,但仍然比你用Object.keys()得到的字符串[]要好。

编辑:

在VS Code中,你也可以在键盘绑定中设置快捷键。json用于执行“Quick Fix”,这使得它可以更快地触发添加缺失的属性。看起来是这样的:

{
  "key": "shift+cmd+a",
  "command": "editor.action.codeAction",
  "args": {
    "kind": "quickfix",
    "apply": "ifSingle"
  }
}

然后,如果某个东西有红色下划线,你可以单击它并使用键盘快捷键,如果只有一个可用的快速修复选项,那么它将运行。如果有一种方法可以针对特定的快速修复,那就太好了,如果它可以在文件保存时自动完成,那就更好了,但我认为在撰写本文时这是不可能的。

从TypeScript 2.3(或者我应该说2.4,因为在2.3这个特性包含一个bug,该bug已在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命令。有一个问题,要求插件支持自定义变压器。