[javascript] Typescript 인터페이스의 키를 문자열 배열로 가져옵니다.
나는 Lovefield에 많은 테이블 과 그들이 가진 열에 대한 각각의 인터페이스를 가지고 있습니다.
예:
export interface IMyTable {
id: number;
title: string;
createdAt: Date;
isDeleted: boolean;
}
이 인터페이스의 속성 이름을 다음과 같은 배열로
const IMyTable = ["id", "title", "createdAt", "isDeleted"];
만들고 싶습니다. 나는 IMyTable
테이블의 인터페이스 이름을 동적으로 가져 오기 때문에 트릭을 수행해야하는 인터페이스를 기반으로 개체 / 배열을 직접 만들 수 없습니다 . 따라서 인터페이스에서 이러한 속성을 반복하고 배열을 가져와야합니다.
이 결과를 얻으려면 어떻게해야합니까?
답변
현재 타이프 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']
불행히도, 커스텀 트랜스포머는 현재 사용하기 쉽지 않습니다. tsc 명령을 실행하는 대신 TypeScript 변환 API 와 함께 사용해야합니다 . 사용자 지정 변환기에 대한 플러그인 지원을 요청 하는 문제 가 있습니다 .
답변
다음은 사용자가 직접 키를 나열해야하지만 적어도 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,
};
답변
인터페이스와 객체를 모두 갖고 싶어하는 거대한 속성 목록이 있다는 비슷한 문제가 있습니다.
참고 : 속성을 두 번 작성 (키보드로 입력)하고 싶지 않았습니다! 그냥 말리십시오.
여기서 주목해야 할 점은 인터페이스는 컴파일 타임에 강제 유형이며 객체는 대부분 런타임에 적용된다는 것입니다. ( 출처 )
@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의 위 코드입니다.)
추신. 클래스의 속성에 초기 값을 할당하지 않고 유형을 유지하고 싶지 않은 경우 생성자 트릭을 수행 할 수 있습니다.
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"]
답변
이것은 작동합니다
var IMyTable: Array<keyof IMyTable> = ["id", "title", "createdAt", "isDeleted"];
또는
var IMyTable: (keyof IMyTable)[] = ["id", "title", "createdAt", "isDeleted"];
답변
너무 늦었을 수도 있지만 버전 2.1의 typescript에서는 다음 key of
과 같이 사용할 수 있습니다 .
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
답변
안전한 변종
안전 컴파일 시간 검사를 사용하여 인터페이스에서 키 배열 또는 튜플 을 만들려면 약간의 창의성이 필요합니다. 유형은 런타임에 지워지고 개체 유형 (정렬되지 않음, 명명 됨)은 지원되지 않는 기술에 의존하지 않고 튜플 유형 (정렬 됨, 명명되지 않음)으로 변환 될 수 없습니다 .
다른 답변과 비교
여기에서 제안 된 변형은 모두 IMyTable
. 와 같은 참조 객체 유형이 주어진 경우 중복되거나 누락 된 튜플 항목의 경우 컴파일 오류를 고려 / 트리거합니다 . 예를 들어 배열 유형을 선언하면 (keyof IMyTable)[]
이러한 오류를 포착 할 수 없습니다.
또한 특정 라이브러리 (마지막 변형 사용 ts-morph
, 일반 컴파일러 래퍼로 간주)가 필요하지 않으며 객체 (첫 번째 솔루션 만 배열 생성) 또는 와이드 배열 유형 (비교) 과는 반대로 튜플 유형 을 방출 합니다. 이 답변 ) 마지막으로 수업이 필요하지 않습니다 .
변형 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")[]
+
+-
자동 완성 -
배열로 가장 쉬운 수동 , 튜플 없음
레코드 생성이 마음에 들지 않으면 및 어설 션 유형 이Set
있는 이 대안을 살펴보십시오 .
변형 2 : 도우미 함수가있는 튜플
function createKeys<T extends readonly (keyof IMyTable)[] | [keyof IMyTable]>(
t: T & CheckMissing<T, IMyTable> & CheckDuplicate<T>): T {
return t
}
+
+-
자동 완성 기능이있는 튜플 수동 +-
더 고급, 복잡한 유형
설명
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"]
참고 : 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"]]
참고 : 튜플의 고유 항목 검사에 대한 자세한 정보는 이 게시물에 있습니다. 와 TS 4.1 , 우리는 또한 오류 문자열에없는 키의 이름을 지정할 수 있습니다 – 한 번 봐 가지고 이 놀이터를 .
변형 3 : 재귀 유형
버전 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"] // ✔
+
튜플 +-
자동 완성 기능과 수동 +
없는 도우미 함수의 --
성능
변형 4 : 코드 생성기 / TS 컴파일러 API
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
로이 파일을 컴파일하고 실행하면 다음 내용이 포함 된 tsc && node dist/mybuildstep.js
파일 ./src/generated/IMyTable-keys.ts
이 생성됩니다.
// ./src/generated/IMyTable-keys.ts
const keys = ["id","title","createdAt","isDeleted"] as const;
+
+
여러 속성에 대해 확장 가능한 자동 생성 솔루션 +
도우미 기능 없음 +
튜플 -
추가 빌드 단계 -
에는 컴파일러 API에 대한 지식이 필요합니다.
답변
정의하는 대신 IMyTable
인터페이스에서와 같이 클래스로 정의 해보십시오. 타이프 스크립트에서는 인터페이스와 같은 클래스를 사용할 수 있습니다.
따라서 예를 들어 다음과 같이 클래스를 정의 / 생성하십시오.
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());