Typescript: أضف دعمًا لطرح النوع الحرفي

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

الآن لدينا Mapped Types و keyof ، يمكننا إضافة عامل طرح يعمل فقط على الأنواع الحرفية:

type Omit<T, K extends keyof T> = {
    [P in keyof T - K]: T[P];
};

type Overwrite<T, K> = K & {
    [P in keyof T - keyof K]: T[P];
};

type Item1 = { a: string, b: number, c: boolean };
type Item2 = { a: number };

type T1 = Omit<Item1, "a"> // { b: number, c: boolean };
type T2 = Overwrite<Item1, Item2> // { a: number, b: number, c: boolean };
Fixed Suggestion

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

بعض الأشياء الممتعة هنا. لقد دمجت للتو # 16446 مما يتيح حلاً أبسط:

type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] };
type Overwrite<T, U> = { [P in Diff<keyof T, keyof U>]: T[P] } & U;

type T1 = Diff<"a" | "b" | "c", "c" | "d">;  // "a" | "b"

type Item1 = { a: string, b: number, c: boolean };
type Item2 = { a: number };

type T2 = Omit<Item1, "a"> // { b: number, c: boolean };
type T3 = Overwrite<Item1, Item2> // { a: number, b: number, c: boolean };

النوع Diff هو عامل طرح فعال لأنواع السلاسل الحرفية. يعتمد على معادلات النوع التالية:

  • T | never = T
  • T & never = never (الذي يوفره # 16446)

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

يجب أن يشتمل عامل الطرح الحرفي الأصلي على علاقات معينة من نوع الترتيب الأعلى. على سبيل المثال ، يجب اعتبار معلمات النوع T و U و T - U نوعًا فرعيًا من T و { [P in keyof T - U]: T[P] } يجب اعتباره نوع فائق من T . لا يحتوي النوع Diff على هذه ، لذا قد تكون تأكيدات الكتابة ضرورية في بعض الحالات.

ال 87 كومينتر

تكرار # 4183؟

سأقوم بإعادة تسمية هذا الاقتراح لأن # 4183 لا يحتوي على اقتراح في الواقع.

ahejlsberg تحقق من اقتراحي في # 4183

نأمل أن نرى هذا قريبًا ، سيكون تحسنًا كبيرًا أيضًا!

لاحظ أن keyof T قد يكون string (اعتمادًا على المفهرسات المتاحة) ، وبالتالي يجب تحديد سلوك string - 'foo' ، وما إلى ذلك.

تحرير: تصحيح أن keyof T دائمًا عبارة عن سلسلة ، ومع ذلك تظل نقطة string - 'foo' .

لاحظ أن keyof T قد يكون سلسلة أو رقمًا أو سلسلة | number (اعتمادًا على المفهرسات المتاحة) ، وبالتالي فإن سلوك السلسلة - "foo" ، والرقم - "1" ، وما إلى ذلك يجب تحديده.

هذا ليس دقيقا. keyof دائمًا string .

إذا كانت هناك أي أسئلة حول حالات الاستخدام ، فسيكون هذا مفيدًا بشكل لا يصدق لكتابة Redux ، و Recompose ، ومكتبات مكونات أخرى ذات ترتيب أعلى لـ React. على سبيل المثال ، يؤدي التفاف قائمة منسدلة غير مضبوطة في withState HOC إلى إزالة الحاجة إلى الدعائم isOpen أو toggle ، دون الحاجة إلى تحديد نوع يدويًا.

يلتف () Redux's connect () بالمثل ويوفر بعض / جميع الخاصيات إلى أحد المكونات ، تاركًا مجموعة فرعية من الواجهة الأصلية.

الرجل الفقير Omit :

type Omit<A, B extends keyof A> = A & {
  [P in keyof A & B]: void
}

لا يزال المفتاح "المحذوف" موجودًا ، ولكن غالبًا ما يكون غير قابل للاستخدام ، نظرًا لأن نوعه هو T & void ، ولا شيء عاقل يمكنه تلبية هذا القيد. لن يمنعك من الوصول إلى T & void ، لكنه سيمنعك من تعيينه أو استخدامه كمعامل في دالة.

بمجرد هبوط https://github.com/Microsoft/TypeScript/pull/13470 ، يمكننا أن نفعل ما هو أفضل.

niieani لن يعمل هذا الحل لرد فعل HoC الذي يضخ الدعائم.

إذا استخدمت هذا النمط في "إزالة" الخاصيات ، فسيظل مدقق الحروف يشتكي إذا حذفتها عند استخدام أحد المكونات:

type Omit<A, B extends keyof A> = A & {
  [P in keyof A & B]: void
}

class Foo extends React.Component<{a:string, b:string}, void> {
}

type InjectedPops = {a: any}
function injectA<Props extends InjectedPops>(x:React.ComponentClass<Props>):React.ComponentClass<Omit<Props, 'a'>>{
  return x as any
}

const Bar = injectA(Foo)

var x = <Bar b=''/>
// Property 'a' is missing in type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<Omit<{ a: string; b: string; }, "a">, Co...'.

أعتقد أنه سيكون من الأسهل إدراك هذا بالشكل التالي:

[P in keyof A - B] يجب أن يعمل فقط إذا امتد A إلى B ، ويعيد جميع المفاتيح الموجودة في A وليست في B.

type Omit<A extends B, B> = { 
  [P in keyof A - B]: P[A] 
}

type Impossible<A, B> = {
 [P in keyof A - B]: string
} /* ERROR: A doesn't extend B */

interface IFoo {
   foo: string
}

interface IFooBar extends IFoo {
  bar: string
}

type IBar = Omit<IFooBar, IFoo>; // { bar: string }

نظرًا لأنه لا يمكننا تغيير أنواع الحقول عند التمديد ، فإن جميع التناقضات (المعروفة أيضًا باسم السلسلة) - "العالم" ستختفي.

Overwrite مغطى بالرقم 10727 #. يبدو هذا الاقتراح صيغة بديلة لـ # 13470؟

13470 ليس هو نفسه ، لأنه لا يسمح لك بإنشاء أنواع الاختلافات ديناميكيًا.

Overwrite باستخدام بنية اليوم:

type Item1 = { a: string, b: number, c: boolean };
type Item2 = { a: number };

type ObjHas<Obj extends {}, K extends string> = ({[K in keyof Obj]: '1' } & { [k: string]: '0' })[K];
type Overwrite<K, T> = {[P in keyof T | keyof K]: { 1: T[P], 0: K[P] }[ObjHas<T, P>]};
type T2 = Overwrite<Item1, Item2> // { a: number, b: number, c: boolean };

تحرير: تم إصلاح مشكلة الفهرسة.

لقد جربت يدي أيضًا على Omit ، إذا كان النجاح مختلطًا:

// helpers...
type Obj<T> = { [k: string]: T };
type SafeObj<O extends { [k: string]: any }, Name extends string, Param extends string> = O & Obj<{[K in Name]: Param }>;
type SwitchObj<Param extends string, Name extends string, O extends Obj<any>> = SafeObj<O, Name, Param>[Param];
type Not<T extends string> = SwitchObj<T, 'InvalidNotParam', {
  '1': '0';
  '0': '1';
}>;
type UnionHas<Union extends string, K extends string> = ({[S in Union]: '1' } & { [k: string]: '0' })[K];
type Obj2Keys<T> = {[K in keyof T]: K } & { [k: string]: never };

// data...
type Item1 = { a: string, b: number, c: boolean };

// okay, Omit, let's go.
type Omit_<T extends { [s: string]: any }, K extends keyof T> =
  {[P2 in keyof T]: { 1: Obj2Keys<T>[P2], 0: never }[Not<UnionHas<K, P2>>]}
type T1 = Omit_<Item1, "a">;
// intermediary result: { a: never; b: "b"; c: "c"; }
type T2 = {[P1 in T1[keyof Item1] ]: Item1[P1]}; // { b: number, c: boolean };
// ^ great, the result we want!

رائع ، لقد تم حل مشكلة أخرى!
...

// now let's combine the steps?!
type Omit<T extends { [s: string]: any }, K extends keyof T> =
  {[P1 in {[P2 in keyof T]: { 1: Obj2Keys<T>[P2], 0: never }[Not<UnionHas<K, P2>>]}[keyof T] ]: T[P1]};
type T3 = Omit<Item1, "a">;
// ^ yields { a: string, b: number, c: boolean }, not { b: number, c: boolean }
// uh, could we instead do the next step in a separate wrapper type?:
type Omit2<T extends { [s: string]: any }, K extends keyof T> = Omit_<T, K>[keyof T];
// ^ not complete yet, but this is the minimum repro of the part that's going wrong
type T4 = Omit2<Item1, "a">;
// ^ nope, foiled again! 'a'|'b'|'c' instead of 'b'|'c'... wth? 
// fails unless this access step is done after the result is calculated, dunno why

لاحظ أن محاولتي لحساب الاختلافات النقابية في # 13470 عانت من نفس المشكلة ... أي من خبراء TypeScript هنا؟ 😄

@ tycho01 ما تفعله هنا مذهل. لقد تلاعبت بها قليلاً ويبدو أن السلوك المكون من خطوة واحدة يبدو وكأنه خطأ في TypeScript. أعتقد أنه إذا قمت بتقديم تقرير خطأ منفصل حول هذا الموضوع ، فيمكننا حله والحصول على خطوة واحدة رائعة Omit و Overwrite ! :)

niieani : نعم ، القضية مرفوعة في # 16244 الآن.

لقد كتبت قليلاً عن فهمي الحالي / تقدمي في عمليات نوع TS ؛ فقط ضع صورة هنا . كود هنا .
إنه مثل ، يمكننا القيام بعمليات شبيهة بالمنطقية باستخدام السلاسل ، وبالنسبة لعمليات النقابات / الكائنات ، فإن الحدود الحالية تتغلب على خلل الاختلاف هذا.

يبدو أن العمليات على الأساسيات الفعلية غير مطروحة حاليًا ، بينما بالنسبة لأنواع الصفوف لا تزال الأمور صعبة أيضًا - يمكننا الحصول على تمثيلات الاتحاد / الكائنات باستخدام keyof / Partial ، ونأمل أن يكون الاختلاف تعمل بهذه الطريقة أيضًا. أقول "نأمل" لأن بعض العمليات مثل keyof يبدو أنها لا تحب المؤشرات الرقمية ...

أشياء مثل تحويل تلك الأنواع مرة أخرى إلى أنواع tuple ، أو مجرد التدمير المستقيم ... على مستوى النوع ، ليست ممكنة حتى الآن.
عندما نفعل ذلك ، قد نصل إلى التكرار على مستوى النوع لعمليات شبيهة بالاختزال ، حيث تصبح الأشياء أكثر إثارة للاهتمام من imo.

تحرير: حل مشكلة strictNullChecks .

@ tycho01 ،
شيء مذهل حقا !!

تلاعبت بها قليلا ما رأيك بهذا الحل؟

type Obj<T> = { [k: string]: T };
type SafeObj<O extends { [k: string]: any }, Name extends string, Param extends string> = O & Obj<{[K in Name]: Param }>;
type SwitchObj<Param extends string, Name extends string, O extends Obj<any>> = SafeObj<O, Name, Param>[Param];
type Not<T extends string> = SwitchObj<T, 'InvalidNotParam', {
  '1': '0';
  '0': '1';
}>;
type UnionHas<Union extends string, K extends string> = ({[S in Union]: '1' } & { [k: string]: '0' })[K];
type Obj2Keys<T> = {[K in keyof T]: K } & { [k: string]: never };

type Omit_<T extends { [s: string]: any }, K extends keyof T> =
  {[P2 in keyof T]: { 1: Obj2Keys<T>[P2], 0: never }[Not<UnionHas<K, P2>>]}

export type Omit<T extends { [s: string]: any }
    , K extends keyof T
    , T1 extends Omit_<T, K>= Omit_<T, K>
    , T1K extends keyof Pick<T1, keyof T>=keyof Pick<T1, keyof T>> =
    {[P1 in T1[T1K]]: T[P1]}
  ;

type Item1 = { a: string, b: number, c: boolean };

const ommited: Omit<Item1, 'a'> = {
  b: 6,
  c: true,
}

يبدو أن nirendy يعمل من أجلي! 👍
@ tycho01 عمل رائع في حل مشكلة strictNullChecks .

niieani : يمكنك أن تشكر jaen على ذلك ، انظر # 13470 . 😃

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

ومع ذلك ... كان فصل الخطوات هنا شيئًا واحدًا ، ولكن أيضًا استبدال [keyof T] لذلك keyof Pick<> ؟ كيف عرفت شيئًا من هذا ؟! 😆

هذا يدهش يا شباب الكرات ، كيف فعلت ذلك؟

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

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

مدهش!

nirendy : نعم ، لا توجد شكاوى هنا! 😃

تحرير: @ aleksey-bykov: بالنسبة لأجزائي على الأقل ، خطوة بخطوة ، من الألف إلى الياء (انظر # 16392).

بعض الأشياء الممتعة هنا. لقد دمجت للتو # 16446 مما يتيح حلاً أبسط:

type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] };
type Overwrite<T, U> = { [P in Diff<keyof T, keyof U>]: T[P] } & U;

type T1 = Diff<"a" | "b" | "c", "c" | "d">;  // "a" | "b"

type Item1 = { a: string, b: number, c: boolean };
type Item2 = { a: number };

type T2 = Omit<Item1, "a"> // { b: number, c: boolean };
type T3 = Overwrite<Item1, Item2> // { a: number, b: number, c: boolean };

النوع Diff هو عامل طرح فعال لأنواع السلاسل الحرفية. يعتمد على معادلات النوع التالية:

  • T | never = T
  • T & never = never (الذي يوفره # 16446)

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

يجب أن يشتمل عامل الطرح الحرفي الأصلي على علاقات معينة من نوع الترتيب الأعلى. على سبيل المثال ، يجب اعتبار معلمات النوع T و U و T - U نوعًا فرعيًا من T و { [P in keyof T - U]: T[P] } يجب اعتباره نوع فائق من T . لا يحتوي النوع Diff على هذه ، لذا قد تكون تأكيدات الكتابة ضرورية في بعض الحالات.

لقد لاحظت اختلافًا تقنيًا بسيطًا في أن المنظف Overwrite تعريف ينتج نوع تقاطع:

type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Overwrite<T, U> = { [P in Diff<keyof T, keyof U>]: T[P] } & U;
type Item1 = { a: string, b: number, c: boolean };
type Item2 = { a: number };
type T3 = Overwrite<Item1, Item2> // { b: number, c: boolean } & { a: number }

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

type Overwrite<T, U, Int = { [P in Diff<keyof T, keyof U>]: T[P] } & U> = Pick<Int, keyof Int>;

... على حد علمي يبدو أن هذا لا يزيد عن كونه اختلافًا جماليًا. اوه حسنا!

تحرير: استرجع ذلك ؛ لم أعد أوصي باستخدام هذا الإصدار المستند إلى Pick . يبدو في الواقع أنه يكسر بعض الأشياء بمهارة مثل محاولاتي لإعادة كتابة Ramda fromPairs / zipObject هنا .

حاولت استخدام هذا (واحد من @ tycho01 و / أو nirendy ، وليس الجديد من ahejlsberg حتى الآن) لحذف خاصية من فئة تتجاوز toString() ، ولم تنجح. هذا مثال بسيط:

type Item1 = { a: string, b: number, c: boolean, toString(): string };
type OmittedItem1 = Omit<Item1, "a"> 
// OmittedItem1 should be { b: number, c: boolean, toString(): string }, but is {}!

يمكنني أن أتخيل نوعًا ما لماذا تجاوز toString() والشركة (آه ... valueOf() ؟ ربما جميع طرق النماذج الأولية الأخرى؟) من شأنه أن يفسد أي شيء يعدد المفاتيح ، لكن يبدو أنه أمر مؤسف.

أي شخص يريد التكهن بما إذا كان هذا السلوك المقصود هو Omit ، أو خطأ في Omit ، أو خطأ في Typescript الذي يكسر Omit ؟

jcalz :

نعم ، لقد واجهت مشكلات كبيرة مع toString (وطريقة Object.prototype toLocaleString ).
لا أحصل على {} رغم ذلك - ما هو إصدار TS؟ يبدو أن الملعب والإصدارات الأحدث أفضل.

تحرير: يبدو أن المشكلة تكمن في Diff : إنها تعمل عن طريق عمل & { [P in U]: never } & { [x: string]: never } . في حالة toString الرغم من ذلك ، من المتوقع أن يكون لهذين النوعين من toString ، مما يؤدي إلى إفسادهما. سأحذف toLocaleString للإيجاز.

بالنسبة إلى الأخير ، يبدو من الممكن أن تقول { toString: "toString"; [k: string]: never } للكتابة عليه. الشيء الصعب هو ، لا أعتقد أنه يمكنك جعله never هنا لأنه سيتم استبداله بطريقة النموذج الأولي ...

بالنسبة للأولى ، يبدو أن بناء جملة مثل { toString: "something"; [P in U]: never } لا يعمل بالنسبة لي ، مما ينتج عنه A computed property name must be of type 'string', 'number', 'symbol', or 'any' ، أو يبدو أن النصف الأخير يتم تجاهله.
سيكون بناء الجملة { [P in keyof U]: never; [K in keyof PrototypeMethods]: "something"; } مرغوبًا أكثر هنا ولكن مرة أخرى لا نرد. ثم مرة أخرى ، لا يزال السؤال هو ما الذي نكتبه بدلاً من "something" ، لأنك تريد فقط ما تم تحديده بخلاف ذلك ، ولكن في كلتا الحالتين حتى عندما تريد الكتابة فوق toString كـ never ، لا يمكنك ذلك. بشكل أساسي ، يتم حل جميع العمليات تقريبًا حاليًا toString .

أفترض أن الجمع بين تراكيب الكائنات هذه لم يكن شائعًا تمامًا ، وقد لا يتم دعم هذه الحالة بالفعل.
... أي تعليق على ذلكahejlsberg؟

ما زلت أفكر في البدائل ... إنه بالتأكيد صعب رغم ذلك.

تحرير 2: هناك مشكلة واحدة هنا هي الغموض في { [k: string]: 123 }['toString'] - يجب أن يكون toString من بين k ، حل إلى 123 ، أو يجب حلها للتو لطريقة النموذج الأولي؟
يفترض TS حاليًا الخيار الأخير ، مما يجعل من الصعب تقديم حالات الاستخدام التي تنوي الأولى.
في الأساس ، أود أن أحاول إنشاء طريقة بديلة للوصول إلى خصائص الكائن المحصنة ضد هذا ، بدلاً من التحقق من المفاتيح الخاصة فقط بدلاً من أساليب النموذج الأولي أيضًا.
لقد أحرزت بعض التقدم ، ولكن من الصعب تحقيق أي شيء دون استخدام عامل الوصول الأصلي للممتلكات ، لذلك يجب أن تتخطى الأمور بعض الشيء لمنع سلوك عربات التي تجرها الدواب من التسلل إلى كل مكان.

لا أحصل على {} - ما هو إصدار TS؟ يبدو أن الملعب والإصدارات الأحدث أفضل.

توضيح

هذا مذهل! يبدو أن إصدار nirendy لا يعمل مع الاختيارات:

type Item1 = { a: string, b?: number, c: boolean }
const omitted: Omit<Item1, 'a'> = { c: true } 
// Property 'b' is missing in type '{ c: true; }'.

jcalz : الرد المتأخر ولكن شكرا على التوبيخ. يبدو أن حلها قد لا يكون تافهًا على الرغم من أن الوصول إلى الممتلكات ومعظم العمليات التي تعتمد عليها تتأثر ؛ سيحاول أكثر في نقطة واحدة.

dleavitt :
شكرا لتقرير الشوائب. حاولت قليلا يبدو ، كما لاحظت ، أن Omit (أي من الإصدارين) لا ينتج حاليًا ? . باستخدام strictNullChecks يقوم بدلاً من ذلك بإلحاق | undefined بالقيمة ، والتي لا تبدو جيدة بما يكفي حتى الآن لإجراء فحص النوع هذا. لست متأكدًا حتى الآن من طريقة لمعالجة هذا الأمر لا يتطلب فحوصات من نوع على مستوى (من المحتمل أن يتم حلها عن طريق على سبيل المثال # 6606) للتحقق مما إذا كان undefined يفي بخصائص الكائن ...

@ tycho01 هل يمكننا حل مشكلة ? بكتابة تمرير متعدد؟

بمعنى آخر:

  1. اجعل الخصائص الاختيارية غير اختيارية
  2. إخفاء قائمة الخصائص الاختيارية فقط ، بشكل عام ، جانبًا
  3. قم بعمل Omit على النوع غير الاختياري
  4. أعد إضافة | undefined إلى المفاتيح الموجودة من النوع المحذوف

niieani :

  1. إعادة إضافة | غير معرف للمفاتيح الموجودة من النوع المحذوف

هذا لا يقطعها في الواقع ؛ باستخدام strictNullChecks استرجعنا بالفعل { b: number | undefined; c: boolean; } ، لذا يبدو أن ? مهم.

سأحتاج إلى الاعتراف بأنني لا أفهم تمامًا ما يحدث حتى الآن. إذا كنت تستخدم { [P in keyof Item1]: P } فإن ? يظهر على ما يرام ، مما يعني أن المعلومات تظهر في keyof Item1 ، على الرغم من أنك إذا قمت بالتحقق من ذلك بنفسك فلن ترى أي معلومات إضافية . يبدو وكأنه خاصية سحرية غير مرئية ، وليس بعد التحقق من الأجزاء الداخلية للمترجم.

2: اخفِ قائمة الخصائص الاختيارية فقط ، بشكل عام ، جانباً

التحقق مما إذا كانت الخاصية اختيارية يمثل تحديًا حاليًا. يبدو أن القيام بـ keyof لا يعطي هذه المعلومات (بطريقة مرئية) ؛ number | undefined واضح لنا ، لكن مقدار المشغلين على مستوى النوع محدود للغاية - إذا علمنا أن undefined يرضي هذا النوع ، فنحن جيدون ، ولكن على مستوى النوع ، كما هو الحال ، ليس لدينا طريقة للقيام بذلك.

// insert `Omit` typing, one or the other depending on whether you use Playground or 2.5

type Item1 = { a: string, b?: number, c: boolean }
type noA = Omit<Item1, 'a'>
// { b: number | undefined; c: boolean; }
// ^ `Omit` degenerates `?` to ` | undefined`
type keys = keyof Item1
// "a" | "b" | "c"
type almost1 = { [P in keyof Item1]: If<ObjectHasKey<noA, P>, (noA & Obj<never>)[P], never> }
// { a: never; b?: number | undefined; c: boolean; }
type almost2 = { [P in keyof Item1]: If<ObjectHasKey<noA, P>, P, never> }
// { a: never; b?: "b"; c: "c"; }

الشيء الذي يجعلنا `` عالقين '' في هذه المرحلة هو أن الطريقة الوحيدة للتخلي عن خاصية a: never هي عن طريق توصيل مفاتيح almost2 في نفسها ، على الرغم من أن ذلك سيجعلنا في نفس الوقت نخسر b 's ? الحالة. في almost1 اقتربنا من المكان الذي نريد الذهاب إليه ، لكن لا نقترب من التخلي عن a بطريقة تترك b ? سليمة .

في حين أننا لا نستطيع الكشف أو العمل مباشرة على ? نفسه ، فإن التشغيل عليه بالوكيل من خلال شيكات | undefined يبدو حلاً عادلاً بدرجة كافية. لكن نعم ، لا توجد فحوصات من النوع على مستوى. بعيدًا عن التصويت على طلب الميزة (أو البدائل ، رأيت مؤخرًا تعليقًا يطلب مستوى النوع instanceof + المستوى المنطقي على مستوى النوع) ، لا أعرف.

niieani : بدلاً من ذلك ، قل أن | undefined يجب اعتباره معادلاً لـ ? والملف كخطأ. قد يكون هذا مثيرًا للجدل (لست متأكدًا مما إذا كنت سأعتبره أكثر أناقة بنفسي) ، لكنه سيكون أقل انخراطًا في جانب TS ، لذا من المحتمل أن تكون فرص الإصلاح أفضل لأغراضك.

@ tycho01 نعم ، أراه. أعتقد أن فريق TypeScript ذكر في مكان ما أن | undefined يجب اعتباره معادلاً لـ ? - أي أنه يتوافق مع أهدافهم. بالنسبة لمعظم المقاصد والأغراض ، هذه هي الطريقة التي يتصرف بها TS حاليًا ، على الرغم من أنني أرى ما تقصده من خلال تمييز المترجم بطريقة ما.

على سبيل المثال ، يعامل Flow هذين ( ? و | void ) كأشياء مختلفة ، أي يمكنك من الناحية الفنية امتلاك خاصية وتتوقع أن يكون نوعها undefined . ولكن لأسباب عملية (جميع؟) هم متماثلون إلى حد كبير.

niieani : نعم. يبدو من المناسب تقديم خطأ بعد ذلك.

المعذرة لكوني خارج الموضوع

يمكن محوjcalz عمليًا toString من ملفات التصريح ، وبهذه الطريقة سيظهر فقط على الفئات / الكائنات التي تنفذها بشكل صريح (بشرط أن يكون التنفيذ الافتراضي عديم الفائدة على أي حال)

هذه هي الطريقة التي يمكن القيام بها:

  1. قم بتعيين noLib: true في tsconfig.json
  2. احصل على نسخة من ملفات التصريح التي تحتاجها من https://github.com/Microsoft/TypeScript/tree/master/src/lib وقم بتضمينها في مشروعك
  3. اعثر على toString من Object وقم بالتعليق عليها
interface Object {
    /** The initial value of Object.prototype.constructor is the standard built-in Object constructor. */
    constructor: Function;

    /** Returns a string representation of an object. */
    // toString(): string; <--- HERE

dleavittniieani تقديمه في # 16762.

@ tycho01dleavitt باستخدام Pick يحفظ اخليارات في كلا الإصدارين من Omit . لست متأكدًا مما إذا كان هذا يكسر أي شيء آخر.

إليك مثال كامل مع تعريف 2.4 الموجز.

type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;

interface A {
  a: string;
  b?: string;
  c: number;
}

type B = Omit<A, 'c'>;

const a: A = {
  a: '5',
  c: 5,
};

const b: B = {
  a: '5',
};

// Note: typeof B is (correctly) Pick<A, 'a' | 'b'>

وبالمثل ، يمكنك إعادة تعريف Omit في النسخة القديمة (باقي التعريف هو _omitted_).

type Omit<T extends { [s: string]: any }
    , K extends keyof T
    , T1 extends Omit_<T, K>= Omit_<T, K>
    , T1K extends keyof Pick<T1, keyof T> = keyof Pick<T1, keyof T>> = Pick<T, T1[T1K]>;

Pinpickle : لقد حاولت قليلاً مع الاختبارات الأخرى التي أجريتها. أرى فقط مشكلات تعريف Omit الخاصة بك ، ولم يتم العثور على سلبيات! 😃

Pinpickle يعمل هذا أيضًا "بشكل جيد بما فيه الكفاية" مع تجاوز toString() ، أو على الأقل بشكل قابل للحل. ("الحل البديل" كلمة ، أليس كذلك؟)

type Item1 = { a: string, b: number, c: boolean, toString(): string }; 
type OmittedItem1 = Omit<Item1, "a">; // { b: number, c: boolean } almost?
type OmittedItem1Fixed = OmittedItem1 & { toString(): string }; // good enough

تم تذكيرني بتعليق في # 12424 ، لا تزال عمليات التنفيذ الحالية Omit و Overwrite تفشل في إعادة إنشاء أي فهرس سلسلة قد يكون لدى أنواع الكائنات المصدر. يمكن التحقق من وجود الفهرس باستخدام # 6606.

ahejlsbergPinpickle وغيرها - بفضل بمساعدة على هذا! باستخدام التقنية المذكورة أعلاه ، تمكنا من تكرار نوع التدفق $ Diff . بالتعامل بشكل صحيح مع defaultProps في React :

/**
 * From https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458
 * The Diff type is a subtraction operator for string literal types. It relies on:
 *  - T | never = T
 *  - T & never = never
 *  - An object with a string index signature can be indexed with any string.
 */
export type StringDiff<T extends string, U extends string> = ({[K in T]: K} &
  {[K in U]: never} & {[K: string]: never})[T];

/**
 * From https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766
 * Omits keys in K from object type T
 */
export type ObjectOmit<T extends object, K extends keyof T> = Pick<T, StringDiff<keyof T, K>>;

/**
 * Returns a version of type T where all properties which are also in U are optionalized.
 * Useful for makding props with defaults optional in React components.
 * Compare to flow's $Diff<> type: https://flow.org/en/docs/types/utilities/#toc-diff
 */
export type ObjectDiff<T extends object, U extends object> = ObjectOmit<T, keyof U & keyof T> &
  {[K in (keyof U & keyof T)]?: T[K]};

طرح سؤال محذوف قبل تبديل الدماغ إلى وضع جافا سكريبت :-) (آسف على البريد العشوائي)

@ rob3c :

type OmitFromBoth<T, K extends keyof T> = Omit<T & typeof T, K | 'prototype'>;
type WorksForBoth = OmitFromBoth<Hmm, 'I1' | 'S1'>;
// "I1" | "S2"

مثل هذا؟ لم تختبر ، لكنك أردت فقط اختصار مثالك الأخير ، أليس كذلك؟

@ tycho01 لقد حاولت ذلك بالفعل بالإضافة إلى الاختلافات الأخرى ، لكن لم ينجح أي منها. لا يبدو أن هناك طريقة للإشارة إلى النموذج الأولي لـ T ضمن التعريف العام. ربما يرجع ذلك إلى عدم سماح جافا سكريبت بالوصول إلى خصائص النموذج الأولي "الثابتة" من مثيل (على عكس جافا ، على سبيل المثال). لهذا السبب حذفت السؤال ، لكنك أجبت بسرعة :-)

@ rob3c : إذن لا يوجد typeof على T هاه؟ أرى ما ستحصل عليه في ذلك الوقت ؛ لم أجرب مثل هذه الأشياء حقًا حتى الآن. سوف أعترف أنني لست على علم بالحلول البديلة في هذه الحالة. أكتشف ببطء أن التصحيح باستخدام المترجم أقل سوءًا مما كنت أعتقد ، لكنه لا يزال غير رائع.

robc هناك طرق للوصول إلى prototype في سياق عام (أشعر بالفضول الآن حول ماهية السؤال 😁).

تم إصلاح الخطأ المطبعي

aluanhaddad كان السؤال أساسًا حول كتابة إصدار Omit أعلاه والذي يُرجع الخصائص المفلترة keyof لكل من المثيل والأعضاء الساكنين من النوع بينما تحتاج فقط إلى كتابة Omit<MyClass, ...> وليس Omit<MyClass & typeof MyClass, ...> . كنت أقول prototype أعلاه ، لكن كان من المفترض أن يكون constructor ، حيث أن الأعضاء "الثابتون" عالقون بواسطة الكتابة المطبوعة (أعتقد أنني قد تشتت انتباهي بمحاولة تصفية "النموذج الأولي" الممتلكات في رمز بلدي والمثال). ولأسباب مختلفة موثقة في مكان آخر ، تتم كتابة الخاصية constructor فقط على أنها Function ، لذا فإن جميع الأشكال التي جربتها والتي سحبت المفاتيح من عضو 'constructor' نتج عنها فقط تلك الموجودة على Function بدون الدعائم الثابتة المعلنة في الفصل.

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

كان الرمز الأصلي شيئًا من هذا القبيل:

type Diff<T extends string, U extends string> = (
    & { [P in T]: P }
    & { [P in U]: never }
    & { [x: string]: never }
)[T];

type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;

class Hmm {
    readonly I1 = 'I1';
    readonly I2 = 'I2';
    static readonly S1 = 'S1';
    static readonly S2 = 'S2';
};

// "I2"
type WorksForInstancePropsOnly = base.Omit<Hmm, 'I1'>;

/// "prototype" | "S1" | "S2"
type HasAnnoyingPrototypeKey = keyof typeof Hmm;

// "S2"
type WorksForStaticPropsOnly = base.Omit<typeof Hmm, 'S1' | 'prototype'>;

// "I1" | "S2"
type WorksForBoth = base.Omit<Hmm & typeof Hmm, 'I1' | 'S1' | 'prototype'>;

type OmitForBoth<T, ...> = ???

@ rob3c

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

هذا البيان غير دقيق.

من الممكن أفق

class Hmm {
  static s1 = 'static one'
  static s2 = 'static two'
  i1 = 'instance one'
  i2 = 'instance two'
}

const foo = new Hmm()

console.log(foo)

const {s1, s2} = foo.constructor

console.log({s1, s2})

تضمين التغريدة

هذا البيان غير دقيق.

في الواقع، بياني دقيق، ولكن قد تكون لديكم يساء فهمها المصطلحات بلدي :-)

على عكس Java ، على سبيل المثال ، لا يمكنك الوصول مباشرة إلى الأعضاء الثابتة في مثيل (على سبيل المثال myInstance.myStaticProp ). يوضح المثال الخاص بك محاولة للوصول غير المباشر عبر خاصية constructor للمثيل - والتي ستشتكي من الكتابة المطبوعة مع تمايل الأحمر المقابل في vscode ، لأن هذا متاح فقط في وقت التشغيل. لهذا السبب أسفت على كتابة constructor فقط على أنه Function أعلاه ، بدلاً من شيء مكتوب بقوة أكبر (راجع https://github.com/Microsoft/TypeScript/issues/3841 وما يتصل بها).

20170816 constructor prop screenshot for github thread small

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

حتى فيما يتعلق بالسؤال النظري فقط حول ما إذا كان من الممكن دمج كليهما في تعريف نوع عام باستخدام T ، فإن الإجابة هي لا بدون كتابة constructor صراحة على فئات الاهتمام ، حيث إنها فقط كتب Function بشكل عام.

20170816 constructor prop screenshot 2 intellisense errors small

أحاول استخدام نوع الحذف كما اقترحه في هذا التعليق :

export type RegionsTableProps = 
ObjectOmit<TableProps, 'headerHeight' | 'rowHeight' | 'rowCount'> & 
{
  regions: Region[];
  onRegionClick: (id: string) => void;
};

class RegionsTable extends React.PureComponent<RegionsTableProps> {}

حيث يكون TableProps من @ types / response-virtualized ( width?: number; )

ومع ذلك ، عندما أحاول استخدام RegionTable مثل هذا:

  table = ({ width, height }: Dimensions) => (
    <RegionsTable
      regions={this.props.regions.list}
      onRegionClick={this.onRegionClick}
      width={width}
      height={height}
    />
  );

لدي خطأ:

TS2339: Property 'width' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<RegionsTable> & Readonly<{ children?: ReactNode; }...'.

إذا استبدلت ObjectOmit<TableProps, 'headerHeight' | 'rowHeight' | 'rowCount'> بـ Partial<TableProps> تجميع كل شيء.

لا أفهم تمامًا ما يجري وكيفية إصلاح ذلك. أي شخص لديه أي أفكار؟

doomsower ربما يرجع ذلك إلى [key: string]: any في GridCoreProps

// A = never 
export type A = StringDiff<keyof TableProps, 'headerHeight' | 'rowHeight' | 'rowCount'>

(TS 2.4.2)

ahejlsberg Diff ، Omit و Overwrite هي رائعة ، هل يمكننا أن نتوقع منهم أن يدخلوا المكتبة القياسية في وقت ما إلى جانب Record ، Partial ، Readonly ، Pick ، وما إلى ذلك؟

مقابل Overwrite ، ما هو الإصدار المفضل؟

للمهتمين ، لقد أضفت Diff ، Omit و Overwrite إلى Type Zoo .

goodmind لمزيد من

متفق عليه ، بينما لم نستقر حتى الآن على أفضل الممارسات الصحيحة ، فإننا بالتأكيد بحاجة إلى مزيد من المنافسة. لا تزال أنواع libs تواجه مشكلات كبيرة في الاختبار على سبيل المثال الأنواع غير المنتهية (عادةً الأنواع العودية التي تم التنصت عليها) - بدلاً من وجود اختبار وحدة واحدة يفشل التجميع الكامل الخاص بك فقط في الإنهاء. أود أن أرى المزيد من الناس يحاولون أن يأخذوا الأمور بأنفسهم على أي مستوى.

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

type Diff<T extends string, U extends string> = ({ [P in T]: P } &
  { [P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] };

interface BaseType {
  x: string;
}

const fnWithDefault = <T extends BaseType>(
  input: Omit<T, keyof BaseType>
): T => {
  // Error: Spread types may only be created from object types
  const attempt1 = { ...input, x: 'foo' };
  // Error: Type 'Omit<T, "x"> & { x: string; }' is not assignable to type 'T'.
  return Object.assign({}, input, { x: 'foo' });
};

fnWithDefault<{ x: string; y: string }>({ y: 'bar' });

fnWithDefault هي وظيفة تضيف x إلى النتيجة ، وتعيد T ، وبالتالي يجب أن يكون نوع الإدخال T بدون x . على الرغم من ذلك ، لا يبدو أن TypeScript يسمح بهذا مع أي من السبريد أو Object.assign .

بشكل أساسي ، أتوقع أن يتم Omit<T, 'x'> & { x: string } إلى T . (بافتراض أن T.x عبارة عن سلسلة نصية). لست متأكدًا مما إذا كانت هذه مشكلة في TypeScript ، أو في تنفيذ Omit ، أو حتى حسب التصميم.

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

// built-in Pick definition for reference
// type Pick<T, K extends keyof T> = {[P in K]: T[P]; };

// well-known Diff implementation
type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T];

// our example interface with some optional fields
interface Example {
    a?: number;
    readonly b: boolean;
    c?: string;
    readonly d: any;
}

// Omit implementation which doesn't respect optional fields and readonly
type Omit<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
type ExampleSubsetA = Omit<Example, 'a' | 'b'>;
// ExampleSubsetA = { c: string; d: any; }

// Omit implementation using Pick, which works good with optional fields and readonly
type OmitUsingPick<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;
type ExampleSubsetB = OmitUsingPick<Example, 'a' | 'b'>;
// ExampleSubsetB = { c?: string; readonly d: any; }

// ok, so we now substitute Diff with its body, resulting in
// Omit implementation with Pick but without Diff, that still works good with optional fields and readonly
type OmitUsingPickWithoutDiff<T, K extends keyof T> = Pick<T, ({[P in keyof T]: P } & {[P in K]: never } & { [x: string]: never })[keyof T]>;
type ExampleSubsetC = OmitUsingPickWithoutDiff<Example, 'a' | 'b'>;
// ExampleSubsetC = { c?: string; readonly d: any; }

// well, so now we substitute Pick with its body resulting in
// Omit implementation without any other interfaces, which suddenly stopped respecting optional fields and readonly
type OmitSelfsufficient<T, K extends keyof T> = {[P in ({[P in keyof T]: P } & {[P in K]: never } & { [x: string]: never })[keyof T]]: T[P]};
type ExampleSubsetD = OmitSelfsufficient<Example, 'a' | 'b'>;
// ExampleSubsetD = { c: string; d: any; }

( كود على TypeScript Playground )

أسئلتي هي ، لماذا التخلص من نتائج Pick من المعدلات الاختيارية والقراءة فقط التي يتم فقدانها في العملية؟ بالنسبة لي يبدو أن هناك بعض الأخطاء في TypeScript نفسها.

يبدو أن هناك تغييرًا أخيرًا في Diff و Omit :

type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T]
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>

لقد كان يعمل منذ فترة ، ولكنه الآن أخطاء (باستخدام dtslint الذي يسحب أحدث typescript@next

ERROR: 2:45  expect  TypeScript<strong i="13">@next</strong> compile error:
Type '({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[keyof T]' does not satisfy the constraint 'keyof T'.
  Type '({ [P in T]: P; } & { [P in U]: never; })[keyof T]' is not assignable to type 'keyof T'.

لقد جربت العديد من تطبيقات Omit المدرجة بواسطة aczekajski وحصلت على العديد من الأخطاء:

type Omit<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
ERROR: 1:44  expect  TypeScript<strong i="21">@next</strong> compile error:
Type '({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[keyof T]' is not assignable to type 'string'.
  Type '({ [P in T]: P; } & { [P in U]: never; })[keyof T]' is not assignable to type 'string'.
ERROR: 1:63  expect  TypeScript<strong i="22">@next</strong> compile error:
Type 'P' cannot be used to index type 'T'.



md5-b82dbd8360a59e843eda22c0d24ea876



خطأ: 1:54 توقع خطأ ترجمة TypeScript @ التالي :
اكتب '({[P in T]: P؛} & {[P in U]: never؛} & {[x: string]: never؛}) [keyof T]' لا يفي بالقيد 'keyof T' .
اكتب '({[P in T]: P؛} & {[P in U]: never؛}) [keyof T]' غير قابل للتخصيص لكتابة 'keyof T'.

```ts
type OmitUsingPickWithoutDiff<T, K extends keyof T> = Pick<T, ({[P in keyof T]: P } & {[P in K]: never } & { [x: string]: never })[keyof T]>
ERROR: 1:65  expect  TypeScript<strong i="31">@next</strong> compile error:
Type '({ [P in keyof T]: P; } & { [P in K]: never; } & { [x: string]: never; })[keyof T]' does not satisfy the constraint 'keyof T'.
  Type '({ [P in keyof T]: P; } & { [P in K]: never; })[keyof T]' is not assignable to type 'keyof T'.



md5-220859cd669f13e25d02f34222d801f3



خطأ: 1:58 توقع خطأ ترجمة TypeScript @ التالي :
اكتب '({[P in keyof T]: P؛} & {[P in K]: never؛} & {[x: string]: never؛}) [keyof T]' غير قابل للتخصيص لكتابة 'string' .
اكتب '({[P in keyof T]: P؛} & {[P in K]: never؛}) [keyof T]' غير قابل للتخصيص لكتابة 'string'.
الخطأ: 1: 138 توقع خطأ ترجمة TypeScript @ التالي :
لا يمكن استخدام النوع "P" لفهرسة النوع "T".
""

قد يكون هذا العلاقات العامة قد كسرها: https://github.com/Microsoft/TypeScript/pull/17912

في حين أن السلوك المتوقع Omit تم كسره بشكل واضح في next الحالي ، يجب أن يدرك الشخص الذي يرغب في إصلاحه أنه كما

إصلاح @ aleksey-bykov يصل إلى # 21156.
aczekajski نحن على دراية بالسلوك الغريب ، وهو ناتج عن تفاعل عدد قليل من الأنظمة غير السليمة تمامًا. أنا أعتبر هذه الأنواع "استخدام على مسؤوليتك الخاصة".

أعتقد أن النوع Spread في # 21316 هو في الأساس نسخة ممتازة من Overwrite هنا.

التعليق هنا للأشخاص الذين وصلوا إلى صفحة هذا العدد من النت بعد إصدار 2.8.

مع TS 2.8 الطريقة الأكثر ملاءمة لتنفيذ Omit الآن أدناه ، مع الاستفادة من الأنواع الشرطية والنوع المدمج الجديد Exclude :

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

إلى جانب هذه الأسطر ، إليك تنفيذ Overwrite :

type Overwrite<T1, T2> = {
    [P in Exclude<keyof T1, keyof T2>]: T1[P]
} & T2;

أو بمجرد أن يكون لديك Omit ،

type Overwrite<T, U> = Omit<T, Extract<keyof T, keyof U>> & U;

نظرًا لوجود Omit جديد وأفضل ، كنت آمل أن أفعل شيئًا بدا مستحيلًا مع إصدارات TS السابقة. لنفترض أن لدي واجهات من هذا القبيل:

interface P1 {
  type: 1,
  a: string;
}

interface P2 {
  type: 2,
  b: string;
}

type P = { id: number } & (P1 | P2);

ما أريد تحقيقه هو التخلص من حقل id ولا يزال لدي اتحاد تمييزي. ومع ذلك ، عندما أقوم بعمل Omit<P, 'id'> ما أحصل عليه لم يعد اتحادًا مميّزًا ، تختفي الحقول a و b أثناء مرحلة Exclude .

الأمر المثير للاهتمام ، ببساطة إعادة كتابة نوع P بشيء من هذا القبيل:

type Rewrite<T> { [K in keyof T]: T[K] };

لن يكسر الاتحاد. لذلك فكرت في استخدام الأنواع الشرطية لحذف جزء "الاستبعاد". لذلك كتبت هذا:

type OmitFromUnion<T, X extends keyof T> = { [K in keyof T]: (K extends X ? never : T[K]) };

المنطق في ذهني وراء هذا: إذا كانت إعادة الكتابة يمكن أن تحافظ على اتحاد مميز ، فلنقم بذلك ولكن إذا كان هذا K هو ما نريد حذفه ، اجعله never (بدلاً من استبعاد هذا المفتاح في جزء رسم الخرائط). فشل هذا فشلاً ذريعًا ، ولم يفعل شيئًا بشكل فعال (أنتج اتحادًا تمييزيًا ولكن لا يزال مع هذا الحقل id فيه).

هل لا يزال من المستحيل حتى يكون هناك طريقة للحصول على جميع المفاتيح الممكنة للنقابات التمييزية (لجميع الحالات)؟

aczekajski إذا كنت تريد توزيع Omit على الاتحادات ، فيمكنك تحديده على النحو التالي :

type Omit<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never

الذي يستخدم حقيقة أن الأنواع الشرطية توزع تلقائيًا T في الحالة أعلاه).

يمكنك التحقق من أن ما ورد أعلاه Omit<P, 'id'> يتصرف مثل P1 | P2 .

jcalz حسنًا ، عند قراءة المستندات ، فاتني تمامًا حقيقة أنه يتم توزيعها تلقائيًا على النقابات. في البداية ، يبدو أن هذا T extends any هذا صحيح دائمًا وعلى هذا النحو لا يفعل شيئًا ، لذلك لا أحب حقًا "شعور القرصنة" حول هذا الحل ، ولكن القديم Diff كان أكثر من ذلك من القرصنة.

تلخيصًا ، أليس الحذف المذكور في التعليق أعلاه هو الأفضل من الحل السابق الذي يفرمل النقابات؟

بالمناسبة ، من الممكن الآن الحصول على جميع المفاتيح لجميع حالات النقابات التي تم التمييز ضدها من:

type AllPossibleKeys<T> = T extends any ? keyof T : never;

هل هناك طريقة مع 2.8 Exclude وما إلى ذلك لمنع بصق الخصائص الاختيارية مثل type | undefined هنا؟

type Overwrite<T1, T2> = { [P in Exclude<keyof T1, keyof T2>]: T1[P] } & T2;
type A = Overwrite<{ myOptional?: string, myRequired: number }, { myRequired: string }>;

// compiler complains Property 'myOptional' is missing in type '{ myRequired: string; }' 
let x: A = {
  myRequired: 'hello'
};

@ drew-r لا يمكنك فقط استخدام NonNullable

@ drew-r جرب هذا الحل: https://github.com/Microsoft/TypeScript/issues/12215#issuecomment -378367303

@ drew-r استخدم هذا مقابل Overwrite :

type Overwrite<T1, T2> = Pick<T1, Exclude<keyof T1, keyof T2>> & T2

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

ferdaber ، شكرًا على هذا

يتم التعامل مع Pick بشكل خاص بواسطة المترجم وسيحتفظ بالمعدلات في النوع الأصلي ، بدلاً من تحويلها ، مثل الأنواع المعينة متجانسة الشكل.

يمكن العثور على مزيد من المعلومات في المستندات :

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

type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>

تعمل الأنواع غير المتجانسة بشكل أساسي على إنشاء خصائص جديدة ، لذا لا يمكنها نسخ معدّلات الخصائص من أي مكان.

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

أعلم أن هذا قد تم حذفه من قاعدة كود TypeScript ، لكنني أرى نفسي وزملائي بحاجة إلى هذا بشكل أسبوعي تقريبًا وفي كل مشروع جديد. نحن نستخدمه فقط _ كثيرًا _ ولأنه يشبه "عكس" Pick أشعر أكثر فأكثر أن هذا يجب أن يتم شحنه مع TypeScript افتراضيًا. من الصعب بشكل خاص على مطوري TS الجدد الذين يرون Pick ويبحثون عن Omit ولا يتعلق الأمر الآن بخيط GitHub هذا.

هل توجد طريقة لإزالة نوع عريض من نوع توحيد بدون إزالة الأنواع الفرعية أيضًا؟

export interface JSONSchema4 {
  id?: string
  $ref?: string
  // to allow third party extensions
  [k: string]: any
}
type KnownProperties = Exclude<keyof JSONSchema4, string | number>
// I want to end up with
type KnownProperties = 'id' | 'ref'
// But, somewhat understandably, get this
type KnownProperties = never
// yet it seem so very within reach
type Keys = keyof JSONSchema4 // string | number | 'id' | 'ref'

أيضا على المكدس .

جرب هذا:

type KnownKeys<T> = {
    [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends {[_ in keyof T]: infer U} ? U : never;

image
أنا عليه ، كابتن!

ferdaber ، لقد
هل هذا يعتمد إلى حد ما على الاختراق الأصلي Diff ؟
لم أفكر حتى في تجربة الأنواع الشرطية هنا.
أرى أنه يستند إلى حقيقة أن string يمتد string (تمامًا كما 'a' يمتد string ) لكن string لا يمتد 'a' ، وبالمثل للأرقام.

أولاً ، يقوم بإنشاء نوع معين ، حيث تكون القيمة لكل مفتاح من T:

  • إذا كانت السلسلة توسع المفتاح (المفتاح هو سلسلة ، وليس نوعًا فرعيًا) => أبدًا
  • إذا كان الرقم يمد المفتاح (المفتاح هو رقم وليس نوعًا فرعيًا) => أبدًا
  • آخر ، مفتاح السلسلة الفعلي

بعد ذلك ، تحصل بشكل أساسي على valueof للحصول على اتحاد لجميع القيم:

type ValuesOf<T> = T extends { [_ in keyof T]: infer U } ? U : never

""
اختبار الواجهة {
req: سلسلة
opt: سلسلة
}
اكتب النصف الأول= {
}

اكتب ValuesOf= T يمتد {[_ في مفتاح T]: استنتاج U}؟ U: أبدا
// أو ما يعادله ، نظرًا لأن T هنا ، و T في النصف الأول لهما نفس المفاتيح ،
// يمكننا استخدام T من FirstHalf بدلاً من ذلك:
اكتب SecondHalf= يمتد أولاً {[_ في مفتاح T]: استنتاج U}؟ U: أبدا؛

اكتب أ = النصف الأول
//انتاج:
اكتب أ = {
[x: string]: مطلقًا ؛
req: "req" ؛
اختيار ؟: "اختيار" | غير معرف؛
}
اكتب a2 = ValuesOf // "req" |
// "req" |

// استبدال ، لإنشاء تعريف نوع واحد ، نحصل على حل ferdaber :
اكتب KnownKeys= {
} يمتد {[_ in keyof T]: استنتاج U}؟ U: أبدا؛
// النوع ب = المفاتيح المعروفة// "مطلوب" | "اختيار" // رائع للغاية!

ferdaber هذا مذهل. تكمن الحيلة في كيفية عمل infer ... يبدو أنه يتكرر عبر جميع المفاتيح ، سواء المفاتيح "المعروفة" (التي أسميها "الحرفية") ومفاتيح الفهرس ، ثم تعطي اتحاد النتائج . هذا يختلف عن عمل T[keyof T] الذي ينتهي فقط باستخراج توقيع الفهرس. عمل جيد جدا.

@ qm3ster. يمكنك بالفعل التمييز بين المفاتيح الاختيارية والمفاتيح المطلوبة التي قد تكون قيمها undefined :

type RequiredKnownKeys<T> = {
    [K in keyof T]: {} extends Pick<T, K> ? never : K
} extends { [_ in keyof T]: infer U } ? ({} extends U ? never : U) : never

type OptionalKnownKeys<T> = {
    [K in keyof T]: string extends K ? never : number extends K ? never : {} extends Pick<T, K> ? K : never
} extends { [_ in keyof T]: infer U } ? ({} extends U ? never : U) : never

التي تنتج

type c = RequiredKnownKeys<test> // 'reqButUndefined' | 'req'
type d = OptionalKnownKeys<test> // 'opt'

كل الفضل يعود إلىajafff! https://github.com/Microsoft/TypeScript/issues/25987#issuecomment -408339599

تضمين التغريدة

{} extends Pick<T, K>

لماذا لم أفعل!
أنتم مجموعة من المعجبين هنا أو شيء من هذا القبيل؟

ferdaber بعد فوات الأوان ، لقد
لا يمر عمل جيد بلا عقاب.

مثل donaldpipowitch المشار إليه أعلاه ، هل يمكننا رجاءًا الحصول على Omit في TypeScript أيضًا. أنواع المساعد الحالي مثل Exclude و Pick مفيدة للغاية. أعتقد أن Omit هو أيضًا شيء مفيد جدًا. يمكننا دائمًا إنشاءها بأنفسنا

اكتب حذف= انتقاء>

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

نوع المساعد Omit يحظى بدعم رسمي بدءًا من TypeScript 3.5
https://devblogs.microsoft.com/typescript/announcing-typescript-3-5/#the -omit-helper-type

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

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

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

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

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

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

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