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

例子:

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

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

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

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

我如何实现这个结果?


当前回答

在这个博客中

从数组中获取一个类型

现在我们可以使用typeof从animals数组中获取一个类型:

const animals = ['cat', 'dog', 'mouse'] as const
type Animal = typeof animals[number]

// type Animal = 'cat' | 'dog' | 'mouse'

其他回答

有些人建议这样做,这是最简单的解决方案:

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

然而,尽管这增加了一些类型安全性(我们不能错误地使用不存在的属性),但这并不是一个完全安全的解决方案,因为我们可能会错过一些属性并拥有重复的属性。所以我已经修复了这个问题,这个详细的解决方案是完全类型安全的,并防止了数组的编译时类型和运行时值之间的不一致:

const properties: [
    keyof Pick<IMyTable, 'id'>,
    keyof Pick<IMyTable, 'title'>,
    keyof Pick<IMyTable, 'createdAt'>,
    keyof Pick<IMyTable, 'isDeleted'>
] = ['id', 'title', 'createdAt', 'isDeleted'];

当然,这只适用于如果你不避免重复,但至少你只需要确保你正确地写所有属性一次(在Pick类型util),如果有任何错误,其余的将总是引发一个错误。我认为这是最健壮的解决方案中简单,容易理解和易读的解决方案。

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

如果您手动编写它们,那么简单的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',
);

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

我也遇到过类似的问题:我有一个巨大的属性列表,我既想将其作为接口(编译时),也想将其作为对象(运行时)。

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


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

正如@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游乐场中的构造函数技巧。

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

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