Typescript 인터페이스의 키를 문자열 배열로 가져옵니다.


112

나는 Lovefield에 많은 테이블 과 그들이 가진 열에 대한 각각의 인터페이스를 가지고 있습니다.
예:

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

이 인터페이스의 속성 이름을 다음과 같은 배열로

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

만들고 싶습니다. 나는 IMyTable테이블의 인터페이스 이름을 동적으로 가져 오기 때문에 트릭을 수행해야하는 인터페이스를 기반으로 개체 / 배열을 직접 만들 수 없습니다 . 따라서 인터페이스에서 이러한 속성을 반복하고 배열을 가져와야합니다.

이 결과를 얻으려면 어떻게해야합니까?

답변:


55

현재 타이프 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 2.4를 사용하기 때문에 지금은 사용하지 않습니다.
Tushar Shukla

17
안녕하세요,이 라이브러리는 내 요구 사항을 정확히 충족하지만 ts_transformer_keys_1.keys is not a function설명서의 정확한 단계를 따를 때 얻습니다 . 이 해결 방법이 있습니까?
Hasitha 산

산뜻한! 동적 유형 매개 변수를 사용하도록 확장 할 수 있다고 생각하십니까 (Readme의 참고 2)?
켄신

문서에 자세히 보면 @HasithaShan - 당신은 작동하려면 패키지에 타이프 라이터 컴파일러 API를 사용해야합니다
야로 슬라브 바이

3
안타깝게도 패키지가 깨졌습니다. 내가 무엇을하든 항상 받고 있습니다ts_transformer_keys_1.keys is not a function
fr1sk

20

다음은 사용자가 직접 키를 나열해야하지만 적어도 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,
};

아주 멋진 트릭. 이제 모든 키를 구현하는 IUserProfile것이 쉽고 const에서 쉽게 추출 할 수 있습니다 IUserProfileKeys. 이것이 바로 제가 찾던 것입니다. 이제 모든 인터페이스를 클래스로 변환 할 필요가 없습니다.
Anddo

17

인터페이스와 객체를 모두 갖고 싶어하는 거대한 속성 목록이 있다는 비슷한 문제가 있습니다.

참고 : 속성을 두 번 작성 (키보드로 입력)하고 싶지 않았습니다! 그냥 말리십시오.


여기서 주목해야 할 점은 인터페이스는 컴파일 타임에 강제 유형이며 객체는 대부분 런타임에 적용된다는 것입니다. ( 출처 )

@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"] 

TypeScript 놀이터의 생성자 트릭 .


propsArray당신이 비록 키를 초기화 할 경우에만 액세스 할 수 있습니다.
denkquer

@denkquer "초기화"가 의미하는 바를 이해하지 못합니다. 첫 번째 예에서는 의미 propsArray하는 tableInstance경우 이전에 사용할 수 있으므로 인스턴스 초기화 전에 명확하게 사용할 수 있습니다 . 그러나에 할당 된 가짜 값을 참조하는 MyTableClass경우 속성의 "유형"을 간단히 암시하기 위해 존재합니다. 원하지 않는 경우 PS 예제에서 생성자 트릭을 사용할 수 있습니다.
Aidin

1
내 이해에서 값은 값이있을 때 초기화됩니다. "생성자 트릭"은를 MyTableClass후자로 대체 할 수없고 propsArray초기화되지 않은 var 및 유형이 런타임에 제거되므로 키를받을 수 있기 때문에 오해의 소지가 있습니다 . 항상 어떤 종류의 기본값을 제공해야합니다. 나는 그들을 초기화하는 undefined것이 최선의 접근 방식 이라는 것을 알았습니다 .
denkquer

1
@Aidin 솔루션에 감사드립니다. 또한 매개 변수의 초기화를 피할 수 있는지 궁금합니다. 내가 생성자 트릭을 사용하면 더 이상 MyTableClass를 확장하는 인터페이스를 만들 수 없습니다. .. typescript 놀이터 링크의 생성자 트릭은 비어 있습니다
Flion

1
@Flion, 알아 주셔서 감사합니다. 방금 생성자 트릭에 대한 플레이 그라운드 링크를 업데이트했습니다. 지금 작동하는지 확인하십시오.
Aidin

10

이것은 작동합니다

var IMyTable: Array<keyof IMyTable> = ["id", "title", "createdAt", "isDeleted"];

또는

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

15
틀린 것은 아니지만 여기서 명확하게하기 위해 "배열의 값을 올바르게 적용"하는 것입니다. 개발자는 여전히 수동으로 두 번 기록해야합니다.
Aidin

Aidin이 말한 것이 사실 일 수도 있지만, 어떤 경우에는 이것이 바로 제가 찾고 있던 것이 었습니다. 감사합니다.
Daniel

4
이것은 키 중복 또는 누락 된 키를 방지하지 않습니다. 좋아요var IMyTable: Array<keyof IMyTable> = ["id", "createdAt", "id"];
ford04

나에게도 선택적으로 키를 받아들이고 싶지만 인터페이스에 정의 된 키에 지나지 않기 때문에 내가 찾던 것이 었습니다. 위의 코드에서 이것이 기본값이 될 것이라고 예상하지 못했습니다. 나는 우리가 그것을 위해 여전히 일반적인 TS 방법이 필요하다고 생각합니다. 어쨌든 위 코드에 감사드립니다!
nicoes

10

너무 늦었을 수도 있지만 버전 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

문서 : https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types


답변 주셔서 감사하지만 누군가가 인터페이스에서 정적으로 생성 된 유형을 사용하는 데 도움이되는지 확실하지 않습니다. IMHO, 우리는 대부분의 경우 인터페이스 / 유형을 서로 바꿔서 사용할 수 있습니다. 또한 여러 인터페이스에 대한 유형을 수동으로 생성해야합니다. 그러나 누군가 인터페이스에서 유형을 가져와야하는 경우 솔루션이 좋아 보입니다.
Tushar Shukla

8

안전한 변종

안전 컴파일 시간 검사를 사용하여 인터페이스에서 키 배열 또는 튜플 을 만들려면 약간의 창의성이 필요합니다. 유형은 런타임에 지워지고 개체 유형 (정렬되지 않음, 명명 됨)은 지원되지 않는 기술에 의존하지 않고 튜플 유형 (정렬 됨, 명명되지 않음)으로 변환 될 수 없습니다 .

다른 답변과 비교

여기에서 제안 된 변형은 모두 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에 대한 지식이 필요합니다.


6

정의하는 대신 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());

5

인터페이스를 구현하는 클래스를 만들고 인스턴스화 한 다음 Object.keys(yourObject)속성을 가져 오는 데 사용해야 합니다.

export class YourClass implements IMyTable {
    ...
}

그때

let yourObject:YourClass = new YourClass();
Object.keys(yourObject).forEach((...) => { ... });

제 경우에는 작동하지 않습니다. 인터페이스의 속성을 나열해야하는데 그게 제가 원하는 것이 아닙니다. 인터페이스의 이름을 동적으로 제공하고 난 속성을 결정했습니다
Tushar Shukla에게

이로 인해 오류가 발생합니다 (v2.8.3). Cannot extend an interface […]. Did you mean 'implements'?그러나 implements대신 사용하려면 인터페이스를 수동으로 복사해야하는데, 이는 정확히 제가 원하지 않는 것입니다.
jacob

@jacob 죄송합니다. 그래야했고 implements내 대답을 업데이트했습니다. @basarat가 말했듯이 인터페이스는 런타임에 존재하지 않으므로 유일한 방법은 클래스로 구현하는 것입니다.
Dan Def

인터페이스 대신 클래스를 사용한다는 의미입니까? 안타깝게도 인터페이스가 타사 ( @types/react) 에서 제공되므로 할 수 없습니다 . 수동으로 복사했지만 미래 보장이 거의 없습니다 😪 비 수명주기 메서드 (이미 바인딩 된)를 동적으로 바인딩하려고하지만 React.Component (클래스)에서 선언되지 않았습니다.
jacob

아니요, 타사 인터페이스를 구현하는 클래스를 만들고 런타임에 해당 클래스의 속성을 가져 오는 것을 의미합니다.
Dan Def

3

캔트. 인터페이스는 런타임에 존재하지 않습니다.

해결 방법

유형의 변수를 만들고 사용 Object.keys하십시오 🌹


1
:이 같은 의미합니까var abc: IMyTable = {}; Object.keys(abc).forEach((key) => {console.log(key)});
Tushar Shukla

4
아니요, 그 개체에는 키가 없기 때문입니다. 인터페이스는 TypeScript가 사용하지만 JavaScript에서 증발하므로 "반사"또는 "관찰"을 알리는 정보가 남아 있지 않습니다. 모든 JavaScript는 빈 개체 리터럴이 있다는 것을 알고 있습니다. 유일한 희망은 TypeScript에 인터페이스의 모든 키가있는 배열 또는 객체를 소스 코드로 생성하는 방법이 포함되어 있기를 기다리거나 요청 하는 것입니다. 또는 Dan Def가 말했듯이 클래스를 사용할 수 있다면 모든 인스턴스에서 속성 형식으로 정의 된 키를 갖게됩니다.
Jesper

17
이것이 작동하지 않는다면 왜이 답변에 대한 찬성표가 있습니까?
dawez


2
가치를 제공해야하므로 궁극적으로 훌륭한 솔루션은 아닙니다. 아마도 키 목록을 유지하는 것이 좋습니다.
Daniel Thompson

0

이것은 힘든 일이었습니다! 여러분의 도움에 감사드립니다.

필자는 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)

-1

당신은 할 수 없습니다. 인터페이스는 런타임에 존재하지 않습니다 ( @basarat가 말한 것처럼 ).

이제 다음 작업을하고 있습니다.

const IMyTable_id = 'id';
const IMyTable_title = 'title';
const IMyTable_createdAt = 'createdAt';
const IMyTable_isDeleted = 'isDeleted';

export const IMyTable_keys = [
  IMyTable_id,
  IMyTable_title,
  IMyTable_createdAt,
  IMyTable_isDeleted,
];

export interface IMyTable {
  [IMyTable_id]: number;
  [IMyTable_title]: string;
  [IMyTable_createdAt]: Date;
  [IMyTable_isDeleted]: boolean;
}

모델이 많고 모두를 위해 그렇게한다고 상상해보세요 ... 너무 많은 시간이 걸립니다.
William Cuervo

-6
// declarations.d.ts
export interface IMyTable {
      id: number;
      title: string;
      createdAt: Date;
      isDeleted: boolean
}
declare var Tes: IMyTable;
// call in annother page
console.log(Tes.id);

1
런타임에서 typescript 구문을 사용할 수 없으므로이 코드는 작동하지 않습니다. typescript 플레이 그라운드에서이 코드를 확인하면 JavaScript로 컴파일되는 유일한 것은 console.log(Tes.id)물론 오류 'Uncaught ReferenceError : Tes is not defined'입니다.
Tushar Shukla
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.