我在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',
);
如果缺少字段,则会显示错误。
正如其他人已经说过的,这些类型在运行时不可用,因此您需要生成它们或手工编写它们。
如果您手动编写它们,那么简单的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"]
这是一个艰难的问题!谢谢大家的帮助。
我的需要是将接口的键作为字符串数组来简化mocha/chai脚本。不关心在应用程序中使用(还),所以不需要创建ts文件。感谢ford04的帮助,他上面的解决方案是一个巨大的帮助,它的工作完美,没有编译器黑客。下面是修改后的代码:
选项2:基于TS编译器API的代码生成器(TS -morph)
节点模块
npm install --save-dev ts-morph
keys.ts
注意:这里假设所有ts文件都位于./src的根目录下,并且没有子文件夹,请相应地调整
import {
Project,
VariableDeclarationKind,
InterfaceDeclaration,
} from "ts-morph";
// initName is name of the interface file below the root, ./src is considered the root
const Keys = (intName: string): string[] => {
const project = new Project();
const sourceFile = project.addSourceFileAtPath(`./src/${intName}.ts`);
const node = sourceFile.getInterface(intName)!;
const allKeys = node.getProperties().map((p) => p.getName());
return allKeys;
};
export default Keys;
使用
import keys from "./keys";
const myKeys = keys("MyInterface") //ts file name without extension
console.log(myKeys)