Nhận các khóa của giao diện Typecript dưới dạng mảng chuỗi


102

Tôi có rất nhiều bảng trong Lovefield và các Giao diện tương ứng của chúng cho những cột nào chúng có.
Thí dụ:

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

Tôi muốn có các tên thuộc tính của giao diện này trong một mảng như thế này:

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

Tôi không thể tạo một đối tượng / mảng dựa trên giao diện IMyTabletrực tiếp, điều này sẽ thực hiện thủ thuật vì tôi sẽ lấy tên giao diện của các bảng một cách động. Do đó, tôi cần phải lặp lại các thuộc tính này trong giao diện và lấy một mảng ra khỏi nó.

Làm thế nào để tôi đạt được kết quả này?

Câu trả lời:


52

Đối với TypeScript 2.3 (hoặc tôi nên nói 2.4 , như trong 2.3 , tính năng này chứa một lỗi đã được sửa trong stylescript@2.4-dev ), bạn có thể tạo một máy biến áp tùy chỉnh để đạt được những gì bạn muốn.

Trên thực tế, tôi đã tạo một máy biến áp tùy chỉnh như vậy, cho phép những điều sau đây.

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']

Thật không may, máy biến áp tùy chỉnh hiện không dễ sử dụng như vậy. Bạn phải sử dụng chúng với API chuyển đổi TypeScript thay vì thực hiện lệnh tsc. Đã xảy ra sự cố khi yêu cầu hỗ trợ plugin cho máy biến áp tùy chỉnh.


Cảm ơn phản hồi của bạn, tôi đã nhìn thấy và cài đặt biến áp tùy chỉnh này ngày hôm qua nhưng vì nó sử dụng phiên bản 2.4, điều này không có ích cho tôi cho đến nay.
Tushar Shukla

16
Xin chào, thư viện này cũng phục vụ chính xác yêu cầu của tôi, tuy nhiên, tôi nhận được ts_transformer_keys_1.keys is not a functionkhi làm theo các bước chính xác trong tài liệu. có cách giải quyết này không?
Hasitha Shan

Khéo léo! Bạn có nghĩ rằng nó có thể được mở rộng để lấy tham số kiểu động (lưu ý 2 trong readme) không?
kenshin

@HasithaShan xem xét kỹ các tài liệu - bạn phải sử dụng API trình biên dịch TypeScript để gói thứ tự hoạt động
Yaroslav Bai

2
Thật không may, gói hàng bị hỏng, bất cứ điều gì tôi làm, tôi luôn nhận đượcts_transformer_keys_1.keys is not a function
fr1sk

17

Phần sau yêu cầu bạn phải tự liệt kê các khóa, nhưng ít nhất TypeScript sẽ thực thi IUserProfileIUserProfileKeyscó các khóa giống hệt nhau ( Required<T>đã được thêm vào 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,
};

Mẹo khá hay. Giờ đây, thật dễ dàng để thực thi tất cả các khóa của IUserProfilevà sẽ dễ dàng giải nén chúng từ const IUserProfileKeys. Đây chính xác là những gì tôi đang tìm kiếm. Không cần phải chuyển đổi tất cả các giao diện của tôi thành các lớp ngay bây giờ.
Anddo

13

Tôi đã gặp phải một vấn đề tương tự là tôi có một danh sách khổng lồ các thuộc tính mà tôi muốn có cả giao diện và một đối tượng trong đó.

LƯU Ý: Tôi không muốn viết (gõ bằng bàn phím) các thuộc tính hai lần! Chỉ KHÔ.


Một điều cần lưu ý ở đây là, các giao diện là kiểu thực thi tại thời điểm biên dịch, trong khi các đối tượng chủ yếu là thời gian chạy. ( Nguồn )

Như @derek đã đề cập trong một câu trả lời khác , mẫu số chung của giao diệnđối tượng có thể là một lớp phục vụ cả kiểugiá trị .

Vì vậy, TL; DR, đoạn mã sau đây sẽ đáp ứng các nhu cầu:

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

( Đây là đoạn mã trên trong Sân chơi Typecript để chơi thêm)

Tái bút. Nếu bạn không muốn gán giá trị ban đầu cho các thuộc tính trong lớp và ở lại với kiểu, bạn có thể thực hiện thủ thuật xây dựng:

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

Thủ thuật xây dựng trong Sân chơi TypeScript .


Tuy nhiên, propsArraychỉ có thể truy cập được khi bạn khởi tạo các khóa.
denkquer

Tôi không hiểu ý của bạn khi "khởi tạo" @denkquer. Trong ví dụ đầu tiên, propsArraycó sẵn trước tableInstancenếu đó là ý của bạn, nên rõ ràng trước khi khởi tạo phiên bản. Tuy nhiên, nếu bạn đang đề cập đến các giá trị giả được chỉ định trong đó MyTableClass, những giá trị đó chỉ ở đó để hàm ý ngay về "loại" của thuộc tính. Nếu bạn không muốn chúng, bạn có thể sử dụng thủ thuật khởi tạo trong ví dụ PS.
Aidin

1
Theo hiểu biết của tôi, một giá trị được khởi tạo khi nó có bất kỳ giá trị nào. "Thủ thuật xây dựng" của bạn gây hiểu lầm bởi vì bạn không thể chỉ thay thế cái MyTableClassbằng cái sau và mong đợi nhận được các khóa trong propsArraykhi các loại và vars chưa được khởi tạo sẽ bị loại bỏ trong thời gian chạy. Bạn luôn phải cung cấp cho chúng một số loại giá trị mặc định. Tôi thấy rằng khởi tạo chúng bằng undefinedlà cách tốt nhất.
denkquer

@denkquer xin lỗi, thủ thuật Constructor của tôi bị thiếu readonly?trên paramteres. Tôi vừa cập nhật điều đó. Cảm ơn đã chỉ ra. Bây giờ tôi nghĩ rằng nó hoạt động theo cách bạn nói là sai lệch và không thể hoạt động. :)
Aidin

@Aidin cảm ơn bạn về giải pháp của bạn. Tôi cũng tự hỏi liệu tôi có thể tránh việc khởi tạo các tham số hay không. Nếu tôi sử dụng thủ thuật xây dựng, tôi không thể tạo giao diện mở rộng MyTableClass nữa .. thủ thuật xây dựng của bạn trong liên kết sân chơi
typecript

10

Điều này sẽ hoạt động

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

hoặc là

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

10
Không phải điều đó sai, nhưng nói rõ ở đây bạn chỉ đang "thực thi các giá trị của mảng" là đúng. Nhà phát triển vẫn cần viết chúng ra hai lần, theo cách thủ công.
Aidin

Mặc dù những gì Aidin nói có thể đúng, nhưng đối với một số trường hợp, đây chính xác là những gì tôi đang tìm kiếm, đối với trường hợp của tôi. Cảm ơn bạn.
Daniel

4
Điều này sẽ không ngăn chặn các bản sao khóa hoặc thiếu khóa. Thíchvar IMyTable: Array<keyof IMyTable> = ["id", "createdAt", "id"];
ford04 Ngày

Đối với tôi, đó cũng là những gì tôi đang tìm kiếm vì tôi muốn tùy chọn chấp nhận các phím chứ không gì khác ngoài các phím được xác định trong giao diện. Tôi không mong đợi điều này được mặc định với mã trên. Tôi đoán chúng ta vẫn cần một cách TS chung cho điều đó. Cảm ơn trong mọi trường hợp cho mã trên!
nicoes

8

Có lẽ đã quá muộn, nhưng trong phiên bản 2.1 của typecript bạn có thể sử dụng key ofnhư sau:

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

Tài liệu: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types


Cảm ơn câu trả lời nhưng không chắc liệu nó có giúp ai đó sử dụng Loại được tạo tĩnh từ giao diện hay không. IMHO, chúng tôi có thể sử dụng các loại / giao diện thay thế cho nhau trong hầu hết các trường hợp. Thêm vào đó, điều này sẽ yêu cầu tạo thủ công các loại cho nhiều giao diện. Tuy nhiên, giải pháp có vẻ tốt nếu ai đó chỉ cần lấy các loại ra khỏi giao diện.
Tushar Shukla

6

Thay vì xác định IMyTablenhư trong giao diện, hãy thử định nghĩa nó như một lớp. Trong bảng chữ, bạn có thể sử dụng một lớp giống như một giao diện.

Vì vậy, đối với ví dụ của bạn, hãy xác định / tạo lớp của bạn như sau:

export class IMyTable {
    constructor(
        public id = '',
        public title = '',
        public createdAt: Date = null,
        public isDeleted = false
    )
}

Sử dụng nó làm giao diện:

export class SomeTable implements IMyTable {
    ...
}

Nhận chìa khóa:

const keys = Object.keys(new IMyTable());

5

Bạn sẽ cần tạo một lớp triển khai giao diện của mình, khởi tạo nó và sau đó sử dụng Object.keys(yourObject)để lấy các thuộc tính.

export class YourClass implements IMyTable {
    ...
}

sau đó

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

Không hoạt động trong trường hợp của tôi, tôi phải liệt kê các thuộc tính đó của giao diện nhưng đó không phải là điều tôi muốn? Tên giao diện tự động xuất hiện và sau đó tôi phải xác định các thuộc tính của nó
Tushar Shukla

Điều này tạo ra lỗi (v2.8.3): Cannot extend an interface […]. Did you mean 'implements'?Tuy nhiên, việc sử dụng implementsthay thế yêu cầu sao chép giao diện theo cách thủ công, đó chính là điều tôi không muốn.
jacob

@jacob xin lỗi, nó đáng ra phải như vậy implementsvà tôi đã cập nhật câu trả lời của mình. Như @basarat đã nêu, các giao diện không tồn tại trong thời gian chạy vì vậy cách duy nhất là triển khai nó dưới dạng một lớp.
Dan Def

Ý bạn là thay vì một giao diện sử dụng một lớp? Rất tiếc, tôi không thể vì giao diện đến từ bên thứ 3 ( @types/react). Tôi đã sao chép chúng theo cách thủ công, nhưng điều đó khó có thể chứng minh được trong tương lai 😪 Tôi đang cố gắng liên kết động các phương thức không có vòng đời (đã được ràng buộc), nhưng chúng không được khai báo trên React.Component (lớp).
jacob

Không, ý tôi là tạo một lớp triển khai giao diện bên thứ 3 của bạn và nhận các thuộc tính của lớp đó trong thời gian chạy.
Dan Def

4

Không thể. Các giao diện không tồn tại trong thời gian chạy.

cách giải quyết

Tạo một biến loại và sử dụng Object.keystrên đó 🌹


1
Ý bạn có phải như thế này không:var abc: IMyTable = {}; Object.keys(abc).forEach((key) => {console.log(key)});
Tushar Shukla

4
Không, vì đối tượng đó không có chìa khóa trên đó. Giao diện là thứ mà TypeScript sử dụng nhưng bay hơi trong JavaScript, vì vậy không còn thông tin nào để thông báo cho bất kỳ "phản chiếu" hoặc "giao điểm" nào. Tất cả những gì JavaScript biết là có một đối tượng rỗng theo nghĩa đen. Hy vọng duy nhất của bạn là chờ đợi (hoặc yêu cầu điều đó ) TypeScript bao gồm một cách để tạo một mảng hoặc đối tượng với tất cả các khóa trong giao diện thành mã nguồn. Hoặc, như Dan Def nói, nếu bạn có thể sử dụng một lớp, bạn sẽ có các khóa được xác định dưới dạng thuộc tính trong mọi trường hợp ..
Jesper

1
Bạn cũng sẽ gặp lỗi bởi TypeScript trên dòng này: var abc: IMyTable = {}bởi vì đối tượng rỗng không phù hợp với hình dạng của giao diện đó.
Jesper

16
Nếu điều này không hiệu quả, tại sao có số phiếu ủng hộ cho câu trả lời này?
dawez

1
Lý do downvote: không đề cập đến rằng nó không hoạt động cho các giá trị nullable
TamusJRoyce

4

Không có cách nào dễ dàng để tạo một mảng khóa từ một giao diện. Các loại bị xóa trong thời gian chạy và các loại đối tượng (không có thứ tự, được đặt tên) không thể chuyển đổi thành các loại tuple (có thứ tự, không có tên) mà không có một số loại hack.


Tùy chọn 1: Cách tiếp cận thủ công

// 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")[]

(+) kiểu trả về mảng (-) đơn giản, không viết thủ công tuple (+ -) với tính năng tự động hoàn thành

Phần mở rộng: Chúng tôi có thể cố gắng trở nên lạ mắt và sử dụng các kiểu đệ quy để tạo một bộ tuple. Điều này chỉ phù hợp với tôi đối với một vài đạo cụ (~ 5,6) cho đến khi hiệu suất bị giảm sút hàng loạt. Kiểu đệ quy lồng nhau sâu cũng không được TS hỗ trợ chính thức - Tôi liệt kê ví dụ này ở đây vì mục đích đầy đủ.


Tùy chọn 2: Trình tạo mã dựa trên API trình biên dịch TS ( ts-morph )

// ./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

Sau khi chúng tôi biên dịch và chạy tệp này với tsc && node dist/mybuildstep.js, một tệp ./src/generated/IMyTable-keys.tscó nội dung sau sẽ được tạo:

// ./src/generated/IMyTable-keys.ts
const keys = ["id","title","createdAt","isDeleted"] as const;

(+) giải pháp tự động (+) loại tuple chính xác (-) yêu cầu bước xây dựng


Tái bút: Tôi đã chọn ts-morph, vì nó là một giải pháp thay thế đơn giản cho API trình biên dịch TS gốc.


-1

Bạn không thể làm điều đó. Các giao diện không tồn tại trong thời gian chạy (như @basarat đã nói).

Bây giờ, tôi đang làm việc với:

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

Hãy tưởng tượng chỉ có rất nhiều mô hình, và làm điều đó cho tất cả mọi người ... thật tốn kém thời gian.
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
Mã này sẽ không hoạt động vì cú pháp bảng chữ không có sẵn trong thời gian chạy. Nếu bạn kiểm tra mã này trên nguyên cảo sân chơi sau đó bạn sẽ nhận thấy rằng điều duy nhất mà biên dịch JavaScript là console.log(Tes.id)trong đó tất nhiên sẽ là lỗi 'của router ReferenceError: Tes không được định nghĩa'
Tushar Shukla
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.