Dapatkan kunci antarmuka Ketikan sebagai larik string

113

Saya memiliki banyak tabel di Lovefield dan Antarmuka masing-masing untuk kolom apa yang mereka miliki.
Contoh:

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

Saya ingin memiliki nama properti antarmuka ini dalam array seperti ini:

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

Saya tidak dapat membuat objek / array berdasarkan antarmuka IMyTablesecara langsung yang seharusnya melakukan trik karena saya akan mendapatkan nama antarmuka tabel secara dinamis. Oleh karena itu saya perlu mengulang properti ini di antarmuka dan mendapatkan array darinya.

Bagaimana cara saya mencapai hasil ini?

Tushar Shukla
sumber

Jawaban:

57

Mulai TypeScript 2.3 (atau harus saya katakan 2.4 , seperti di 2.3 fitur ini berisi bug yang telah diperbaiki di [email protected] ), Anda dapat membuat transformator khusus untuk mencapai apa yang ingin Anda lakukan.

Sebenarnya, saya sudah membuat trafo ubahsuaian, yang memungkinkan yang berikut ini.

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

Sayangnya, trafo khusus saat ini tidak begitu mudah digunakan. Anda harus menggunakannya dengan API transformasi TypeScript daripada menjalankan perintah tsc. Ada masalah saat meminta dukungan plugin untuk transformer kustom.

kimamula
sumber
Terima kasih atas tanggapan Anda, saya sudah melihat dan memasang trafo kustom ini kemarin tetapi karena ini menggunakan skrip tipe 2.4, ini tidak ada gunanya bagi saya seperti sekarang.
Tushar Shukla
17
Hai, perpustakaan ini melayani kebutuhan saya juga, namun, saya dapatkan ts_transformer_keys_1.keys is not a functionketika saya mengikuti langkah-langkah yang tepat dalam dokumentasi. apakah ada solusi untuk ini?
Hasitha Shan
Rapi! Apakah menurut Anda itu dapat diperpanjang untuk mengambil parameter tipe dinamis (catatan 2 di readme)?
kenshin
@HasithaShan melihat lebih dekat ke dokumen - Anda harus menggunakan API kompilator TypeScript agar paket berfungsi
Yaroslav Bai
3
Sayangnya, paketnya rusak, apa pun yang saya lakukan selalu saya dapatkants_transformer_keys_1.keys is not a function
fr1sk
20

Hal berikut mengharuskan Anda untuk membuat daftar kunci Anda sendiri, tetapi setidaknya TypeScript akan diberlakukan IUserProfiledan IUserProfileKeysmemiliki kunci yang sama persis ( Required<T>ditambahkan di 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,
};
Maciek Wawro
sumber
Trik yang cukup keren. Sekarang mudah untuk menerapkan implementasi semua kunci dari IUserProfiledan akan mudah untuk mengekstraknya dari const IUserProfileKeys. Inilah yang saya cari-cari. Tidak perlu mengonversi semua antarmuka saya ke kelas sekarang.
Anddo
17

Saya memiliki masalah serupa yaitu saya memiliki daftar besar properti yang saya inginkan untuk memiliki antarmuka dan objek darinya.

CATATAN: Saya tidak ingin menulis (mengetik dengan keyboard) properti dua kali! KERING saja.


Satu hal yang perlu diperhatikan di sini adalah, antarmuka adalah tipe yang diberlakukan pada waktu kompilasi, sedangkan objek sebagian besar merupakan waktu proses. ( Sumber )

Seperti yang disebutkan @derek di jawaban lain , penyebut umum antarmuka dan objek dapat berupa kelas yang melayani tipe dan nilai .

Jadi, TL; DR, potongan kode berikut harus memenuhi kebutuhan:

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

( Ini adalah kode di atas di Typecript Playground untuk bermain lebih banyak)

PS. Jika Anda tidak ingin menetapkan nilai awal ke properti di kelas, dan tetap menggunakan tipenya, Anda dapat melakukan trik konstruktor:

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

Trik Konstruktor di TypeScript Playground .

Aidin
sumber
Ini propsArrayhanya dapat diakses ketika Anda menginisialisasi kunci.
denkquer
Saya tidak mengerti apa yang Anda maksud dengan "diinisialisasi" @denkquer. Dalam contoh pertama, propsArraytersedia sebelum tableInstancejika itu yang Anda maksud, jadi jelas sebelum inisialisasi contoh. Namun, jika Anda mengacu pada nilai palsu yang ditetapkan MyTableClass, nilai tersebut hanya ada untuk menyiratkan "jenis" properti. Jika Anda tidak menginginkannya, Anda dapat menggunakan trik konstruktor dalam contoh PS.
Aidin
1
Dalam pemahaman saya, sebuah nilai diinisialisasi ketika memiliki nilai apa pun. "Trik konstruktor" Anda menyesatkan karena Anda tidak bisa begitu saja mengganti MyTableClassdengan yang terakhir dan berharap untuk menerima kunci dalam propsArrayvars dan tipe yang tidak diinisialisasi dihapuskan pada waktu proses. Anda selalu harus memberikan mereka beberapa jenis nilai default. Saya telah menemukan bahwa menginisialisasi mereka dengan undefinedadalah pendekatan terbaik.
denkquer
1
@Aidin terima kasih atas solusinya. Saya juga bertanya-tanya apakah saya dapat menghindari inisialisasi parameter. Jika saya menggunakan trik konstruktor, saya tidak dapat membuat antarmuka lagi yang memperluas MyTableClass .. meskipun trik konstruktor Anda di taman bermain skrip kosong
Flion
1
@Flion, terima kasih telah memperhatikannya. Saya baru saja memperbarui tautan taman bermain untuk trik konstruktor. Lihat apakah itu berhasil sekarang.
Aidin
11

Mungkin sudah terlambat, tapi di typecript versi 2.1 anda bisa menggunakan key ofseperti ini:

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

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

Nathan Gabriel
sumber
Terima kasih atas jawabannya tetapi tidak yakin apakah itu membantu seseorang untuk menggunakan Jenis yang dibuat secara statis dari antarmuka. IMHO, kita dapat menggunakan antarmuka / tipe secara bergantian di sebagian besar kasus. Ditambah ini akan membutuhkan pembuatan tipe manual untuk banyak antarmuka. Namun solusinya terlihat bagus jika seseorang hanya perlu mendapatkan tipe dari sebuah antarmuka.
Tushar Shukla
9

Ini seharusnya berhasil

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

atau

var IMyTable: (keyof IMyTable)[] = ["id", "title", "createdAt", "isDeleted"];
Damathryx
sumber
17
Bukan karena itu salah, tetapi untuk memperjelas di sini Anda hanya "memaksakan nilai-nilai array" agar benar. Pengembang masih perlu menuliskannya dua kali, secara manual.
Aidin
Meskipun apa yang dikatakan Aidin mungkin benar, untuk beberapa kasus, inilah yang sebenarnya saya cari, untuk kasus saya. Terima kasih.
Daniel
4
Ini tidak akan mencegah duplikat kunci atau kunci yang hilang. Sepertivar IMyTable: Array<keyof IMyTable> = ["id", "createdAt", "id"];
ford04
Bagi saya itu juga yang saya cari karena saya ingin menerima kunci secara opsional tetapi tidak lebih dari kunci yang ditentukan dalam antarmuka. Tidak mengharapkan ini menjadi default dengan kode di atas. Saya kira kita masih membutuhkan cara TS umum untuk itu. Bagaimanapun juga, terima kasih untuk kode di atas!
bagus
8

Varian yang aman

Membuat array atau tuple kunci dari antarmuka dengan pemeriksaan waktu kompilasi keamanan membutuhkan sedikit kreativitas. Tipe dihapus pada waktu proses dan tipe objek (tidak berurutan, dinamai) tidak dapat diubah menjadi tipe tupel (diurutkan, tidak dinamai) tanpa menggunakan teknik yang tidak didukung .

Perbandingan dengan jawaban lain

Varian yang diusulkan di sini semua mempertimbangkan / memicu kesalahan kompilasi jika ada item tupel duplikat atau hilang dengan jenis objek referensi seperti IMyTable. Misalnya mendeklarasikan tipe array(keyof IMyTable)[] tidak dapat menangkap kesalahan ini.

Selain itu, mereka tidak memerlukan pustaka tertentu (penggunaan varian terakhir ts-morph, yang saya anggap sebagai pembungkus kompiler generik), memancarkan jenis tupel sebagai lawan dari objek (hanya solusi pertama yang membuat larik) atau jenis larik lebar (bandingkan dengan jawaban ini ) dan terakhir tidak perlu kelas .

Varian 1: Array yang diketik sederhana

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

++-manual termudah dengan pelengkapan otomatis- larik , tanpa tupel

Tempat bermain

Jika Anda tidak suka membuat record, lihat alternatif with Setdan assertion types ini .


Varian 2: Tuple dengan fungsi helper

function createKeys<T extends readonly (keyof IMyTable)[] | [keyof IMyTable]>(
    t: T & CheckMissing<T, IMyTable> & CheckDuplicate<T>): T {
    return t
}

++-manual tuple dengan pelengkapan otomatis +-tipe yang lebih canggih dan kompleks

Tempat bermain

Penjelasan

createKeysmelakukan pemeriksaan waktu kompilasi dengan menggabungkan tipe parameter fungsi dengan tipe pernyataan tambahan, yang mengeluarkan kesalahan untuk input yang tidak sesuai. (keyof IMyTable)[] | [keyof IMyTable]adalah cara "sihir hitam" untuk memaksa inferensi tupel alih-alih larik dari sisi callee. Atau, Anda dapat menggunakan pernyataan konstanta /as const dari sisi pemanggil.

CheckMissingcek, jika Tmelewatkan kunci dari 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"]

Catatan: T & "Error: missing keys"hanya untuk kesalahan IDE yang bagus. Anda juga bisa menulis never. CheckDuplicatesmemeriksa item tupel ganda:

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

Catatan: Info lebih lanjut tentang pemeriksaan item unik di tuple ada di posting ini . Dengan TS 4.1 , kami juga dapat memberi nama kunci yang hilang dalam string kesalahan - lihat Playground ini .


Varian 3: Tipe rekursif

Dengan versi 4.1, TypeScript secara resmi mendukung jenis rekursif bersyarat , yang berpotensi digunakan di sini juga. Padahal, jenis komputasi mahal karena kompleksitas kombinasinya - kinerja menurun drastis untuk lebih dari 5-6 item. Saya mendaftar alternatif ini untuk kelengkapan ( 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"] // ✔

++-manual tuple dengan pelengkapan otomatis +tanpa --kinerja fungsi pembantu


Varian 4: Pembuat kode / API penyusun TS

ts-morph dipilih di sini, karena ini adalah alternatif pembungkus anak laki-laki yang lebih sederhana dari API kompilator TS asli . Tentu saja, Anda juga dapat menggunakan API kompilator secara langsung. Mari kita lihat kode generatornya:

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

Setelah kami mengkompilasi dan menjalankan file ini dengan tsc && node dist/mybuildstep.js, file ./src/generated/IMyTable-keys.tsdengan konten berikut dihasilkan:

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

+solusi pembuatan otomatis yang dapat +diskalakan untuk beberapa properti, +tidak ada fungsi pembantu, +tuple, -langkah build tambahan, -perlu pemahaman dengan API kompiler

ford04
sumber
6

Alih-alih mendefinisikan IMyTablesebagai antarmuka, coba definisikan sebagai kelas. Dalam skrip ketikan Anda dapat menggunakan kelas seperti antarmuka.

Jadi untuk contoh Anda, tentukan / buat kelas Anda seperti ini:

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

Gunakan sebagai antarmuka:

export class SomeTable implements IMyTable {
    ...
}

Dapatkan kunci:

const keys = Object.keys(new IMyTable());
Derek
sumber
5

Anda perlu membuat kelas yang mengimplementasikan antarmuka Anda, membuat instance-nya, lalu menggunakan Object.keys(yourObject)untuk mendapatkan properti.

export class YourClass implements IMyTable {
    ...
}

kemudian

let yourObject:YourClass = new YourClass();
Object.keys(yourObject).forEach((...) => { ... });
Dan Def
sumber
Tidak berfungsi dalam kasus saya, saya harus membuat daftar properti antarmuka tersebut tetapi bukan itu yang saya inginkan? Nama antarmuka muncul secara dinamis dan kemudian saya harus menentukan propertinya
Tushar Shukla
Ini menghasilkan kesalahan (v2.8.3): Cannot extend an interface […]. Did you mean 'implements'?Namun, menggunakan implementssebaliknya memerlukan penyalinan antarmuka secara manual, yang sebenarnya tidak saya inginkan.
jacob
@jacob maaf, seharusnya sudah implementsdan saya telah memperbarui jawaban saya. Seperti yang dikatakan @basarat, antarmuka tidak ada pada waktu proses jadi satu-satunya cara adalah mengimplementasikannya sebagai kelas.
Dan Def
Maksud Anda alih-alih antarmuka, gunakan kelas? Sayangnya saya tidak bisa karena antarmukanya berasal dari pihak ketiga ( @types/react). Saya menyalinnya secara manual, tapi itu hampir tidak tahan masa depan 😪 Saya mencoba mengikat metode non-siklus hidup secara dinamis (yang sudah terikat), tetapi metode tersebut tidak dideklarasikan di React.Component (kelas).
jacob
Tidak, maksud saya buat kelas yang mengimplementasikan antarmuka pihak ke-3 Anda dan dapatkan properti kelas tersebut saat runtime.
Dan Def
3

Tidak bisa. Antarmuka tidak ada saat runtime.

solusi

Buat variabel dari tipe dan gunakan Object.keysdi atasnya 🌹

basarat
sumber
1
Apakah yang Anda maksud seperti ini:var abc: IMyTable = {}; Object.keys(abc).forEach((key) => {console.log(key)});
Tushar Shukla
4
Tidak, karena benda itu tidak memiliki kunci. Antarmuka adalah sesuatu yang digunakan TypeScript tetapi menguap di JavaScript, jadi tidak ada informasi tersisa untuk menginformasikan "refleksi" atau "interspeksi". Semua JavaScript tahu bahwa ada literal objek kosong. Satu-satunya harapan Anda adalah menunggu (atau meminta itu ) TypeScript menyertakan cara untuk menghasilkan larik atau objek dengan semua kunci di antarmuka ke dalam kode sumber. Atau, seperti yang dikatakan Dan Def, jika Anda dapat menggunakan kelas, Anda akan mendapatkan kunci yang ditentukan dalam bentuk properti di setiap contoh ..
Jesper
17
Jika ini tidak berhasil, mengapa ada suara positif untuk jawaban ini?
dawez
2
alasan downvote: tidak disebutkan bahwa itu tidak berfungsi untuk nilai nullable
TamusJRoyce
2
Ini pada akhirnya bukan solusi yang bagus karena Anda harus memberikan nilai. Mungkin lebih baik menyimpan daftar kunci.
Daniel Thompson
0

Ini yang sulit! Terima kasih, semuanya, atas bantuan Anda.

Kebutuhan saya adalah mendapatkan kunci antarmuka sebagai array string untuk menyederhanakan skrip mocha / chai. Tidak peduli tentang penggunaan di aplikasi (belum), jadi tidak perlu file ts untuk dibuat. Terima kasih kepada ford04 atas bantuannya, solusinya di atas sangat membantu dan berfungsi dengan sempurna, TANPA peretasan kompiler. Berikut kode yang dimodifikasi:

Opsi 2: Pembuat kode berdasarkan TS compiler API (ts-morph)

Modul Node

npm install --save-dev ts-morph

keys.ts

CATATAN : ini mengasumsikan semua file ts terletak di root ./src dan tidak ada subfolder, sesuaikan

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;

pemakaian

import keys from "./keys";

const myKeys = keys("MyInterface") //ts file name without extension

console.log(myKeys)
Michael
sumber
-1

Anda tidak bisa melakukannya. Antarmuka tidak ada saat runtime (seperti yang dikatakan @basarat ).

Sekarang, saya sedang mengerjakan yang berikut:

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;
}
Eduardo Cuomo
sumber
Bayangkan hanya memiliki banyak model, dan melakukannya untuk semua orang ... waktu yang sangat mahal.
William Cuervo
-7
// 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);
Brillian Andrie Nugroho Wiguno
sumber
1
Kode ini tidak akan berfungsi karena sintaks skrip ketikan tidak tersedia pada waktu proses. Jika Anda memeriksa kode ini di taman bermain skrip maka Anda akan melihat bahwa satu-satunya hal yang mengkompilasi ke JavaScript adalah console.log(Tes.id)yang tentu saja akan menjadi kesalahan 'Uncaught ReferenceError: Tes tidak ditentukan'
Tushar Shukla