Typescript: أنواع السلاسل الحرفية كأنواع معلمات توقيع الفهرس؟

تم إنشاؤها على ١٦ نوفمبر ٢٠١٥  ·  71تعليقات  ·  مصدر: microsoft/TypeScript

وفقًا لـ https://github.com/Microsoft/TypeScript/pull/5185 ، يمكن تخصيص الأنواع الحرفية للسلسلة لسلاسل عادية. بالنظر إلى تعريف النوع التالي:

type NodeType = "IfStatement"
              | "WhileStatement"
              | "ForStatement";

كلا التعيينات صالحة:

const nodeType1: NodeType = "IfStatement";
const nodeType2: string = nodeType1;

ومع ذلك ، لا يمكن حاليًا استخدام الأنواع الحرفية للسلسلة كأنواع معلمات توقيع الفهرس. لذلك يشكو المترجم من الكود التالي:

let keywords: { [index: NodeType]: string } = {
    "IfStatement": "if",
    "WhileStatement": "while",
    "ForStatement": "for"
};

// error TS1023: An index signature parameter type must be 'string' or 'number'.

ألا يجب دعم هذا السيناريو ، بالنظر إلى أن الأنواع الحرفية للسلسلة قابلة للتخصيص للسلاسل؟

@ ccDanielRosenwasser

Literal Types Fixed Suggestion

التعليق الأكثر فائدة

يجب أن يكون هذا ممكنًا الآن باستخدام الأنواع المعينة

لذلك يمكنك كتابة المثال في المثال الأصلي على النحو التالي:

type NodeType = "IfStatement"
              | "WhileStatement"
              | "ForStatement";

let keywords: {[P in NodeType]: string };

keywords = {
      "IfStatement": "if",
      "WhileStatement": "while",
      "ForStatement": "for"
};  // OK

keywords.ifStatement; // OK


keywords = {  "another": "wrong" } // Error

ال 71 كومينتر

هذه المشكلة هي نوع من "ثنائي" رقم 2491 ، وإذا تعاملنا مع هذا ، فقد نرغب في إعادة النظر في هذه المشكلة.

هذا شيء طرحه معيweswigham عدة مرات. إحدى المشكلات المتعلقة بهذا النوع من الأشياء هي أن undefined و null قيمتان محتملتان لكل نوع حرفي للسلسلة. هذا ليس شيئًا نعتبره مشكلة كبيرة في تواقيع الفهرس الرقمية (فهم يعانون من نفس المشكلة) ، ولكن بطريقة ما أشعر بأن هذا قد يكون أكثر تضليلًا. بشكل عام ، لا يبدو أن هذا سبب قوي بما يكفي لرفضه بالرغم من ذلك.

أود أن أجادل من منظور آخر لأوضح لماذا أعتقد أن هذه الميزة تستحق التنفيذ. ضع في اعتبارك نوع NodeType أعلاه كما هو موجود في محلل مثل Esprima . تهدف الأنواع الحرفية للسلسلة إلى وصف مجموعة من قيم السلسلة المحدودة الممكنة ، وإدراج جميع أنواع العبارات / التعبيرات المتاحة للمحلل اللغوي هو حالة استخدام مثالية لذلك. إذا اضطررت إلى استخدام سلاسل عادية كأنواع معلمات توقيع الفهرس ، فسأفقد أمان النوع الذي صنعت من أجله الأنواع الحرفية للسلسلة من أجل إعطائي في المقام الأول.

أوافق ، القضايا مرتبطة. دعونا نرى ما إذا كان بإمكاننا إيجاد حل هنا.

لهذا النوع من الأشياء ، لا تحتاج بالضرورة إلى توقيع فهرس سلسلة حرفية - الطريقة البديلة هي فقط استخدام اسم الخاصية المناسب عند الفهرسة بأنواع حرفية للسلسلة. إنه أحد الأسئلة المفتوحة على # 5185:

بالنظر إلى أن لدينا المحتوى النصي لنوع حرفي للسلسلة ، يمكننا بشكل معقول إجراء عمليات بحث عن الخاصية في كائن. أعتقد أن هذا يستحق النظر. سيكون هذا أكثر فائدة إذا أجرينا تضييقًا.

المطابقة رقم 2491

jhlange لا أعتقد ذلك تمامًا. بينما نتلاعب بفكرة توحيد الأنواع الحرفية مع التعدادات ، فإن هذا الأمر مختلف إلى حد ما في الوقت الحالي. إذا انتهى الأمر بجمعهم معًا ، فسيصبح # 2491 أكثر صلة بالمناقشة.

+1

هناك نقاش وثيق الصلة بهذا الموضوع في المشكلة المكررة بالضبط # 7656. أقترح التحقق من بعض المعلومات هناك.

من أجل تنفيذ شيء من هذا القبيل حقًا ، يجب أولاً التحقق من نوع معلمة توقيع الفهرس مقابل النوع المتخصص المقيدة به ، على سبيل المثال ، قد يرفض { [key: number]: any } string أو Symbol تستخدم كمفتاح. حاليا هذا في غير المطبق. برجاء مشاهدة هذا التعليق في # 7660 والمشاركة في المناقشة.

هذا التعليق في # 7660 وثيق الصلة بالموضوع هنا ، على الرغم من أنه يشير إلى القضية الأكثر عمومية حول مدى دقة فحص مفاتيح توقيع الفهرسة.

+1

نشر christyharagan بعض الأمثلة الأكثر تحفيزًا في # 8336.

كما يشير malibuzios ، سيكون membersof (# 7722) ميزة رائعة للاقتران مع هذا ، ولكن أعتقد أنه يجب اعتبار ذلك متعامدًا ولا تتم مناقشته هنا .

إضافة بعض الأمثلة إلى ما قاله mariusschulz

يصف مجموعة من قيم السلسلة المحدودة الممكنة

قد تكون القدرة على تعداد مجموعة قيم السلسلة الممكنة مفيدة للغاية. على سبيل المثال ، معطى:

type DogName = "spike" | "brodie" | "buttercup";

interface DogMap {
  [index: DogName ]: Dog
}

let dogs: DogMap = { ... };

... سيكون من الرائع حقًا أن تكون قادرًا على القيام بما يلي:

let dogNames: DogName[] = Object.keys(dogs);
// or
for (let dogName:DogName in dogs) {
  dogs[dogName].bark();
}

فيما يلي مثال آخر على كيف سيكون الأمر أجمل إذا تمكنا من استخدام الأنواع الحرفية للسلسلة كمفاتيح فهرسة.

ضع في اعتبارك هذه الواجهة:

interface IPerson {
  getFullName(): string;
}

إذا اختبرت هذا ، فقد تكتب شيئًا كالتالي:

let person: IPerson = jasmine.createSpyObj('person', ['getFullName']);

أو

let person = jasmine.createSpyObj<IPerson>('person', ['getFullName']);

لكن افترض أنك أخطأت وكتبت هذا بدلاً من ذلك:

let person: IPerson = jasmine.createSpyObj('person', ['foo']);

لا توجد حاليًا طريقة تتيح لـ TS إخبارك بوجود مشكلة. ولكن إذا كان تعريف النوع يبدو كالتالي:

function createSpyObj<T extends string>(baseName: string, methodNames: T[]): {[key: T]: any};

ثم يمكن أن يكون النوع المستنتج شيئًا مثل { 'foo': any } مما قد يتسبب في حدوث خطأ.

الآن ، لست متأكدًا الآن مما إذا كان المترجم سيستنتج نوع T كنوع سلسلة حرفية لشيء مثل هذا:

let person: IPerson = jasmine.createSpyObj('person', ['foo', 'bar']); //ERROR

من الناحية المثالية ، ستكون هناك طريقة لمساعدة TS على استنتاج T العام كـ 'foo' | 'bar' بحيث يمكن أن يكون نوع الإرجاع المستنتج { 'foo': any; 'bar': any; } .

+1

حالة استخدام أخرى: تمثل علامات لغة BCP47 الصالحة كمفاتيح لخرائط اللغة ، على سبيل المثال:

{
  "@context":
  {
    "occupation": { "@id": "ex:occupation", "@container": "@language" }
  },
  "name": "Yagyū Muneyoshi",
  "occupation":
  {
    "ja": "忍者",
    "en": "Ninja",
    "cs": "Nindža"
  }
}

قد يكون من الصعب تمثيل ذلك ، نظرًا لوجود العديد من علامات اللغة الصالحة. لا يقتصر الأمر على المعرفات المكونة من حرفين فقط ، بل يمكن أن تتضمن أشياء مثل المنطقة ، على سبيل المثال:

  {
    "en-US": "elevator",
    "en-GB": "lift"
  }

يحتوي Flow على هذه الميزة ومن الجيد جدًا امتلاكه.

إليك حالة استخدام أخرى: كتابة وظيفة الشرطة السفلية mapObject ستحافظ بشكل مثالي على المعرفة حول مجموعة المفاتيح الممكنة ، بحيث:

// foo conforms to type { [key: 'x'|'y']: string }
const foo = {
  x: 'hello',
  y: 'world',
}

// type of bar is inferred to be { [key: 'x'|'y']: number }
const bar = mapObject(foo, s => s.length)

استخدام آخر ، المفتاح الحرفي للسلسلة كقيود للكائن.

function prop<K extends string>(name: K): <S extends { [K]: any }>(s: S) => S[K] {
    return s => s[name];
}

prop('myProp')({myProp: 123})

يستخدم هذا النمط بكثرة في مكتبة وظيفية مثل رامبدا.

بشكل عام ، يتيح المفتاح الحرفي للسلسلة بناء نوع ديناميكي.

يوفر استخدام الأنواع الحرفية (وبشكل أكثر تحديدًا ، معلمات النوع العام المقيدة للأنواع الحرفية) مجموعة شاملة صارمة من الوظائف المتوفرة في # 11929. بشكل خاص:

function get<T, K extends keyof T>(obj: T, name: K): T[K] {
    return obj[name];
}
function set<T, K extends keyof T>(obj: T, name: K, value: T[K]): void {
    obj[name] = value;
}

يعادل ، على الرغم من إتقانه بالتأكيد:

function get<K extends (string | number), V, K1 extends K>(obj: { [key: K]: V }, name: K1): V {
    return obj[name];
}
function set<K extends (string | number), V, K1 extends K, V1 extends V>(obj: { [key: K]: V }, name: K1, value: V1): void {
    obj[name] = value;
}

لاحظ أن معلمات النوع الإضافية K1 extends K, V1 extends V ضرورية نظرًا لحقيقة أنه لا يمكن تعطيل استدلال معلمة النوع العام بشكل انتقائي ، لذلك بدونها K و V سيعود في النهاية إلى ( string | number إن لم يكن {} ) و ( {} أو any ) على التوالي.

يجب أن يكون هذا ممكنًا الآن باستخدام الأنواع المعينة

لذلك يمكنك كتابة المثال في المثال الأصلي على النحو التالي:

type NodeType = "IfStatement"
              | "WhileStatement"
              | "ForStatement";

let keywords: {[P in NodeType]: string };

keywords = {
      "IfStatement": "if",
      "WhileStatement": "while",
      "ForStatement": "for"
};  // OK

keywords.ifStatement; // OK


keywords = {  "another": "wrong" } // Error

لماذا لا تعمل مع الفئات والواجهات؟ على سبيل المثال

interface Keywords {
    [P in NodeType]: string;
}

ملعب مطبعي

mhegazy هذا لا يعمل مع الأنواع ذات المعلمات أيضًا.

interface Component<Model, Action> {
  stateful: {
    init: () => Model,
    actions: {
      [Key in Action]: ({state}: {state: Model}, event: any) => Model
    }
  }
}

لماذا لا تعمل مع الفئات والواجهات؟ على سبيل المثال

الأنواع المعينة مدعومة فقط في الأنواع المستعارة. تحتوي الواجهات والفئات على دلالات مدمجة لا تتناسب مع الكتابة المعينة.

ccorcos هل تقصد [Key in keyof Action] ؟

أم أن Action سلسلة؟ إذا كان الأمر كذلك ، فقم بإضافة قيد لـ Action extends string .

لقد جربت هذا ، لكنني لم أفلح.

  type ItemName = "A" | "B" | "C";
  namespace ItemName {
    export const A: ItemName = "A";
    export const B: ItemName= "B";
    export const C: ItemName = "C";
  };

  const itemComments: {[name in ItemName]: string} = {
    [ItemName.A]: "Good",
    [ItemName.B]: "So-so",
    [ItemName.C]: "Bad",
  };

الخطأ يقول:

[ts]
Type '{ [x: string]: string; }' is not assignable to type '{ A: string; B: string; C: string; }'.
  Property 'A' is missing in type '{ [x: string]: string; }'.

أعتقد أنه يجب التعرف على [ItemName.A] كنوع ItemName ، وليس سلسلة.

أعتقد أنه يجب التعرف على [ItemName.A] كنوع ItemName ، وليس سلسلة.

هذه هي نفس المشكلة التي تتبعها https://github.com/Microsoft/TypeScript/issues/5579.

mhegazy إذا لم ينجح ذلك في الواجهات ، فهل هناك حل مختلف متاح ، أو مشكلة تتبع لهذه الحالة؟

أحاول استخدام هذا في بعض تعريفات الأنواع ، وهو لا يدعم حالتي ، باستخدام الفئات (أو الواجهات):

type ValidKey = "a" | "b" | "c";

declare class MyClass {
  [key: k in ValidKey]: string
}

أحصل على "مُهيئ المعلمة مسموح به فقط في وظيفة أو تنفيذ مُنشئ" (وهو ليس خطأً كبيرًا أيضًا ، حتى لو كان هذا غير مدعوم).

اكتشفت أنه من أجل جعل المفاتيح اختيارية ، يمكنك استخدام Partial مع هذا على سبيل المثال

type mapping = Partial<{ [k in "a" | "b"]: string }>

شيء صغير ، لكني كافحت لفترة مع هذا.

ملاحظة: ربما يجب أن أقرأ المواصفات عن كثب ، وهناك أيضًا { [k in "a" | "b"] ? : string } للمفاتيح الاختيارية.

سيكون رائعًا إذا نجح هذا أيضًا مع الأرقام الحرفية:

{ [k in 1 | 2]: string }

بما أن هذا يعمل بالفعل:

{ [k: number]: string }

leoasis يبدو أنني أتذكر أنه عند تقديم الأنواع المعينة لأول مرة ، كانت تدعم حرفية أخرى غير السلاسل ، ولكن تم إزالة هذا الدعم لاحقًا. أتساءل لماذا ...

أي تحديث على هذا؟

إذا فهمت الخيط بشكل صحيح ، فهل من غير الممكن تحديد فئة حسب النوع ، ثم استخدم مفاتيح هذا النوع في توقيع الفهرس ، مثل:

class Form<T> {
    initialValues: {[fieldName: keyof T]: string} = {}
}

MaxGabriel هل تقصد شيئًا كهذا؟

class Form<T> {
  constructor(readonly initialValues: Partial<{ [field in keyof T]: string }> = {}) {}
}

@ karol-majewski أوه هذا يعمل بشكل رائع! شكرا لك :)

لقد جئت إلى هنا عندما واجهت خطأ التعدادات. هذه هي الطريقة التي جعلتها تعمل:

export enum ThreeLetters {
  a = 'a',
  b = 'b',
  c = 'c'
}

const letterToName: {[key in ThreeLetters]: string} = {
  a: 'Ashok',
  b: 'Bobby',
  c: 'Clever'
}

تعد الأنواع الحرفية للسلسلة المتوافقة مع تواقيع الفهرس إحدى الميزات التي افتقدها في TypeScript منذ فترة طويلة.

كثيرًا ما أحدد تعدادات حرفية للسلسلة في مشروعي ، مثل:

type EmployeeType = "contractor" | "permanent";

أو حتى قائمة بالمعلومات الجغرافية أو المتعلقة بالموقع أو المعلومات المتعلقة بالمال ، مثل:

type Currency = "USD" | "GBP" | "EUR";

(كلما تم إصلاح عدد العناصر ، وليس مجرد string ).

بالنظر إلى مجموعة العناصر التي تتضمن خاصية من الأنواع المذكورة أعلاه ، عند التقليل أو التجميع حسب تلك الخاصية ، أتوقع أن أحصل على:

{
  "contractor": ReducedValue...,
  "permanent": ReducedValue...
}

ولا يمكنني تحديد النوع على النحو التالي:

{ [type: EmployeeType]: ReducedType }

لأنه يمكن أن يكون مجرد سلسلة أو رقم. لذا فإن الشيء الوحيد الذي يمكنني فعله هو توسيعه إلى سلسلة ، وهو في الواقع واسع النطاق ، نظرًا لأن "other" ليس جزءًا من EmployeeType .

أتمنى أن تهبط هذه الميزة الصغيرة في TypeScript يومًا ما :) لست متأكدًا حقًا من سبب عدم السماح بالتعداد الحرفي للسلسلة - كونه نوعًا فرعيًا من string - كتوقيع فهرس (و string ، الأوسع واحد هو).

ducin هل يمكنك مساعدتي في فهم حالة الاستخدام الخاصة بك بشكل أفضل؟ يبدو أن ما يلي يعمل كما أتوقع (أنا) :

type EmployeeType = "contractor" | "permanent";

const employees: { [employeeType in EmployeeType]: {} } = {
  "contractor": {}, // ok
  "permanent": {}, // ok
  "volunteer": {} // error
}

niedzielski أشعر بالخجل لأنني لم أعرف أبدًا بهذه الطريقة لتحديد توقيع الفهرس. شكرا لك على هذا التلميح!

راجع للشغل لماذا لا يُسمح بـ { [type: employeeType]: {} } (على سبيل المثال ، in مسموح و : $ غير مسموح به ، ما الفرق)؟

يصنع in نوعًا معينًا ، بينما الآخر هو توقيع فهرس. تختلف الأنواع المعينة قليلاً من حيث أنه لا يمكن أن يكون لها أي خصائص بخلاف عملية التعيين (ولا يمكن استخدامها في الواجهات).

niedzielski ماذا لو أردت أن تكون أي مجموعة فرعية من النوع الحرفي للسلسلة على ما يرام. هل يمكنني القيام بذلك في 2018؟

vehsakul تقصد مثل هذا؟

type MyStringLiteral =
  | "a"
  | "b"
  | "c";

const asRecordType: Partial<Record<MyStringLiteral, any>> = {
    b: 1,
}

const asMappedType: { [property in MyStringLiteral]?: any } = {
    b: 1,
}

@ karol-majewski نعم بالضبط. شكرا. انسحب من عالم TS لبعض الوقت. الآن اسأل أسئلة غبية :)

لست متأكدًا مما إذا كنت أفقد شيئًا ما هنا ، لكنني كنت أتوقع أن يعمل هذا.

type Department = "finance" | "engineering" | "marketing";
type DepartmentMap<TValue> = { [P in Department]: TValue };

let employeesByDepartment: DepartmentMap<string[]> = {
    "finance": ["Karen", "Jack"],
    "engineering": ["Natalie", "Matt"],
    "marketing": ["Michael", "Sally"]
};

for (let department in employeesByDepartment) {
    // Type of department is string, so:
    // Why am I allowed to use it as an indexer on line 13? Shouldn't I only be allowed to index with a Department?
    // Why is it not of type Department?
    let employees: string[] = employeesByDepartment[department];
    assignDepartmentInPayroll(department, employees); // this line does not compile
}

function assignDepartmentInPayroll(department: Department, employees: string[]) {
    // do something
}

لدي شعور بأن هناك طريقة صحيحة للقيام بذلك ، لكن لا يمكنني العثور عليها.

MRayermannMSFT نوع التأكيد (department as Department, employees) يقوم بالمهمة.

في رأيي ، الكود أعلاه صحيح من الناحية المعنوية ، فقط for..in غير قادر على تضييق نوع المفتاح إلى سلسلة حرفية تعداد. تظهر مثل هذه الحالات من عدم التوافق الداخلي في TypeScript ، للأسف. ربما يمكن تحسين هذه المشكلة بسهولة ، mhegazy ؟

راجع https://github.com/Microsoft/TypeScript/pull/12253#issuecomment -263132208 لمعرفة سبب عدم إمكانية استنتاج أن department يحتوي على النوع Department . وجود أنواع دقيقة قد يحل المشكلة؟

نعم ، هذا من شأنه أن يفسر سبب عدم استنتاج النوع. سأبحث في الأنواع الدقيقة ، ducin ، نعم ، يمكنني استخدام as للحصول على نوع العقار ، لكن بالنسبة لي هذا ليس الحل المثالي. شكرًا لك على الاقتراح بالرغم من ذلك ، لأنه ربما يتعين عليّ التعامل معه. 😄

سؤال ذو صلة ، أتساءل لماذا:
منح:

type NodeType = "IfStatement"
              | "WhileStatement"
              | "ForStatement";

الكود أدناه يعمل:

type GoodStuff = {[P in NodeType]?: string } // Note that value type is set to "string"
let keywords: GoodStuff = {
      IfStatement: "if",
      WhileStatement: "while",
      another: "another", // This triggers error as expected
}; 

لكن هذا لا:

type GoodStuff = {[P in NodeType]?: NodeType } // Note that value type is set to "NodeType"
let keywords: GoodStuff = {
      IfStatement: "if",
      WhileStatement: "while",
      another: "another", // This no longer triggers error :(
}; 

@ benjamin21st يبدو أنه يعمل بشكل صحيح. راجع ملعب TypeScript مع تثبيت TypeScript 3.0.1 .

@ karol-majewski آه ~ شكرا! ثم سأضطر إلى إقناع فريقي بالترقية إلى 3.0 : joy:

إذا كان لديك تعداد (مفهرس بالأرقام) ، لكنك تريد أن يحتوي الكائن الخاص بك على مفاتيح سلسلة ، فإليك الحل:

enum NodeEnum {
    IfStatement,
    WhileStatement,
    ForStatement,
}

const keywords: { [index in keyof typeof NodeEnum]: string } = {
    "IfStatement": "if",
    "WhileStatement": "while",
    "ForStatement": "for",
}

في الواقع يمكنك كتابة هذا على النحو التالي:

enum Color {
    red,
    green,
    blue
}

type ColorMap<C extends object, T> = { [P in keyof C]: T };

const colors: ColorMap<typeof Color, string> = {
    red: 'red',
    green: 'green',
    blue: 'blue'
};

فحص نوع بوم!

في الواقع يمكنك كتابة هذا على النحو التالي:

enum Color {
    red,
    green,
    blue
}

type ColorMap<C extends object, T> = { [P in keyof C]: T };

const colors: ColorMap<typeof Color, string> = {
    red: 'red',
    green: 'green',
    blue: 'blue'
};

فحص نوع بوم!

شكرا!!! هذا رائع!!!

تعداد اللون {أحمر ، أخضر ، أزرق} اكتب ColorMap= {[P in keyof C]: T} ؛ ألوان ثابتة: ColorMap= {أحمر: 'أحمر' ، أخضر: 'أخضر' ، أزرق: 'أزرق'} ؛

ليس حقًا ، إذا قمت بتغيير colors ليصبح:

const colors: ColorMap<typeof Color, string> = {
    red: 'green', // <------- We don't expect this, do we?
    green: 'green',
    blue: 'blue'
};

نفس الموقف

type Method = 'get' | 'post' | 'put' | 'delete'
const methods: Method[] = ['get', 'post', 'put', 'delete']
class Api {
  [method in Method]: Function // <-- Error here
  constructor() {
    methods.forEach(method => {
      this[method] = (options: any) => { this.send({ ...options, method }) }
    })
  }
  send(options:any) {
    // ....
  } 
}

كيف يمكنني التعامل مع هذه القضية؟

لموظفي Google وغيرهم

ما تريده (ولا يعمل):

type Ma = { [key: 'Foo']: number }
type Mb = { [key: 'Foo' | 'Bar']: number }
type Mc = { [key: 'Foo' | 'Bar' | 0.3]: number }
// etc

ما تحتاجه (وهل يعمل):

type Ma = { [key in 'Foo']?: number }
type Mb = { [key in 'Foo' | 'Bar']?: number }
type Mc = { [key in 'Foo' | 'Bar' | 0.3]?: number }

const x: Ma = {}
const y: Ma = { 'Foo': 1 }
const z: Mc = { [0.3]: 1 }
// const w: Ma = { 'boop': 1 } // error

قيود مؤسفة :
مثال 1

type Mx = {
  Bar: number // OK, but has to be a number type
  [key: string]: number
}

مثال 2

type My = {
  Bar: number, // Error, because of below
  [key in 'Foo']: number
}

هل من الممكن تحقيق شيء مثل هذا:

// TPropertyName must be a string
export type Foo<TPropertyName = "propertyName"> = {
  [key in TPropertyName]: number
};

@ n1ru4l تحتاج إلى تعيين قيد على TPropertyName :

export type Foo<TPropertyName extends string = "propertyName"> = {
  [key in TPropertyName]: number
};

حسنًا ، مع كل هذه الحلول المقترحة ، يبدو أنه لا يوجد جزء مفقود من اللغز ؛ التكرار فوق مفاتيح الكائن:

declare enum State {
  sleep,
  idle,
  busy,
}

type States = { [S in keyof typeof State]: number };

const states: States = {
  sleep: 0x00,
  idle: 0x02,
  busy: 0x03,
};

function getNameFromValue(state: number): State | undefined {
  for (const k in states){
    if (states[k] === state) {
      return k; // type string !== type State and cannot be typecasted
    }
  }
}

الحل المقترح dcousens لا يساعد حقًا لأن تعداد الولاية الخاص بي هو في الواقع 20 سطرًا من التعليمات البرمجية ولا أعتقد أن أي شخص يريد ذلك في مشروعه.

LukasBombach ما الذي تريد إرجاعه getNameFromValue - اسم أحد المفاتيح أو القيمة الرقمية الموجودة على الجانب الأيمن من التعداد الخاص بك؟

  • يشير State إلى _value_ للتعداد ،
  • typeof State سيكون نوع التعداد الخاص بك (هنا: كائن) ،
  • keyof typeof State هو مفتاح تعدادك.

إذا كان تعدادك يبدو هكذا:

enum State {
  sleep = 0x00,
  idle = 0x02,
  busy = 0x03,
}

ثم يمكنك الحصول على المفتاح بالقيام بما يلي:

function getNameFromValue(state: number): keyof typeof State | undefined {
  return State[state] as keyof typeof State | undefined;
}

والقيمة من خلال العمل:

function getNameFromValue(state: number): State | undefined {
    for (const k of UNSAFE_values(State)) {
      if (state === k) {
        return k
      }
    }

    return undefined;
}

const UNSAFE_values = <T extends object>(source: T): T[keyof T][] =>
  Object.values(source) as T[keyof T][];

@ karol-majewski شكرا لك! ما أريد إرجاعه هو السلسلة التي تقتصر على قيم محددة ، وقد تمكنت من القيام بذلك بالطريقة التي أفعلها هناك. الطريقة التي أفهم بها الحل الخاص بك ، هي مشابهة لي ولكن المفاتيح والقيم / الوصول إليها معكوس.

ما يزعجني هو أنني يجب أن أقوم بتمثيل من النوع ، والذي أود تجنبه.

لقد ألقيت نظرة على هذا الخيط ، وأنا في حيرة من أمري. لماذا يعمل هذا:

type Point<D extends string> = {
  [key in D]: number;
}

لكن هذا لا؟

interface Point<D extends string> { 
  [key in D]: number 
}

image

يبدو لي أن الاثنين يجب أن يكونا متساويين. ماذا ينقصني؟

ربما يمكنك نشر مثال لما تبحث عنه هنا لأن ما تعرضه هنا هو أنك تريد كل مفتاح في سلسلة. ليس مطابقا.

إذا كانت لديك نقطة تقول x و y

const point = {
  x: 100,
  y: 200
}

ثم سيكون لديك نوع مثل هذا:

interface IPoint {
  x: number;
  y: number;
}
type PointKeys = keyof IPoint;

ولكن مرة أخرى ربما تنشر المزيد مما تبحث عنه هنا.

أو ربما تبحث عن شيء كهذا:

interface IPoint {
  x: number;
  y: number;
}

const points = {
  one: { x: 100, y: 200 },
  two: { x: 200, y: 300 }
};

type PointKeys = keyof typeof points;

type Points = { [K in PointKeys]: IPoint };

ما كنت أحاول التعبير عنه هو نقطة ذات عدد عشوائي من الأبعاد المسماة. علي سبيل المثال:

const point2D: Point<'x' | 'y'> = {x: 2, y: 4};
const point6D: Point<'u' | 'v' | 'w' | 'x' | 'y' | 'z'> = {
  u: 0,
  v: 1,
  w: 2,
  x: 3,
  y: 4,
  z: 5
};

لكني أعتقد أن حالة الاستخدام الخاصة بي ليست بنفس أهمية السؤال عن سبب عمل توقيع الفهرس في اسم مستعار للنوع ولكن ليس في واجهة؟

لقد قضيت وقتًا طويلاً في محاولة جعله يعمل كواجهة قبل أن أدرك أن نفس الشيء مثل الاسم المستعار من النوع يعمل. إنه أمر محير بعض الشيء لماذا سيعمل المرء ولكن ليس الآخر.

أرى ، لقد أساءت فهم أنك لا تطلب حلاً ولكن لماذا؟

لذا فإن هذا يعمل بشكل جيد ، أفترض أنك أدركت ذلك ولكن لكي أكون واضحًا:

type Point<Keys extends string> = { [K in Keys]: number };

const point2D: Point<'x' | 'y'> = {x: 2, y: 4};

const point6D: Point<'u' | 'v' | 'w' | 'x' | 'y' | 'z'> = {
  u: 0,
  v: 1,
  w: 2,
  x: 3,
  y: 4,
  z: 5
};

على عكس الاسم المستعار للنوع الذي يعدد المفاتيح ، فإن الواجهة هي تعريف ومن ثم يجب أن يكون النوع العام كائنًا أو رمزًا. لذا فإن ما تحاول القيام به هنا يجب أن يتم باستخدام اسم مستعار من النوع لأنك لا تحدده ولكنك تمثل ما يقوم على أساس المفاتيح. فكر في الأمر على أنه Record<T, K extends string> إذا كان ذلك منطقيًا.

أعتقد أن معلمة توقيع الفهرس يجب أن تسمح أيضًا بالنوع والأنواع الفرعية String لأنها صالحة. أحتاج إلى هذا للسيناريو الذي شرحته هنا: https://github.com/microsoft/TypeScript/issues/6579#issuecomment -537306546

mariusschulz ، ماذا عن let keywords: { [key in keyof NodeType]: string } ؟

mariusschulz ، ماذا عن let keywords: { [key in keyof NodeType]: string } ؟

لا أعتقد أنه من المنطقي ، أن يمنحك keyof NodeType سلاسل حرفية مختلفة - طرق على نوع String.

ما أقوم به هو عكس المشكلة ، وعادة ما يكون ذلك كافيًا لحالاتي:

interface KeywordsMappings  {
  IfStatement: "if", // or string if you want to widen the type
  WhileStatement: "while",
  ForStatement: "for"
}

type Keywords = keyof KeywordsMappings

let keywords: KeywordsMappings = {
    "IfStatement": "if",
    "WhileStatement": "while",
    "ForStatement": "for"
};

لست متأكدًا مما يحدث ولكن أعتقد أنها مشكلة مماثلة:

type TestMap<T extends string> = {[key in T]: string}

const a = <T extends string>(aa: T) => {
    const x: TestMap<T> = {
        [aa]: 'string'
    }
}

//Type '{ [x: string]: string; }' is not assignable to type 'TestMap<T>'.(2322)

يمكننا تحقيق ذلك باستخدام Record :

type NodeType = 'IfStatement' | 'WhileStatement' | 'ForStatement'
type NodeTypeObject = Record<NodeType, string>

// works!
var myNodeTypeObject: NodeTypeObject = {
  IfStatement: 'if',
  WhileStatement: 'while',
  ForStatement: 'for',
}

// complains if there are missing proeprties
var myNodeTypeObject: NodeTypeObject = {
  IfStatement: 'if',
  WhileStatement: 'while',
} // --> Error : Property 'ForStatement' is missing but required by type 'Record<NodeType, string>'.

// Complains if additional properties are found
var myNodeTypeObject: NodeTypeObject = {
  IfStatement: 'if',
  WhileStatement: 'while',
  ForStatement: 'for',
  foo :'bar'  // --> Error :  'foo' does not exist in type 'Record<NodeType, string>'.
}

المكافأة: إذا أردنا أن تكون الخصائص اختيارية ، فيمكننا القيام بذلك باستخدام Partial :

type NodeType = 'IfStatement' | 'WhileStatement' | 'ForStatement'
type NodeTypeObject = Partial<Record<NodeType, string>>

// works!
var myNodeTypeObject: NodeTypeObject = {
  IfStatement: 'if',
  WhileStatement: 'while',
}

جربه في الملعب المطبوع

لكن في هذا الحل لا يمكنك تكرار المفاتيح:

type NodeType = 'IfStatement' | 'WhileStatement' | 'ForStatement'
type NodeTypeObject = Record<NodeType, string>

// works
var myNodeTypeObject: NodeTypeObject = {
  IfStatement: 'if',
  WhileStatement: 'while',
  ForStatement: 'for',
}

function getNameFromValue(str: string): NodeType | undefined {
  for (const k in myNodeTypeObject){
    if (myNodeTypeObject[k] === str) { // any
      return k; // is a string
    }
  }
}

ملعب

https://github.com/microsoft/TypeScript/issues/5683#issuecomment -515744911

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

manekinekko picture manekinekko  ·  3تعليقات

weswigham picture weswigham  ·  3تعليقات

seanzer picture seanzer  ·  3تعليقات

blendsdk picture blendsdk  ·  3تعليقات

siddjain picture siddjain  ·  3تعليقات