Tus*_*kla 64 javascript typescript
我在Lovefield中有很多表,他们各自的接口都有他们所拥有的列.
例:
export interface IMyTable {
id: number;
title: string;
createdAt: Date;
isDeleted: boolean;
}
Run Code Online (Sandbox Code Playgroud)
我想在这样的数组中拥有这个接口的属性名称:
var IMyTable = ["id", "title", "createdAt", "isDeleted"];
我不能直接创建基于接口的对象/数组IMyTable,因为我将动态地获取表的接口名称.因此,我需要在界面中迭代这些属性并从中获取数组.
我如何实现这一结果.
Nat*_*iel 53
也许为时已晚,但在 TypeScript 2.1 版中,您可以使用以下keyof方式获取类型:
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
Run Code Online (Sandbox Code Playgroud)
来源:https : //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types
kim*_*ula 35
从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']
Run Code Online (Sandbox Code Playgroud)
不幸的是,定制变压器目前不是那么容易使用.您必须将它们与TypeScript转换API一起使用,而不是执行tsc命令.请求自定义变换器的插件支持存在问题.
Aid*_*din 32
我遇到了一个类似的问题:我有一个巨大的属性列表,我想同时将它们作为接口(编译时)和对象(运行时)。
注意:我不想写(用键盘输入)属性两次!干燥。
这里要注意的一件事是,接口在编译时是强制类型,而对象主要是在运行时。(来源)
正如@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,
};
Run Code Online (Sandbox Code Playgroud)
(这里是上面Typescript Playground中的代码,可以多玩一些)
附注。如果您不想为类中的属性分配初始值,而不想保留类型,则可以使用构造函数技巧:
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"]
Run Code Online (Sandbox Code Playgroud)
TypeScript Playground 中的构造函数技巧。
小智 30
以下要求您自己列出键,但至少 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,
};
Run Code Online (Sandbox Code Playgroud)
for*_*d04 20
从具有安全编译时检查的接口创建键的数组或元组需要一点创造力。类型在运行时被擦除,并且对象类型(无序、命名)不能转换为元组类型(有序、无名),除非采用不受支持的技术。
在给定引用对象类型(如IMyTable. 例如,声明一个数组类型(keyof IMyTable)[]无法捕获这些错误。
此外,它们不需要特定的库(最后一个变体使用ts-morph,我认为它是一个通用的编译器包装器),发出一个元组类型而不是一个对象(只有第一个解决方案创建一个数组)或宽数组类型(与这些 答案),最后不需要课程。
// 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")[]
Run Code Online (Sandbox Code Playgroud)
++-带有自动完成-数组的最简单的手册,没有元组
如果您不喜欢创建记录,请查看此替代方法 withSet和 assertion types。
function createKeys<T extends readonly (keyof IMyTable)[] | [keyof IMyTable]>(
t: T & CheckMissing<T, IMyTable> & CheckDuplicate<T>): T {
return t
}
Run Code Online (Sandbox Code Playgroud)
++-具有自动完成功能的元组手册+-更高级、更复杂的类型
createKeys通过将函数参数类型与其他断言类型合并来进行编译时检查,这些断言类型会针对不合适的输入发出错误。(keyof IMyTable)[] | [keyof IMyTable]是一种从被调用方强制推断元组而不是数组的“黑魔法”方式。或者,您可以从调用方使用const 断言/as const。
CheckMissing检查,是否T遗漏来自U:
type CheckMissing<T extends readonly any[], U extends Record<string, any>> = {
[K in keyof U]: K extends T[number] ? never : K
}[keyof U] extends never ? T : T & "Error: missing keys"
type T1 = CheckMissing<["p1"], {p1:any, p2:any}> //["p1"] & "Error: missing keys"
type T2 = CheckMissing<["p1", "p2"], { p1: any, p2: any }> // ["p1", "p2"]
Run Code Online (Sandbox Code Playgroud)
注意:T & "Error: missing keys"仅适用于不错的 IDE 错误。你也可以写never. CheckDuplicates检查双元组项目:
type CheckDuplicate<T extends readonly any[]> = {
[P1 in keyof T]: "_flag_" extends
{ [P2 in keyof T]: P2 extends P1 ? never :
T[P2] extends T[P1] ? "_flag_" : never }[keyof T] ?
[T[P1], "Error: duplicate"] : T[P1]
}
type T3 = CheckDuplicate<[1, 2, 3]> // [1, 2, 3]
type T4 = CheckDuplicate<[1, 2, 1]>
// [[1, "Error: duplicate"], 2, [1, "Error: duplicate"]]
Run Code Online (Sandbox Code Playgroud)
注意:关于元组中唯一项检查的更多信息在这篇文章中。使用TS 4.1,我们还可以在错误字符串中命名缺失的键 - 看看这个 Playground。
在 4.1 版中,TypeScript 正式支持条件递归类型,这也可能在这里使用。但是,由于组合复杂性,类型计算很昂贵 - 超过 5-6 个项目的性能会大幅下降。为了完整性,我列出了这个替代方案(Playground):
type Prepend<T, U extends any[]> = [T, ...U] // TS 4.0 variadic tuples
type Keys<T extends Record<string, any>> = Keys_<T, []>
type Keys_<T extends Record<string, any>, U extends PropertyKey[]> =
{
[P in keyof T]: {} extends Omit<T, P> ? [P] : Prepend<P, Keys_<Omit<T, P>, U>>
}[keyof T]
const t1: Keys<IMyTable> = ["createdAt", "isDeleted", "id", "title"] // ?
Run Code Online (Sandbox Code Playgroud)
++-具有自动完成+功能的元组手册无辅助功能--性能
此处选择了ts-morph,因为它是原始 TS 编译器 API 的一个更简单的包装器替代品。当然也可以直接使用编译器API。让我们看一下生成器代码:
// ./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
Run Code Online (Sandbox Code Playgroud)
我们用 编译并运行这个文件后tsc && node dist/mybuildstep.js,./src/generated/IMyTable-keys.ts会生成一个包含以下内容的文件:
// ./src/generated/IMyTable-keys.ts
const keys = ["id","title","createdAt","isDeleted"] as const;
Run Code Online (Sandbox Code Playgroud)
+自动生成解决方案+可扩展为多个属性+没有辅助函数+元组-额外的构建步骤-需要熟悉编译器 API
Dam*_*ryx 19
这应该工作
var IMyTable: Array<keyof IMyTable> = ["id", "title", "createdAt", "isDeleted"];
Run Code Online (Sandbox Code Playgroud)
或者
var IMyTable: (keyof IMyTable)[] = ["id", "title", "createdAt", "isDeleted"];
Run Code Online (Sandbox Code Playgroud)
Der*_*rek 13
与其IMyTable在接口中定义as ,不如尝试将其定义为类。在打字稿中,您可以使用像接口这样的类。
因此,对于您的示例,请像这样定义/生成您的类:
export class IMyTable {
constructor(
public id = '',
public title = '',
public createdAt: Date = null,
public isDeleted = false
)
}
Run Code Online (Sandbox Code Playgroud)
将其用作接口:
export class SomeTable implements IMyTable {
...
}
Run Code Online (Sandbox Code Playgroud)
获取钥匙:
const keys = Object.keys(new IMyTable());
Run Code Online (Sandbox Code Playgroud)
不能.接口在运行时不存在.
创建该类型的变量并在其上使用Object.keys
您将需要创建一个实现您的接口的类,实例化它然后用于Object.keys(yourObject)获取属性.
export class YourClass implements IMyTable {
...
}
Run Code Online (Sandbox Code Playgroud)
然后
let yourObject:YourClass = new YourClass();
Object.keys(yourObject).forEach((...) => { ... });
Run Code Online (Sandbox Code Playgroud)
我的需要是获取接口的键作为字符串数组,以简化 mocha/chai 脚本编写。不关心在应用程序中使用(还),因此不需要创建 ts 文件。感谢ford04的帮助,他的上述解决方案提供了巨大的帮助,并且运行完美,没有编译器黑客攻击。这是修改后的代码:
npm install --save-dev ts-morph
Run Code Online (Sandbox Code Playgroud)
注意:这假设所有 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;
Run Code Online (Sandbox Code Playgroud)
import keys from "./keys";
const myKeys = keys("MyInterface") //ts file name without extension
console.log(myKeys)
Run Code Online (Sandbox Code Playgroud)
有些人建议这样做,其优点是最简单的解决方案:
const properties: (keyof IMyTable)[] = ["id", "title", "createdAt", "isDeleted"];
Run Code Online (Sandbox Code Playgroud)
然而,虽然这增加了一些类型安全性(我们不能错误地使用不存在的属性),但这并不是一个完全安全的解决方案,因为我们可能会丢失一些属性并有重复的属性。所以我已经解决了这个问题,这个详细的解决方案是完全类型安全的,可以防止编译时类型和数组的运行时值之间的不一致:
const properties: [
keyof Pick<IMyTable, 'id'>,
keyof Pick<IMyTable, 'title'>,
keyof Pick<IMyTable, 'createdAt'>,
keyof Pick<IMyTable, 'isDeleted'>
] = ['id', 'title', 'createdAt', 'isDeleted'];
Run Code Online (Sandbox Code Playgroud)
当然,这仅适用于您不避免重复的情况,但至少您只需确保一次正确编写所有属性(在 Pick 类型 util 上),其余的将始终引发错误(如果有)任何错误。我认为它是简单、易于理解和可读的解决方案中最强大的解决方案。