स्ट्रिंग की सरणी के रूप में टाइपस्क्रिप्ट इंटरफ़ेस की कुंजी प्राप्त करें


101

मैंने लवफील्ड और उनके संबंधित इंटरफेस में बहुत सी तालिकाएँ रखी हैं कि उनके पास क्या कॉलम हैं।
उदाहरण:

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

मुझे इस इंटरफ़ेस के गुण नामों को इस तरह से सरणी में रखना चाहिए :

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

मैं इंटरफ़ेस के आधार पर IMyTableसीधे ऑब्जेक्ट / सरणी नहीं बना सकता, जो कि चाल करना चाहिए क्योंकि मुझे गतिशील रूप से तालिकाओं के इंटरफ़ेस नाम मिलेंगे। इसलिए मुझे इंटरफ़ेस में इन गुणों पर पुनरावृति करने और इसके लिए एक सरणी प्राप्त करने की आवश्यकता है।

मैं इस परिणाम को कैसे प्राप्त करूं?

जवाबों:


52

टाइपस्क्रिप्ट 2.3 के रूप में (या मुझे 2.4 कहना चाहिए , क्योंकि 2.3 में इस सुविधा में एक बग है जो टाइपस्क्रिप्ट@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 कमांड को निष्पादित करने के बजाय टाइपस्क्रिप्ट ट्रांसफॉर्मेशन एपीआई के साथ उपयोग करना होगा । नहीं है एक मुद्दा कस्टम ट्रांसफार्मर के लिए एक प्लगइन सहायता का अनुरोध।


आपकी प्रतिक्रिया के लिए धन्यवाद, मैंने पहले ही कल इस कस्टम ट्रांसफार्मर को देखा और स्थापित किया था, लेकिन चूंकि यह टाइपस्क्रिप्ट 2.4 का उपयोग करता है, इसलिए अब तक मेरे लिए इसका कोई उपयोग नहीं हुआ है।
तुषार शुक्ला

16
नमस्ते, यह पुस्तकालय वास्तव में मेरी आवश्यकता को पूरा करता है, हालांकि, ts_transformer_keys_1.keys is not a functionजब मैं प्रलेखन में सटीक चरणों का पालन करता हूं तो मुझे मिल रहा है । वहाँ एक समाधान यह है?
हसीता शान

साफ! क्या आपको लगता है कि इसे डायनामिक टाइप पैरामीटर (रीडमी में नोट 2) लेने के लिए बढ़ाया जा सकता है?
केंसिन

@ हसिथासन डॉक्स में बारीकी से देखते हैं - आपको काम करने के लिए ऑर्डर पैकेज में टाइपस्क्रिप्ट कंपाइलर एपीआई का उपयोग करना होगा
यारोस्लाव बाई

2
दुर्भाग्य से, पैकेज टूट गया है, जो कुछ भी मैं करता हूं वह हमेशा हो रहा हैts_transformer_keys_1.keys is not a function
fr1sk

16

निम्नलिखित को आपको अपने दम पर कुंजियों को सूचीबद्ध करने की आवश्यकता होती है, लेकिन कम से कम टाइपस्क्रिप्ट लागू होंगे IUserProfileऔर IUserProfileKeysठीक उसी कुंजी होंगे ( Required<T>टाइपस्क्रिप्ट 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और उन्हें कब्जे से निकालना आसान होगा IUserProfileKeys। यह वही है जिसकी मुझे तलाश है। अब मेरे सभी इंटरफेस को कक्षाओं में बदलने की आवश्यकता नहीं है।
एंड्डो

12

मेरे पास एक समान समस्या थी कि मेरे पास गुणों की एक विशाल सूची थी जिसे मैं एक इंटरफ़ेस और इसके बाहर एक ऑब्जेक्ट दोनों के लिए चाहता था।

नोट: मैं दो बार (कीबोर्ड के साथ टाइप) गुणों को लिखना नहीं चाहता था! बस DRY।


यहाँ एक बात ध्यान देने वाली है, संकलन-समय पर इंटरफेस को लागू किया जाता है, जबकि ऑब्जेक्ट ज्यादातर रन-टाइम होते हैं। ( स्रोत )

जैसा कि @derek ने एक अन्य उत्तर में उल्लेख किया है , इंटरफ़ेस और ऑब्जेक्ट का सामान्य भाजक एक ऐसा वर्ग हो सकता है जो एक प्रकार और एक मूल्य दोनों परोसता है ।

तो, टीएल; डीआर, कोड का निम्नलिखित टुकड़ा जरूरतों को पूरा करना चाहिए:

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

( यहाँ टाइपस्क्रिप्ट गेम ग्राउंड में उपरोक्त कोड अधिक खेलने के लिए है)

पुनश्च। यदि आप वर्ग में गुणों के लिए प्रारंभिक मान निर्दिष्ट नहीं करना चाहते हैं, और प्रकार के साथ रहना चाहते हैं, तो आप कंस्ट्रक्टर ट्रिक कर सकते हैं:

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

टाइपस्क्रिप्ट गेम ग्राउंड में कंस्ट्रक्टर ट्रिक


propsArrayजब आप कुंजी हालांकि प्रारंभ ही उपलब्ध है।
denkquer

मुझे "इनिशियलाइज़्ड" @denkquer से क्या मतलब है। पहले उदाहरण में, यदि आप जो चाहते हैं उससे propsArrayपहले उपलब्ध tableInstanceहै, तो उदाहरण के प्रारंभ से पहले स्पष्ट रूप से। हालांकि, यदि आप में निर्दिष्ट नकली मानों का जिक्र कर रहे हैं, तो MyTableClassवे बस कुछ ही समय में गुणों के "प्रकार" का संकेत देते हैं। यदि आप उन्हें नहीं चाहते हैं, तो आप पीएस उदाहरण में कंस्ट्रक्टर चाल के साथ जा सकते हैं।
एडिन

1
मेरी समझ में किसी मूल्य का आरंभ होने पर उसका मूल्य निर्धारित किया जाता है। आपकी "कंस्ट्रक्टर ट्रिक" भ्रामक है क्योंकि आप केवल MyTableClassबाद वाले के साथ प्रतिस्थापित नहीं कर सकते हैं और असमान किए गए संस्करण में कुंजी प्राप्त करने की अपेक्षा करते propsArrayहैं और प्रकार रनटाइम पर छीन लिए जाते हैं। आपको हमेशा उन्हें किसी प्रकार के डिफ़ॉल्ट मूल्य के साथ आपूर्ति करना होगा। मैंने पाया है कि उनके साथ शुरुआत करना undefinedसबसे अच्छा तरीका है।
denkquer

@ अचानक खेद है, मेरी कंस्ट्रक्टर चाल गायब थी readonlyऔर पैरामैटर ?पर थी। मैंने अभी उसे अपडेट किया है। इशारा करने के लिए धन्यवाद। अब मुझे लगता है कि यह आपके द्वारा बताए गए तरीके से काम करता है और यह भ्रामक नहीं है। :)
एडिन

@ ऐडिन आपके समाधान के लिए धन्यवाद। मैं भी सोच रहा हूं कि क्या मैं मापदंडों के प्रारंभिककरण से बच सकता हूं। अगर मैं कंस्ट्रक्टर ट्रिक का उपयोग करता हूं, तो मैं अब एक इंटरफ़ेस नहीं बना सकता हूं जो MyTableClass का विस्तार करता है .. टाइपस्क्रिप्ट प्लेग्राउंड लिंक में आपका कंस्ट्रक्टर ट्रिक हालांकि खाली है
फ्लिओन

9

यह काम करना चाहिए

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

या

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

10
ऐसा नहीं है कि यह गलत है, लेकिन यहां स्पष्ट होने के लिए आप सही होने के लिए "सरणी के मूल्यों को लागू कर रहे हैं"। डेवलपर को मैन्युअल रूप से उन्हें दो बार लिखने की आवश्यकता है।
एडिन

जबकि एडिन ने जो कहा वह सच हो सकता है, कुछ मामलों के लिए, यह वही था जो मैं अपने मामले के लिए देख रहा था। धन्यवाद।
डैनियल

4
यह कुंजी डुप्लिकेट या लापता कुंजियों को नहीं रोकेगा। जैसेvar IMyTable: Array<keyof IMyTable> = ["id", "createdAt", "id"];
ford04

मेरे लिए यह वही था जो मैं देख रहा था क्योंकि मैं वैकल्पिक रूप से चाबियों को स्वीकार करना चाहता हूं, लेकिन इंटरफ़ेस में परिभाषित कुंजी से ज्यादा कुछ नहीं। उपरोक्त कोड के साथ डिफ़ॉल्ट होने की उम्मीद नहीं की थी। मुझे लगता है कि इसके लिए हमें अभी भी एक सामान्य टीएस तरीके की आवश्यकता होगी। उपरोक्त कोड के लिए किसी भी मामले में धन्यवाद!
निकोज

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());

6

शायद यह बहुत देर हो चुकी है, लेकिन टाइपस्क्रिप्ट 2.1 के संस्करण में आप 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


उत्तर के लिए धन्यवाद, लेकिन यकीन नहीं है कि यह किसी को इंटरफ़ेस से बनाए गए प्रकारों का उपयोग करने में मदद करता है। IMHO, हम ज्यादातर मामलों में इंटरफेस / प्रकार का उपयोग कर सकते हैं। साथ ही इसके लिए कई इंटरफेस के लिए मैन्युअल निर्माण की आवश्यकता होगी। हालाँकि समाधान अच्छा लगता है अगर किसी को केवल एक इंटरफ़ेस से बाहर निकलने की आवश्यकता होती है।
तुषार शुक्ला

5

आपको एक वर्ग बनाने की आवश्यकता होगी जो आपके इंटरफ़ेस को लागू करता है, इसे तुरंत करता है और फिर Object.keys(yourObject)गुण प्राप्त करने के लिए उपयोग करता है।

export class YourClass implements IMyTable {
    ...
}

फिर

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

मेरे मामले में काम नहीं करता है, मुझे इंटरफ़ेस के उन गुणों को सूचीबद्ध करना होगा लेकिन यह वह नहीं है जो मैं चाहता हूं? इंटरफ़ेस का नाम गतिशील रूप से आता है और फिर मुझे इसके गुणों का निर्धारण करना है
तुषार शुक्ला

यह एक त्रुटि (v2.8.3) उत्पन्न करता है: Cannot extend an interface […]. Did you mean 'implements'?हालाँकि, implementsइसके बजाय इंटरफ़ेस को मैन्युअल रूप से कॉपी करने की आवश्यकता होती है, जो कि वास्तव में मुझे नहीं चाहिए।
जाकोब

@ स्कोब सॉरी, यह होना चाहिए था implementsऔर मैंने अपना उत्तर अपडेट कर दिया है। जैसा कि @basarat ने कहा है, इंटरफेस रनटाइम पर मौजूद नहीं होते हैं इसलिए एक वर्ग के रूप में इसे लागू करने का एकमात्र तरीका है।
डैन डेफ

आप एक अंतरफलक के बजाय एक वर्ग का उपयोग मतलब है? दुर्भाग्य से मैं इंटरफ़ेस के रूप में 3-पार्टी ( @types/react) से नहीं आता है । मैंने उन्हें मैन्युअल रूप से कॉपी किया था, लेकिन यह शायद ही भविष्य में प्रूफ है, मैं गैर-जीवनचक्र विधियों (जो पहले से ही बाध्य है) को गतिशील रूप से बांधने की कोशिश कर रहा हूं, लेकिन वे React.Component (वर्ग) पर घोषित नहीं किए जाते हैं।
जाकोब

नहीं, मेरा मतलब है कि एक वर्ग बनाएं जो आपके तीसरे पक्ष के इंटरफ़ेस को लागू करता है और रनटाइम पर उस वर्ग के गुणों को प्राप्त करता है।
डैन डेफ

4

नहीं कर सकते। रनटाइम पर इंटरफेस मौजूद नहीं हैं।

वैकल्पिक हल

प्रकार का एक वैरिएबल बनाएं और Object.keysउस पर उपयोग करें type


1
क्या आपका मतलब इस तरह है:var abc: IMyTable = {}; Object.keys(abc).forEach((key) => {console.log(key)});
तुषार शुक्ला

4
नहीं, क्योंकि उस वस्तु की कोई कुंजी नहीं है। एक इंटरफ़ेस कुछ ऐसा है जिसे टाइपस्क्रिप्ट उपयोग करता है लेकिन जावास्क्रिप्ट में वाष्पित होता है, इसलिए किसी भी "प्रतिबिंब" या "प्रतिच्छेदन" को सूचित करने के लिए कोई जानकारी नहीं बची है। सभी जावास्क्रिप्ट को पता है कि एक खाली वस्तु शाब्दिक है। आपकी एकमात्र आशा है कि (या अनुरोध करें ) टाइपस्क्रिप्ट में स्रोत कोड में इंटरफ़ेस में सभी कुंजियों के साथ एक सरणी या ऑब्जेक्ट उत्पन्न करने का एक तरीका शामिल है। या, जैसा कि डैन डेफ कहते हैं, यदि आप एक वर्ग का उपयोग कर सकते हैं, तो आपके पास हर उदाहरण में गुणों के रूप में परिभाषित कीज़ होंगी ..
जेस्पर

1
आपको इस पंक्ति पर टाइपस्क्रिप्ट द्वारा एक त्रुटि भी मिलेगी: var abc: IMyTable = {}क्योंकि खाली ऑब्जेक्ट शाब्दिक उस इंटरफ़ेस के आकार के अनुरूप नहीं है।
जेस्पर

16
यदि यह काम नहीं करता है, तो इस उत्तर पर उत्थान क्यों होते हैं?
दाऊजी

1
नीच कारण: कोई उल्लेख नहीं है कि यह
अशक्त

3

इंटरफ़ेस से कुंजियाँ सरणी बनाने का कोई आसान तरीका नहीं है। प्रकार रन-टाइम पर मिटा दिए जाते हैं और ऑब्जेक्ट प्रकार (अनियंत्रित, नाम) को कुछ प्रकार की हैक के बिना टुपल प्रकार (ऑर्डर किए गए, अनाम) में परिवर्तित नहीं किया जा सकता है।


विकल्प 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")[]

(+) सरल (-) ऐरर रिटर्न टाइप, नो टपल (+ -) मैन्युअल लेखन ऑटो-कम्प्लीट करने के साथ

विस्तार: हम कल्पना करने की कोशिश कर सकते हैं और टपल उत्पन्न करने के लिए पुनरावर्ती प्रकारों का उपयोग कर सकते हैं। यह केवल मेरे लिए प्रॉप्स के एक जोड़े (~ 5,6) के लिए काम करता था जब तक कि प्रदर्शन में बड़े पैमाने पर गिरावट नहीं हुई। डीप नेस्टेड रिकर्सिव टाइप भी आधिकारिक तौर पर टीएस द्वारा समर्थित नहीं हैं - मैं इस उदाहरण को पूर्णता के लिए यहां सूचीबद्ध करता हूं ।


विकल्प 2: टीएस कंपाइलर एपीआई ( 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

इस फ़ाइल को हम संकलित करने और चलाने के बाद tsc && node dist/mybuildstep.js, ./src/generated/IMyTable-keys.tsनिम्न सामग्री वाली एक फ़ाइल उत्पन्न होती है:

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

(+) स्वचालित समाधान (+) सटीक टपल प्रकार (-) के लिए बिल्ड-स्टेप की आवश्यकता होती है


पुनश्च: मैंने चुना है ts-morph, क्योंकि यह मूल टीएस कंपाइलर एपीआई का एक सरल विकल्प है।


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

कल्पना कीजिए कि बस बहुत सारे मॉडल हैं, और यह हर किसी के लिए है ... यह इतना महंगा समय है।
विलियम क्यूवरो

-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
यह कोड काम नहीं करेगा क्योंकि टाइप-सी सिंटैक्स रन-टाइम पर उपलब्ध नहीं है। यदि आप इस कोड को टाइपस्क्रिप्ट खेल के मैदान पर जांचते हैं, तो आप देखेंगे कि केवल एक चीज जो जावास्क्रिप्ट के लिए संकलित होती है console.log(Tes.id), वह निश्चित रूप से त्रुटि होगी 'अनट्रेडेड रेफ़ररर: टेस को परिभाषित नहीं किया गया है'
तुषार शुक्ला
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.