Dizge dizisi olarak Typescript arabiriminin anahtarlarını alın


113

Lovefield'da ve hangi sütunlara sahip olduklarına ilişkin kendi Arayüzlerinde birçok tablo var.
Misal:

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

Bu arayüzün özellik adlarının şöyle bir dizide olmasını istiyorum: Doğrudan

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

arayüzü temel alan bir nesne / dizi IMyTableoluşturamıyorum ki bu hile yapması gereken, çünkü tabloların arayüz adlarını dinamik olarak alacağım. Bu yüzden arayüzde bu özellikleri yinelemem ve ondan bir dizi almam gerekiyor.

Bu sonuca nasıl ulaşırım?

Yanıtlar:


57

İtibariyle typescript 2.3 (veya demeliyim 2.4 olduğu gibi, 2.3 bu özellik içeren bir hatayı giderilmiştir typescript@2.4-dev ), size ne yapmak istediğinizi elde etmek için özel bir trafo oluşturabilirsiniz.

Aslında, aşağıdakileri mümkün kılan böyle özel bir transformatör yarattım.

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

Ne yazık ki, özel transformatörlerin kullanımı şu anda o kadar kolay değil. Tsc komutunu çalıştırmak yerine bunları TypeScript dönüştürme API'si ile kullanmanız gerekir . Özel transformatörler için eklenti desteği talep eden bir sorun var .


Cevabınız için teşekkürler, bu özel trafoyu dün görmüştüm ve kurdum ama bu tipkript 2.4'ü kullandığından, bu benim için şu an için bir faydası yok.
Tushar Shukla

17
Merhaba, bu kitaplık benim ihtiyacımı da karşılıyor, ancak ts_transformer_keys_1.keys is not a functionbelgelerdeki adımları tam olarak izlediğimde alıyorum . bunun bir çözümü var mı?
Hasitha Shan

Düzgün! Dinamik tipte bir parametre alacak şekilde genişletilebileceğini düşünüyor musunuz (benioku dosyasındaki not 2)?
kenshin

@HasithaShan dokümanlara yakından bakın - paketin çalışması için TypeScript derleyici API'sini kullanmanız gerekir
Yaroslav Bai

3
Ne yazık ki, paket bozuldu, ne yaparsam yapayım her zaman alıyorumts_transformer_keys_1.keys is not a function
fr1sk

20

Aşağıdakiler, anahtarları kendi başınıza listelemenizi gerektirir, ancak en azından TypeScript uygulayacak IUserProfileve IUserProfileKeysaynı anahtarlara sahip olacaktır ( Required<T>TypeScript 2.8'de eklenmiştir ):

export interface IUserProfile  {
  id: string;
  name: string;
};
type KeysEnum<T> = { [P in keyof Required<T>]: true };
const IUserProfileKeys: KeysEnum<IUserProfile> = {
  id: true,
  name: true,
};

Oldukça harika bir numara. Artık tüm anahtarlarını uygulamak IUserProfileve bunları yapılandırmaktan çıkarmak kolay olacaktır IUserProfileKeys. Bu tam olarak aradığım şey. Artık tüm arayüzlerimi sınıflara dönüştürmeme gerek yok.
Anddo

17

Benzer bir problemim vardı, hem bir arayüze hem de bir nesneye sahip olmak istediğim dev bir özellikler listesine sahiptim.

NOT: Özellikleri iki kez yazmak (klavye ile yazmak) istemedim! Sadece KURU.


Burada dikkat edilmesi gereken bir nokta, arabirimler derleme zamanında zorlanan türlerdir, ancak nesneler çoğunlukla çalışma zamanıdır. ( Kaynak )

@Derek'in başka bir cevapta bahsettiği gibi , arayüz ve nesnenin ortak paydası, hem bir türe hem de bir değere hizmet eden bir sınıf olabilir .

Dolayısıyla TL; DR, aşağıdaki kod parçası ihtiyaçları karşılamalıdır:

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

( İşte daha fazla oynamak için Typescript Playground'daki yukarıdaki kod.)

PS. Sınıftaki özelliklere başlangıç ​​değerleri atamak ve türe bağlı kalmak istemiyorsanız, yapıcı hilesini yapabilirsiniz:

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 Playground'da Yapıcı Trick .


propsArrayGerçi anahtarlarını başlatıldı yalnızca erişilebilir.
denkquer

@Denkquer "başlatılmış" derken ne demek istediğini anlamıyorum. İlk örnekte, propsArrayönce mevcut olan tableInstanceo örneğe çalışma öncesinde kadar net, ne demek buysa. Bununla birlikte, atanmış sahte değerlere atıfta bulunuyorsanız, MyTableClassbunlar sadece özelliklerin "türünü" kısaca belirtmek için oradadır. Onları istemiyorsanız, PS örneğindeki yapıcı numarasına gidebilirsiniz.
Aidin

1
Benim anlayışıma göre bir değer, herhangi bir değeri olduğunda başlatılır. "Yapıcı numaranız" yanıltıcıdır çünkü MyTableClassikincisiyle değiştiremezsiniz ve propsArrayçalıştırma zamanında başlatılmamış değişkenler ve türler çıkarılırken anahtarları almayı bekleyebilirsiniz . Onlara her zaman bir tür varsayılan değer sağlamanız gerekir. Onları başlatmanın undefineden iyi yaklaşım olduğunu buldum .
denkquer

1
@Aidin çözümünüz için teşekkür ederim. Ayrıca parametrelerin yeniden başlatılmasını önleyebilir miyim diye merak ediyorum. Yapıcı hilesini kullanırsam, artık MyTableClass'ı genişleten bir arayüz oluşturamıyorum .. Daktilo oyun alanı bağlantısındaki kurucu numaranız boş olsa da
Flion

1
@Flion, fark ettiğin için teşekkürler. Yapıcı numarası için oyun alanı bağlantısını güncelledim. Şimdi çalışıp çalışmadığına bakın.
Aidin

11

Belki çok geç, ancak typcript'in 2.1 sürümünde şu şekilde kullanabilirsiniz 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

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


Cevap için teşekkürler, ancak birinin arayüzden statik olarak oluşturulmuş Türleri kullanmasına yardımcı olup olmadığından emin değilim. IMHO, çoğu durumda arayüzleri / türleri birbirinin yerine kullanabiliriz. Ayrıca bu, birden çok arayüz için manuel olarak türlerin oluşturulmasını gerektirir. Bununla birlikte, birinin bir arayüzden türler çıkarması gerekiyorsa çözüm iyi görünüyor.
Tushar Shukla

9

Bu çalışmalı

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

veya

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

17
Yanlış olduğundan değil, ama burada açık olmak gerekirse, sadece "dizinin değerlerini doğru olmaya zorluyorsunuz". Geliştiricinin bunları manuel olarak iki kez yazması gerekiyor.
Aidin

Aidin'in söylediği doğru olsa da, bazı durumlarda, benim durumum için tam olarak aradığım buydu. Teşekkür ederim.
Daniel

4
Bu, anahtar kopyalarını veya eksik anahtarları engellemez. Beğenvar IMyTable: Array<keyof IMyTable> = ["id", "createdAt", "id"];
ford04

Benim için de aradığım şey buydu çünkü isteğe bağlı olarak anahtarları kabul etmek istiyorum, ancak arayüzde tanımlanan anahtarlardan başka bir şey istemiyorum. Yukarıdaki kodla bunun varsayılan olmasını beklemiyorduk. Sanırım bunun için hala ortak bir TS yoluna ihtiyacımız olacak. Yukarıdaki kod için her durumda teşekkürler!
nikah

8

Güvenli varyantlar

Güvenlik derleme zamanı kontrolleriyle bir arabirimden bir anahtar dizisi veya demeti oluşturmak biraz yaratıcılık gerektirir. Türler çalışma zamanında silinir ve nesne türleri (sırasız, adlandırılmış) desteklenmeyen tekniklere başvurulmadan tuple türlerine (sıralı, adsız) dönüştürülemez .

Diğer cevaplarla karşılaştırma

Burada önerilen varyantların tümü, gibi bir referans nesne türü verilen yinelenen veya eksik tuple öğeleri olması durumunda bir derleme hatasını dikkate alır / tetikler IMyTable. Örneğin bir dizi türü bildirmek (keyof IMyTable)[]bu hataları yakalayamaz.

Ek olarak, belirli bir kitaplık gerektirmezler ( ts-morphgenel bir derleyici sarmalayıcısı olarak düşüneceğim son varyant kullanımları ), bir nesnenin aksine bir tuple türü yayınlarlar (yalnızca ilk çözüm bir dizi oluşturur) veya geniş dizi türü (ile karşılaştırıldığında bu cevaplar ) ve son olarak derslere ihtiyaç duymaz .

Varyant 1: Basit tipli dizi

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

++-otomatik tamamlama -dizisi ile en kolay kılavuz , tuple yok

Oyun alanı

Bir kayıt oluşturmayı sevmiyorsanız, bu alternatife Setve onaylama türlerine bir göz atın .


Varyant 2: Yardımcı işlevli Tuple

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

++-Otomatik tamamlama +-daha gelişmiş, karmaşık türlerle demet kılavuzu

Oyun alanı

Açıklama

createKeysuygun olmayan girdi için bir hata veren fonksiyon parametre tipini ek onaylama tipleriyle birleştirerek derleme zamanı kontrolleri yapar . (keyof IMyTable)[] | [keyof IMyTable]bu , aranan taraftaki bir dizi yerine bir dizinin çıkarımını zorlamanın "kara büyü" yoludur . Alternatif olarak, sabit iddiaları /as const arayan tarafından kullanabilirsiniz.

CheckMissingkontroller, Tanahtarları kaçırırsa 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"]

Not: T & "Error: missing keys"sadece güzel IDE hataları içindir. Sen de yazabilirsin never. CheckDuplicatesçift ​​tuple öğelerini kontrol eder:

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

Not: Tuplelardaki benzersiz öğe kontrolleri hakkında daha fazla bilgi bu yayında . İle TS 4.1 , biz de hata dizesi eksik tuşları adlandırabilirsiniz - bakmak bu Oyun .


Varyant 3: Özyinelemeli tür

TypeScript 4.1 sürümüyle birlikte, potansiyel olarak burada da kullanılabilecek koşullu özyinelemeli türleri resmi olarak destekler . Tür hesaplaması, kombinasyon karmaşıklığı nedeniyle pahalıdır - performans 5-6'dan fazla öğe için büyük ölçüde azalır. Bu alternatifi eksiksizlik için listeliyorum ( 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"] // ✔

++-otomatik tamamlama ile tuple kılavuzu +yardımcı fonksiyon --performansı yok


Varyant 4: Kod oluşturucu / TS derleyici API'si

ts-morph , orijinal TS derleyici API'sine biraz daha basit bir sarmalayıcı alternatifi olduğundan burada seçilmiştir . Elbette, derleyici API'sini doğrudan da kullanabilirsiniz. Jeneratör koduna bakalım:

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

Bu dosyayı derleyip çalıştırdıktan sonra aşağıdaki içeriğe sahip tsc && node dist/mybuildstep.jsbir dosya ./src/generated/IMyTable-keys.tsoluşturulur:

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

+otomatik olarak oluşturma solüsyonu +birden çok özellik için ölçeklenebilir +bir yardımcı işlev +demet -ilave yapı adım -derleyici API ile benzerlik ihtiyacı


6

IMyTableArayüzde olduğu gibi tanımlamak yerine, onu bir sınıf olarak tanımlamayı deneyin. Daktilo yazısında arayüz gibi bir sınıfı kullanabilirsiniz.

Yani örneğiniz için, sınıfınızı şu şekilde tanımlayın / oluşturun:

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

Arayüz olarak kullanın:

export class SomeTable implements IMyTable {
    ...
}

Anahtarları alın:

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

5

Arayüzünüzü uygulayan bir sınıf oluşturmanız, onu başlatmanız ve ardından Object.keys(yourObject)özellikleri almak için kullanmanız gerekecektir.

export class YourClass implements IMyTable {
    ...
}

sonra

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

Benim durumumda çalışmıyor, arayüzün bu özelliklerini listelemem gerekiyor ama istediğim bu değil mi? Arayüzün adı dinamik olarak geliyor ve sonra özelliklerini belirlemem gerekiyor
Tushar Shukla

Bu bir hata oluşturur (v2.8.3): Cannot extend an interface […]. Did you mean 'implements'?Ancak implementsbunun yerine kullanmak , arabirimin manuel olarak kopyalanmasını gerektirir, bu tam olarak istemediğim şeydir.
jacob

@jacob üzgünüm, olmalıydı implementsve cevabımı güncelledim. @ Basarat'ın belirttiği gibi, arayüzler çalışma zamanında mevcut değildir, bu yüzden tek yol onu bir sınıf olarak uygulamaktır.
Dan Def

Arayüz yerine sınıf kullanmak mı istiyorsunuz? Maalesef arayüz 3. partiden ( @types/react) geldiği için yapamıyorum . Bunları manuel olarak kopyaladım, ancak bu pek geleceğe yönelik değil 😪 Yaşam döngüsü olmayan yöntemleri (zaten bağlı olan) dinamik olarak bağlamaya çalışıyorum, ancak React.Component (sınıf) üzerinde bildirilmemişler.
jacob

Hayır, 3. parti arayüzünüzü uygulayan ve çalışma zamanında bu sınıfın özelliklerini alan bir sınıf oluşturmaktan bahsediyorum.
Dan Def

3

Yapamam. Çalışma zamanında arayüzler mevcut değil.

geçici çözüm

Object.keysTüründe bir değişken oluşturun ve üzerinde kullanın 🌹


1
Bunun gibi mi demek istiyorsun:var abc: IMyTable = {}; Object.keys(abc).forEach((key) => {console.log(key)});
Tushar Shukla

4
Hayır, çünkü bu nesnenin üzerinde anahtar yok. Arayüz, TypeScript'in kullandığı ancak JavaScript'te buharlaşan bir şeydir, bu nedenle herhangi bir "yansıma" veya "kesişim" hakkında bilgi verecek hiçbir bilgi kalmaz. JavaScript'in tüm bildiği boş bir nesne değişmezi olduğudur. Tek umudunuz, TypeScript'in kaynak koduna arayüzdeki tüm anahtarları içeren bir dizi veya nesne oluşturmanın bir yolunu içermesini beklemektir (veya bunu talep etmektir). Veya, Dan Def'in dediği gibi, bir sınıfı kullanabiliyorsanız, her durumda özellik şeklinde tanımlanan anahtarlara sahip olacaksınız.
Jesper

17
Bu işe yaramazsa, bu yanıta neden olumlu oylar veriliyor?
dawez

2
olumsuz oy nedeni: null yapılabilir değerler için çalışmadığından bahsetme
TamusJRoyce

2
Sonuçta bu harika bir çözüm değil çünkü değerleri sağlamanız gerekiyor. Muhtemelen anahtarların bir listesini tutmaktan daha iyidir.
Daniel Thompson

0

Bu zordu! Yardımlarınız için herkese teşekkür ederim.

Benim ihtiyacım mocha / chai komut dosyasını basitleştirmek için bir dizi dizge olarak bir arayüzün anahtarlarını almaktı. Uygulamada kullanmakla ilgilenmiyorum (henüz), bu nedenle ts dosyalarının oluşturulması gerekmiyordu. Yardım için ford04 sayesinde , yukarıdaki çözümü çok yardımcı oldu ve mükemmel çalışıyor, HİÇBİR derleyici hackleri. İşte değiştirilen kod:

Seçenek 2: TS derleyici API'sine (ts-morph) dayalı kod oluşturucu

Düğüm Modülü

npm install --save-dev ts-morph

keys.ts

NOT : Bu, tüm ts dosyalarının ./src'nin kök dizininde bulunduğunu ve alt klasör olmadığını varsayar, buna göre ayarlayın

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;

kullanım

import keys from "./keys";

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

console.log(myKeys)

-1

Yapamazsın. Çalışma zamanında arayüzler yoktur ( @ basarat'ın söylediği gibi ).

Şimdi aşağıdakilerle çalışıyorum:

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

Bir sürü modeliniz olduğunu hayal edin ve bunu herkes için yapın ... çok pahalı bir zaman.
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);

1
Typcript sözdizimi çalışma zamanında mevcut olmadığından bu kod çalışmayacaktır. Bu kodu typcript oyun alanında kontrol ederseniz, JavaScript'i derleyen tek şeyin console.log(Tes.id)elbette 'Yakalanmamış Referans Hatası: Tes tanımlanmadı' hatası olacağını fark edeceksiniz
Tushar Shukla
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.