Typescript: الأنواع الدقيقة

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

هذا اقتراح لتمكين بناء جملة لأنواع دقيقة. يمكن رؤية ميزة مماثلة في Flow (https://flowtype.org/docs/objects.html#exact-object-types) ، لكنني أود أن أقترحها كميزة مستخدمة في النوع الحرفي وليس الواجهات. الصيغة المحددة التي سأقترح استخدامها هي الأنبوب (الذي يعكس تقريبًا تنفيذ التدفق ، ولكن يجب أن يحيط ببيان النوع) ، لأنه مألوف مثل بناء الجملة الرياضي المطلق.

interface User {
  username: string
  email: string
}

const user1: User = { username: 'x', email: 'y', foo: 'z' } //=> Currently errors when `foo` is unknown.
const user2: Exact<User> = { username: 'x', email: 'y', foo: 'z' } //=> Still errors with `foo` unknown.

// Primary use-case is when you're creating a new type from expressions and you'd like the
// language to support you in ensuring no new properties are accidentally being added.
// Especially useful when the assigned together types may come from other parts of the application 
// and the result may be stored somewhere where extra fields are not useful.

const user3: User = Object.assign({ username: 'x' }, { email: 'y', foo: 'z' }) //=> Does not currently error.
const user4: Exact<User> = Object.assign({ username: 'x' }, { email: 'y', foo: 'z' }) //=> Will error as `foo` is unknown.

سيكون هذا التغيير في بناء الجملة ميزة جديدة ويؤثر على ملفات التعريف الجديدة التي تتم كتابتها إذا تم استخدامها كمعامل أو نوع مكشوف. يمكن دمج بناء الجملة هذا مع أنواع أخرى أكثر تعقيدًا.

type Foo = Exact<X> | Exact<Y>

type Bar = Exact<{ username: string }>

function insertIntoDb (user: Exact<User>) {}

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

تحرير: تم تحديث هذا المنشور لاستخدام اقتراح بناء الجملة المفضل المذكور في https://github.com/Microsoft/TypeScript/issues/12936#issuecomment -267272371 ، والذي يشمل استخدام بناء جملة أبسط مع نوع عام لتمكين الاستخدام في التعبيرات.

Awaiting More Feedback Suggestion

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

تحدثنا عن هذا لفترة طويلة. سأحاول تلخيص المناقشة.

فحص الممتلكات الزائدة

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

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

جميع الأنواع الاختيارية

تتعلق بـ EPC مشكلة جميع الأنواع الاختيارية (والتي أسميها الأنواع "الضعيفة"). على الأرجح ، قد ترغب جميع الأنواع الضعيفة في أن تكون دقيقة. يجب علينا فقط تنفيذ الكشف عن النوع الضعيف (# 7485 / # 3842) ؛ المانع الوحيد هنا هو أنواع التقاطع التي تتطلب بعض التعقيد الإضافي في التنفيذ.

من هو نوع بالضبط؟

أول مشكلة رئيسية نراها مع الأنواع الدقيقة هي أنه من غير الواضح حقًا أي الأنواع يجب تمييزها بدقة.

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

من الواضح أنه يجب وضع علامة على وظائف "الإلقاء إذا أعطيت بيانات إضافية" على أنها تقبل الأنواع الدقيقة. لكن ماذا عن الوسط؟ من المرجح أن يختلف الناس. Point2D / Point3D مثالًا جيدًا - قد تقول بشكل معقول أن الوظيفة magnitude يجب أن تحتوي على النوع (p: exact Point2D) => number لمنع تجاوز Point3D . ولكن لماذا لا يمكنني تمرير الكائن { x: 3, y: 14, units: 'meters' } إلى هذه الوظيفة؟ هذا هو المكان الذي يأتي فيه EPC - فأنت تريد اكتشاف هذه الخاصية "الإضافية" units في المواقع التي يتم تجاهلها فيها بالتأكيد ، ولكن لا تحظر المكالمات التي تتضمن الاسم المستعار.

انتهاكات الافتراضات / مشاكل التماثل

لدينا بعض المبادئ الأساسية التي قد تبطلها الأنواع الدقيقة. على سبيل المثال ، يُفترض أن النوع T & U قابل دائمًا للتخصيص إلى T ، لكن هذا يفشل إذا كان T النوع الدقيق. هذا يمثل مشكلة لأنه قد يكون لديك بعض الوظائف العامة التي تستخدم هذا المبدأ T & U -> T ، لكن استدعاء الوظيفة مع T إنشاء مثيل لها بنوع دقيق. لذلك لا توجد طريقة يمكننا من خلالها إصدار هذا الصوت (ليس من المقبول حقًا الخطأ في إنشاء مثيل) - ليس بالضرورة مانعًا ، ولكن من المربك أن تكون وظيفة عامة أكثر تساهلاً من إصدار تم إنشاء مثيل له يدويًا!

يُفترض أيضًا أن T قابل للتخصيص دائمًا لـ T | U ، لكن ليس من الواضح كيفية تطبيق هذه القاعدة إذا كان U النوع الدقيق. هل يمكن { s: "hello", n: 3 } لـ { s: string } | Exact<{ n: number }> ؟ تبدو الإجابة "نعم" كإجابة خاطئة لأن من يبحث عن n ووجد أنه لن يكون سعيدًا برؤية s ، ولكن يبدو أيضًا أن "لا" خطأ لأننا انتهكنا T -> T | U الأساسي

المنوعات

ما معنى function f<T extends Exact<{ n: number }>(p: T) ؟ :مشوش:

غالبًا ما تكون الأنواع الدقيقة مطلوبة حيث يكون ما تريده حقًا هو اتحاد "مفكك تلقائيًا". بمعنى آخر ، قد يكون لديك واجهة برمجة تطبيقات يمكنها قبول { type: "name", firstName: "bob", lastName: "bobson" } أو { type: "age", years: 32 } لكنك لا تريد قبول { type: "age", years: 32, firstName: 'bob" } لأن شيئًا لا يمكن التنبؤ به سيحدث. يمكن القول إن النوع "الصحيح" هو { type: "name", firstName: string, lastName: string, age: undefined } | { type: "age", years: number, firstName: undefined, lastName: undefined } ولكنه جيد مزعج للكتابة. يمكننا التفكير في السكر لصنع أنواع مثل هذه.

ملخص: حالات الاستخدام المطلوبة

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

ال 171 كومينتر

أود أن أقترح أن بناء الجملة قابل للنقاش هنا. نظرًا لأن TypeScript يتيح الآن توجيه الأنبوب لنوع الاتحاد.

class B {}

type A = | number | 
B

يجمع الآن ويعادل type A = number | B ، بفضل الإدراج التلقائي للفاصلة المنقوطة.

أعتقد أن هذا قد لا أتوقعه إذا تم تقديم النوع الدقيق.

لست متأكدًا مما إذا كان realted ولكن لمعلوماتك https://github.com/Microsoft/TypeScript/issues/7481

إذا تم اعتماد بناء الجملة {| ... |} ، فيمكننا البناء على الأنواع المعينة حتى تتمكن من الكتابة

type Exact<T> = {|
    [P in keyof T]: P[T]
|}

وبعد ذلك يمكنك كتابة Exact<User> .

ربما يكون هذا هو آخر شيء أفتقده من Flow ، مقارنةً بـ TypeScript.

المثال Object.assign جيد بشكل خاص. أنا أفهم سبب تصرفات TypeScript بالطريقة التي تعمل بها اليوم ، ولكن في معظم الأوقات أفضل أن يكون لدي النوع الدقيق.

تضمين التغريدة لقد ذكرت مشكلتي الأولية ذلك ، لكنني حذفته في النهاية لأن أي شخص سيكون لديه بناء جملة أفضل على أي حال ، اتضح أنهم يفعلون ذلك 😄

DanielRosenwasser هذا يبدو أكثر منطقية ، شكرًا!

wallverb لا أعتقد ذلك ، على الرغم من أنني أرغب أيضًا في رؤية هذه الميزة موجودة 😄

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

|Type1| | |Type2| | Type3 | |Type4| | Type5 | |Type6|

هل يمكنك أن تعرف بسرعة أعضاء الاتحاد غير الدقيقين؟

وبدون تباعد دقيق؟

|Type1|||Type2||Type3||Type4||Type5||Type6|

(الإجابة: Type3 ، Type5 )

rotemdan انظر الإجابة أعلاه ، هناك النوع العام Extact بدلاً من ذلك وهو اقتراح أكثر صلابة من عرضي. أعتقد أن هذا هو النهج المفضل.

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

أجد صعوبة في تصديق أن بناء الجملة هذا قد تم قبوله في لغة برمجة مثل Flow ، والتي لديها اتحادات بنفس بناء الجملة مثل Typescript. بالنسبة لي ، لا يبدو من الحكمة أن أعرض تركيبًا معيبًا يتعارض جوهريًا مع القواعد اللغوية الموجودة ، ثم نحاول جاهدًا "تغطيتها".

أحد البدائل المثيرة للاهتمام (مسلية؟) هو استخدام معدل مثل only . كان لدي مسودة لاقتراح لهذا منذ عدة أشهر ، على ما أعتقد ، لكنني لم أرسله أبدًا:

function test(a: only string, b: only User) {};

كان هذا أفضل بناء جملة يمكن أن أجده في ذلك الوقت.

_Edit_: قد يعمل أيضًا just ؟

function test(a: just string, b: just User) {};

_ (تعديل: الآن بعد أن أذكر أن بناء الجملة كان في الأصل معدلاً للأنواع الاسمية ، لكن أعتقد أنه لا يهم حقًا .. المفهومان قريبان بدرجة كافية لذا قد تعمل هذه الكلمات الرئيسية هنا أيضًا) _

كنت أتساءل ، ربما يمكن تقديم كلتا الكلمتين الرئيسيتين لوصف نوعين مختلفين قليلاً من المطابقة:

  • just T (بمعنى: "بالضبط T ") للمطابقة الهيكلية التامة ، كما هو موضح هنا.
  • only T (بمعنى: "فريد T ") للمطابقة الاسمية.

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

أنا شخصياً لا أعتقد أن الاختلاف الدقيق سيخلق هذا القدر من الارتباك ، على الرغم من أنني أشعر أن الأمر متروك لفريق Typescript ليقرر ما إذا كان مفهوم المعدل الاسمي مثل only يبدو مناسبًا لهم. أنا أقترح هذا كخيار فقط.

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

يبدو هذا نوعًا ما مثل أنواع الطرح المقنعة. قد تكون هذه المشكلات ذات صلة: https://github.com/Microsoft/TypeScript/issues/4183 https://github.com/Microsoft/TypeScript/issues/7993

@ ethanresnick لماذا تصدق ذلك؟

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

(ربما أخطاء أخرى ولكن ليس هذا الخطأ بالذات 😉)

لا أحب تركيب الأنبوب المستوحى من Flow. شيء مثل الكلمة الرئيسية exact خلف الواجهات سيكون أسهل في القراءة.

exact interface Foo {}

@ mohsen1 أنا متأكد من أن معظم الناس سيستخدمون النوع العام Exact في مواضع التعبير ، لذلك لا ينبغي أن يكون الأمر مهمًا للغاية. ومع ذلك ، سأكون مهتمًا بمقترح من هذا القبيل لأنك قد تفرط قبل الأوان في تحميل الجانب الأيسر من الكلمة الأساسية للواجهة والتي تم حجزها مسبقًا للتصدير فقط (بما يتوافق مع قيم JavaScript - على سبيل المثال export const foo = {} ). يشير أيضًا إلى أنه ربما تكون هذه الكلمة الرئيسية متاحة للأنواع أيضًا (على سبيل المثال exact type Foo = {} وستكون الآن export exact interface Foo {} ).

باستخدام بنية {| |} كيف يعمل extends ؟ هل سيكون interface Bar extends Foo {| |} مطابقًا إذا لم يكن Foo دقيقًا؟

أعتقد أن الكلمة الرئيسية exact تجعل من السهل معرفة ما إذا كانت الواجهة دقيقة. يمكن (يجب) العمل مقابل type أيضًا.

interface Foo {}
type Bar = exact Foo

مفيد للغاية للأشياء التي تعمل عبر قواعد البيانات أو مكالمات الشبكة إلى قواعد البيانات أو مجموعات SDK مثل AWS SDK والتي تأخذ كائنات بجميع الخصائص الاختيارية حيث يتم تجاهل البيانات الإضافية بصمت ويمكن أن تؤدي إلى صعوبة العثور على الأخطاء: rose:

@ mohsen1 يبدو هذا السؤال غير ذي صلة Foo دقيقًا أم لا.

يبدو استخدام الكلمة الرئيسية exact غامضًا - أنت تقول إنه يمكن استخدامها مثل exact interface Foo {} أو type Foo = exact {} ؟ ماذا يعني exact Foo | Bar ؟ إن استخدام النهج العام والعمل مع الأنماط الحالية يعني أنه لا يلزم إعادة اختراع أو تعلم. إنه فقط interface Foo {||} (هذا هو الشيء الجديد الوحيد هنا) ، ثم type Foo = Exact<{}> و Exact<Foo> | Bar .

تحدثنا عن هذا لفترة طويلة. سأحاول تلخيص المناقشة.

فحص الممتلكات الزائدة

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

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

جميع الأنواع الاختيارية

تتعلق بـ EPC مشكلة جميع الأنواع الاختيارية (والتي أسميها الأنواع "الضعيفة"). على الأرجح ، قد ترغب جميع الأنواع الضعيفة في أن تكون دقيقة. يجب علينا فقط تنفيذ الكشف عن النوع الضعيف (# 7485 / # 3842) ؛ المانع الوحيد هنا هو أنواع التقاطع التي تتطلب بعض التعقيد الإضافي في التنفيذ.

من هو نوع بالضبط؟

أول مشكلة رئيسية نراها مع الأنواع الدقيقة هي أنه من غير الواضح حقًا أي الأنواع يجب تمييزها بدقة.

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

من الواضح أنه يجب وضع علامة على وظائف "الإلقاء إذا أعطيت بيانات إضافية" على أنها تقبل الأنواع الدقيقة. لكن ماذا عن الوسط؟ من المرجح أن يختلف الناس. Point2D / Point3D مثالًا جيدًا - قد تقول بشكل معقول أن الوظيفة magnitude يجب أن تحتوي على النوع (p: exact Point2D) => number لمنع تجاوز Point3D . ولكن لماذا لا يمكنني تمرير الكائن { x: 3, y: 14, units: 'meters' } إلى هذه الوظيفة؟ هذا هو المكان الذي يأتي فيه EPC - فأنت تريد اكتشاف هذه الخاصية "الإضافية" units في المواقع التي يتم تجاهلها فيها بالتأكيد ، ولكن لا تحظر المكالمات التي تتضمن الاسم المستعار.

انتهاكات الافتراضات / مشاكل التماثل

لدينا بعض المبادئ الأساسية التي قد تبطلها الأنواع الدقيقة. على سبيل المثال ، يُفترض أن النوع T & U قابل دائمًا للتخصيص إلى T ، لكن هذا يفشل إذا كان T النوع الدقيق. هذا يمثل مشكلة لأنه قد يكون لديك بعض الوظائف العامة التي تستخدم هذا المبدأ T & U -> T ، لكن استدعاء الوظيفة مع T إنشاء مثيل لها بنوع دقيق. لذلك لا توجد طريقة يمكننا من خلالها إصدار هذا الصوت (ليس من المقبول حقًا الخطأ في إنشاء مثيل) - ليس بالضرورة مانعًا ، ولكن من المربك أن تكون وظيفة عامة أكثر تساهلاً من إصدار تم إنشاء مثيل له يدويًا!

يُفترض أيضًا أن T قابل للتخصيص دائمًا لـ T | U ، لكن ليس من الواضح كيفية تطبيق هذه القاعدة إذا كان U النوع الدقيق. هل يمكن { s: "hello", n: 3 } لـ { s: string } | Exact<{ n: number }> ؟ تبدو الإجابة "نعم" كإجابة خاطئة لأن من يبحث عن n ووجد أنه لن يكون سعيدًا برؤية s ، ولكن يبدو أيضًا أن "لا" خطأ لأننا انتهكنا T -> T | U الأساسي

المنوعات

ما معنى function f<T extends Exact<{ n: number }>(p: T) ؟ :مشوش:

غالبًا ما تكون الأنواع الدقيقة مطلوبة حيث يكون ما تريده حقًا هو اتحاد "مفكك تلقائيًا". بمعنى آخر ، قد يكون لديك واجهة برمجة تطبيقات يمكنها قبول { type: "name", firstName: "bob", lastName: "bobson" } أو { type: "age", years: 32 } لكنك لا تريد قبول { type: "age", years: 32, firstName: 'bob" } لأن شيئًا لا يمكن التنبؤ به سيحدث. يمكن القول إن النوع "الصحيح" هو { type: "name", firstName: string, lastName: string, age: undefined } | { type: "age", years: number, firstName: undefined, lastName: undefined } ولكنه جيد مزعج للكتابة. يمكننا التفكير في السكر لصنع أنواع مثل هذه.

ملخص: حالات الاستخدام المطلوبة

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

المكان الرئيسي الذي أراه الناس يتفاجأون بعدم وجود نوع محدد من العناصر هو سلوك Object.keys و for..in - إنهم ينتجون دائمًا نوع string بدلاً من 'a'|'b' لشيء كتبه { a: any, b: any } .

كما ذكرت في https://github.com/Microsoft/TypeScript/issues/14094 ووصفت في قسم Miscellany ، من المزعج أن {first: string, last: string, fullName: string} يتوافق مع {first: string; last: string} | {fullName: string} .

على سبيل المثال ، يُفترض أن النوع T & U قابل دائمًا للتخصيص إلى T ، لكن هذا يفشل إذا كان T من النوع الدقيق

إذا كان T النوع الدقيق ، فمن المفترض أن يكون T & U هو never (أو T === U ). حق؟

أو U مجموعة فرعية غير متطابقة من T

حالة الاستخدام التي قادتني إلى هذا الاقتراح هي مخفضات إعادة الإرسال.

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>): State {
   return {
       ...state,
       fullName: action.payload // compiles, but it's an programming mistake
   }
}

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

هل أفهم بشكل صحيح أن تعيين قيمة T إلى Exact<T> سيكون خطأ؟

interface Dog {
    name: string;
    isGoodBoy: boolean;
}
let a: Dog = { name: 'Waldo', isGoodBoy: true };
let b: Exact<Dog> = a;

في هذا المثال ، تضييق Dog إلى Exact<Dog> لن يكون آمنًا ، أليس كذلك؟
ضع في اعتبارك هذا المثال:

interface PossiblyFlyingDog extends Dog {
    canFly: boolean;
}
let c: PossiblyFlyingDog = { ...a, canFly: true };
let d: Dog = c; // this is okay
let e: Exact<Dog> = d; // but this is not

leonadler نعم ، ستكون هذه هي الفكرة. يمكنك فقط تخصيص Exact<T> لـ Exact<T> . حالة الاستخدام الفوري الخاصة بي هي أن وظائف التحقق من الصحة ستتعامل مع أنواع Exact (على سبيل المثال ، أخذ حمولات الطلب كـ any وإخراج Exact<T> صالح). ومع ذلك ، يمكن Exact<T> إلى T .

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

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

لقد اصطدمت بنفس المشكلة واكتشفت هذا الحل الذي يعد حلاً بديلاً رائعًا بالنسبة لي :)

export type State = {
  readonly counter: number,
  readonly baseCurrency: string,
};

// BAD
export function badReducer(state: State = initialState, action: Action): State {
  if (action.type === INCREASE_COUNTER) {
    return {
      ...state,
      counterTypoError: state.counter + 1, // OK
    }; // it's a bug! but the compiler will not find it 
  }
}

// GOOD
export function goodReducer(state: State = initialState, action: Action): State {
  let partialState: Partial<State> | undefined;

  if (action.type === INCREASE_COUNTER) {
    partialState = {
      counterTypoError: state.counter + 1, // Error: Object literal may only specify known properties, and 'counterTypoError' does not exist in type 'Partial<State>'. 
    }; // now it's showing a typo error correctly 
  }
  if (action.type === CHANGE_BASE_CURRENCY) {
    partialState = { // Error: Types of property 'baseCurrency' are incompatible. Type 'number' is not assignable to type 'string'.
      baseCurrency: 5,
    }; // type errors also works fine 
  }

  return partialState != null ? { ...state, ...partialState } : state;
}

يمكنك العثور على المزيد في هذا القسم من دليل redux الخاص بي:

لاحظ أنه يمكن حل هذا في userland باستخدام اقتراح أنواع القيد الخاص بي (# 13257):

type Exact<T> = [
    case U in U extends T && T extends U: T,
];

تحرير: بناء الجملة المحدث المتعلق بالاقتراح

piotrwitek شكرًا لك ، تعمل الخدعة الجزئية بشكل مثالي ووجدت بالفعل خطأ في قاعدة الشفرة الخاصة بي ؛) وهذا يستحق الرمز المعياري الصغير. لكن ما زلت أتفق مع isiahmeadows على أن بالضبطسيكون أفضل

piotrwitek باستخدام Partial مثل هذا _ تقريبًا_ حل مشكلتي ، لكنه لا يزال يسمح للخصائص بأن تصبح غير محددة حتى لو لم تكن واجهة الدولة محارًا (أفترض صارم NullChecks).

انتهى بي الأمر بشيء أكثر تعقيدًا بعض الشيء للحفاظ على أنواع الواجهة:

export function updateWithPartial<S extends object>(current: S, update: Partial<S>): S {
    return Object.assign({}, current, update);
}

export function updateWith<S extends object, K extends keyof S>(current: S, update: {[key in K]: S[key]}): S {
    return Object.assign({}, current, update);
}

interface I {
    foo: string;
    bar: string;
}

const f: I = {foo: "a", bar: "b"}
updateWithPartial(f, {"foo": undefined}).foo.replace("a", "x"); // Compiles, but fails at runtime
updateWith(f, {foo: undefined}).foo.replace("a", "x"); // Does not compile
updateWith(f, {foo: "c"}).foo.replace("a", "x"); // Compiles and works

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

export const CHANGE_BASE_CURRENCY = 'CHANGE_BASE_CURRENCY';

export const actionCreators = {
  changeBaseCurrency: (payload: string) => ({
    type: CHANGE_BASE_CURRENCY as typeof CHANGE_BASE_CURRENCY, payload,
  }),
}

store.dispatch(actionCreators.changeBaseCurrency()); // Error: Supplied parameters do not match any signature of call target.
store.dispatch(actionCreators.changeBaseCurrency(undefined)); // Argument of type 'undefined' is not assignable to parameter of type 'string'.
store.dispatch(actionCreators.changeBaseCurrency('USD')); // OK => { type: "CHANGE_BASE_CURRENCY", payload: 'USD' }

DEMO - قم بتمكين strictNullChecks في الخيارات

يمكنك أيضًا إنشاء حمولة فارغة إذا كنت بحاجة إلى ذلك ، يمكنك قراءة المزيد في دليلي: https://github.com/piotrwitek/react-redux-typescript-guide#actions

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

عرض

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

المساواة تحت --strictTypes :

type A = { x: number, y: string };
type B = { x: number, y: string, ...restB: <T>T };
type C = { x: number, y: string, z: boolean, ...restC: <T>T };

declare const a: A;
declare const b: B;
declare const c: C;

a = b; // Error, type B has extra property: "restB"
a = c; // Error, type C has extra properties: "z", "restC"
b = a; // OK, restB inferred as {}
b = c; // OK, restB inferred as { z: boolean, ...restC: <T>T }

c = a; // Error, type A is missing property: "z"
       // restC inferred as {}

c = b; // Error, type B is missing property: "z"
       // restC inferred as restB 

إذا --strictTypes لا يتم تبديل على ...rest: <T>T الممتلكات يضاف تلقائيا على نوع A . بهذه الطريقة لن يكون السطران a = b; و a = c; أخطاء ، كما هو الحال مع المتغير b في السطرين التاليين.

كلمة عن مخالفات الفرضيات

من المفترض أن يكون النوع T & U قابلاً للتخصيص دائمًا لـ T ، ولكن هذا يفشل إذا كان T من النوع الدقيق.

نعم ، يسمح & بالمنطق الزائف ، لكن هذا هو الحال مع string & number . كلا النوعين string و number نوعان صلبان مميزان لا يمكن تقاطعهما ، لكن نظام النوع يسمح بذلك. الأنواع الدقيقة صلبة أيضًا ، لذا فإن التناقض لا يزال ثابتًا. تكمن المشكلة في عامل التشغيل & - إنه غير سليم.

هل {s: "hello"، n: 3} قابل للتخصيص إلى {s: string} | بالضبط <{n: number}>.

يمكن ترجمة ذلك إلى:

type Test = { s: string, ...rest: <T>T } | { n: number }
const x: Test = { s: "hello", n: 3 }; // OK, s: string; rest inferred as { n: number }

لذا يجب أن تكون الإجابة "نعم". من غير الآمن توحيد الأنواع غير الدقيقة ، لأن الأنواع غير الدقيقة تستوعب جميع الأنواع الدقيقة ما لم تكن خاصية التمييز موجودة.

رد: الوظيفة f<T extends Exact<{ n: number }>(p: T) في تعليق RyanCavanaugh أعلاه ، في إحدى

const checkType = <T>() => <U extends Exact<T>>(value: U) => value;

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

إليك مثال مفتعل قليلاً مع ثلاث محاولات من محاولاتي الفاشلة لتلبية كلا المطلبين:

  1. لا توجد خصائص زائدة فيما يتعلق بـ CorrectObject
  2. قابل للتعيين إلى HasX بدون تحديد HasX كنوع الكائن
type AllowedFields = "x" | "y";
type CorrectObject = {[field in AllowedFields]?: number | string};
type HasX = { x: number };

function objectLiteralAssignment() {
  const o: CorrectObject = {
    x: 1,
    y: "y",
    // z: "z" // z is correctly prevented to be defined for o by Excess Properties rules
  };

  const oAsHasX: HasX = o; // error: Types of property 'x' are incompatible.
}

function objectMultipleAssignment() {
  const o = {
    x: 1,
    y: "y",
    z: "z",
  };
  const o2 = o as CorrectObject; // succeeds, but undesirable property z is allowed

  type HasX = { x: number };

  const oAsHasX: HasX = o; // succeeds
}

function genericExtends() {
  const checkType = <T>() => <U extends T>(value: U) => value;
  const o = checkType<CorrectObject>()({
    x: 1,
    y: "y",
    z: "z", // undesirable property z is allowed
  }); // o is inferred to be { x: number; y: string; z: string; }

  type HasX = { x: number };

  const oAsHasX: HasX = o; // succeeds
}

هنا HasX هو نوع مبسط إلى حد كبير (خرائط النوع الفعلي o مقابل نوع المخطط) والذي يتم تعريفه في طبقة مختلفة عن الثابت نفسه ، لذلك لا يمكنني إنشاء نوع o لتكون ( CorrectObject & HasX ).

مع الأنواع الدقيقة ، سيكون الحل:

function exactTypes() {
  const checkType = <T>() => <U extends Exact<T>>(value: U) => value;
  const o = checkType<CorrectObject>()({
    x: 1,
    y: "y",
    // z: "z", // undesirable property z is *not* allowed
  }); // o is inferred to be { x: number; y: string; }

  type HasX = { x: number };

  const oAsHasX: HasX = o; // succeeds
}

@ andy-ms

إذا كان T من النوع الدقيق ، فمن المفترض أن T & U ليس أبدًا (أو T === U). حق؟

أعتقد أن T & U يجب أن يكون never فقط إذا كان U غير متوافق مع T ، على سبيل المثال إذا كان T هو Exact<{x: number | string}> و U هو {[field: string]: number} ، ثم T & U يجب أن يكون Exact<{x: number}>

انظر الرد الأول على ذلك:

أو U هي مجموعة فرعية غير دقيقة من T.

أود أن أقول ، إذا تم تعيين U إلى T ، فعندئذٍ T & U === T . ولكن إذا كان T و U من الأنواع الدقيقة المختلفة ، فإن T & U === never .

في المثال الخاص بك ، لماذا من الضروري أن يكون لديك وظيفة checkType لا تفعل شيئًا؟ لماذا لا يكون لديك const o: Exact<CorrectObject> = { ... } ؟

لأنه يفقد المعلومات التي تشير إلى وجود x بالتأكيد (اختياري في CorrectObject) وهو رقم (رقم | سلسلة في CorrectObject). أو ربما أساء فهم ما تعنيه Exact ، اعتقدت أنه سيمنع الخصائص الدخيلة فقط ، وليس أنه سيعني بشكل متكرر أن جميع الأنواع يجب أن تكون متطابقة تمامًا.

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

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

@ andy-ms

أود أن أقول ، إذا كانت U قابلة للتخصيص إلى T ، فعندئذٍ T & U === T. ولكن إذا كان T و U نوعين مختلفين تمامًا ، فعندئذٍ T & U === أبدًا.

عامل التشغيل من النوع & هو عامل تشغيل التقاطع ، والنتيجة هي المجموعة الفرعية المشتركة لكلا الجانبين ، والتي لا تساوي بالضرورة أيضًا. أبسط مثال يمكنني التفكير فيه:

type T = Exact<{ x?: any, y: any }>;
type U = { x: any, y? any };

هنا T & U يجب أن يكون Exact<{ x: any, y: any }> ، وهي مجموعة فرعية من T و U ، لكن لا T مجموعة فرعية من U (مفقود x) ولا U مجموعة فرعية من T (y مفقود).

يجب أن يعمل هذا بشكل مستقل عما إذا كان T أو U أو T & U من الأنواع الدقيقة.

magnushiie لديك نقطة جيدة - يمكن للأنواع الدقيقة أن تحد من Exact<{ x: number | string }> مع Exact<{ x: string | boolean }> لتحصل على Exact<{ x: string }> . تتمثل إحدى المشكلات في أن هذا ليس آمنًا في الواقع إذا لم يكن x للقراءة فقط - فقد نرغب في إصلاح هذا الخطأ للأنواع الدقيقة ، نظرًا لأنها تعني الاشتراك في سلوك أكثر صرامة.

يمكن أيضًا استخدام الأنواع الدقيقة لقضايا علاقات وسيطات النوع لفهرسة التوقيعات.

interface T {
    [index: string]: string;
}

interface S {
    a: string;
    b: string;
}

interface P extends S {
    c: number;
}

declare function f(t: T);
declare function f2(): P;
const s: S = f2();

f(s); // Error because an interface can have more fields that is not conforming to an index signature
f({ a: '', b: '' }); // No error because literals is exact by default

إليك طريقة مخترقة للتحقق من النوع الدقيق:

// type we'll be asserting as exact:
interface TextOptions {
  alignment: string;
  color?: string;
  padding?: number;
}

// when used as a return type:
function getDefaultOptions(): ExactReturn<typeof returnValue, TextOptions> {
  const returnValue = { colour: 'blue', alignment: 'right', padding: 1 };
  //             ERROR: ^^ Property 'colour' is missing in type 'TextOptions'.
  return returnValue
}

// when used as a type:
function example(a: TextOptions) {}
const someInput = {padding: 2, colour: '', alignment: 'right'}
example(someInput as Exact<typeof someInput, TextOptions>)
  //          ERROR: ^^ Property 'colour' is missing in type 'TextOptions'.

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

فيما يلي الأدوات المساعدة المطلوبة لإنجاحها (بفضل

type Exact<A, B extends Difference<A, B>> = AssertPassThrough<Difference<A, B>, A, B>
type ExactReturn<A, B extends Difference<A, B>> = B & Exact<A, B>

type AssertPassThrough<Actual, Passthrough, Expected extends Actual> = Passthrough;
type Difference<A, Without> = {
  [P in DiffUnion<keyof A, keyof Without>]: A[P];
}
type DiffUnion<T extends string, U extends string> =
  ({[P in T]: P } &
  { [P in U]: never } &
  { [k: string]: never })[T];

انظر: الملعب .

هذا لطيف! قد يكونgcanti ( typelevel-ts ) و pelotom ( type-zoo ) مهتمين أيضًا. :)

لأي شخص مهتم ، وجدت طريقة بسيطة لفرض الأنواع الدقيقة على معلمات الوظيفة. يعمل على TS 2.7 ، على الأقل.

function myFn<T extends {[K in keyof U]: any}, U extends DesiredType>(arg: T & U): void;

تحرير: أعتقد أنه لكي يعمل هذا ، يجب عليك تحديد كائن حرفي مباشرة في الحجة ؛ هذا لا يعمل إذا قمت بتعريف ثابت منفصل أعلاه وتمريره بدلاً من ذلك. : / لكن أحد الحلول هو استخدام انتشار الكائن في موقع الاستدعاء ، على سبيل المثال ، myFn({...arg}) .

تحرير: آسف ، لم أقرأ أنك ذكرت TS 2.7 فقط. سأختبره هناك!

vaskevich لا يمكنني تشغيله ، أي أنه لا يكتشف colour كخاصية زائدة :

عندما تصل الأنواع الشرطية إلى الأرض (# 21316) ، يمكنك القيام بما يلي لطلب الأنواع الدقيقة كمعلمات دالة ، حتى مع القيم الحرفية للكائنات "غير الحديثة":

type Exactify<T, X extends T> = T & {
    [K in keyof X]: K extends keyof T ? X[K] : never
}

type Foo = {a?: string, b: number}

declare function requireExact<X extends Exactify<Foo, X>>(x: X): void;

const exact = {b: 1}; 
requireExact(exact); // okay

const inexact = {a: "hey", b: 3, c: 123}; 
requireExact(inexact);  // error
// Types of property 'c' are incompatible.
// Type 'number' is not assignable to type 'never'.

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

const inexact = {a: "hey", b: 3, c: 123} as Foo;
requireExact(inexact);  // okay

أفكار؟

يبدو أنه يتم إحراز تقدم في معلمات الوظيفة. هل وجد أي شخص طريقة لفرض الأنواع الدقيقة لقيمة إرجاع دالة؟

تضمين التغريدة راجع # 241 وهو السبب الجذري لإرجاع الدالة التي لم يتم التحقق منها بشكل صحيح للخصائص الإضافية

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

interface A {
    field: string;
}

interface B {
    field2: string;
    field3?: string;
}

type AorB = A | B;

const fixture: AorB[] = [
    {
        field: 'sfasdf',
        field3: 'asd' // ok?!
    },
];

( ملعب )

يمكن أن يكون الحل الواضح لذلك هو:

type AorB = Exact<A> | Exact<B>;

لقد رأيت حلاً مقترحًا في # 16679 ولكن في حالتي ، النوع AorBorC (قد ينمو) ولكل كائن خصائص متعددة ، لذلك من الصعب جدًا حساب مجموعة من الخصائص fieldX?:never يدويًا لكل نوع.

michalstocki أليس هذا # 20863؟ تريد فحص الممتلكات الزائدة على النقابات لتكون أكثر صرامة.

على أي حال ، في حالة عدم وجود أنواع محددة وفحص صارم للخاصية الزائدة على النقابات ، يمكنك القيام بخصائص fieldX?:never برمجيًا بدلاً من يدويًا باستخدام الأنواع الشرطية :

type AllKeys<U> = U extends any ? keyof U : never
type ExclusifyUnion<U> = [U] extends [infer V] ?
 V extends any ? 
 (V & {[P in Exclude<AllKeys<U>, keyof V>]?: never}) 
 : never : never

ثم حدد نقابتك على أنها

type AorB = ExclusifyUnion<A | B>;

الذي يتوسع إلى

type AorB = (A & {
    field2?: undefined;
    field3?: undefined;
}) | (B & {
    field?: undefined;
})

تلقائيا. إنه يعمل مع أي AorBorC أيضًا.

راجع أيضًا https://github.com/Microsoft/TypeScript/issues/14094#issuecomment -373780463 للحصول على حصري أو للتنفيذ

jcalz النوع المتقدم ExclusifyUnion ليس آمنًا جدًا:

const { ...fields } = o as AorB;

fields.field3.toUpperCase(); // it shouldn't be passed

حقول fields كلها غير اختيارية.

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

من الواضح أن الأمور سوف تتصرف بشكل أفضل إذا قمت بكتابة الحراسة قبل التدمير:

declare function isA(x: any): x is A;
declare function isB(x: any): x is B;

declare const o: AorB;
if (isA(o)) {
  const { ...fields } = o;
  fields.field3.toUpperCase(); // error
} else {
  const { ...fields } = o;
  fields.field3.toUpperCase(); // error
  if (fields.field3) {
    fields.field3.toUpperCase(); // okay
  }
}

لا يعني هذا أن هذا "يصلح" المشكلة التي تراها ، ولكن هكذا أتوقع من شخص ما أن يتصرف مع اتحاد مقيد.

ربما https://github.com/Microsoft/TypeScript/pull/24897 يعمل على إصلاح مشكلة الانتشار

قد أتأخر عن الحفلة ، ولكن إليك كيف يمكنك على الأقل التأكد من تطابق أنواعك تمامًا:

type AreSame<A, B> = A extends B
    ? B extends A ? true : false
    : false;
const sureIsTrue: (fact: true) => void = () => {};
const sureIsFalse: (fact: false) => void = () => {};



declare const x: string;
declare const y: number;
declare const xAndYAreOfTheSameType: AreSame<typeof x, typeof y>;
sureIsFalse(xAndYAreOfTheSameType);  // <-- no problem, as expected
sureIsTrue(xAndYAreOfTheSameType);  // <-- problem, as expected

أتمنى أن أفعل هذا:

type Exact<A, B> = A extends B ? B extends A ? B : never : never;
declare function needExactA<X extends Exact<A, X>>(value: X): void;

هل ستساعد الميزة الموضحة في هذه المشكلة في حالة تطابق واجهة فارغة / مفهرسة مع أنواع تشبه الكائنات ، مثل الوظائف أو الفئات؟

interface MyType
{
    [propName: string]: any;
}

function test(value: MyType) {}

test({});           // OK
test(1);            // Fails, OK!
test('');           // Fails, OK!
test(() => {});     // Does not fail, not OK!
test(console.log);  // Does not fail, not OK!
test(console);      // Does not fail, not OK!

الواجهة MyType تعرف فقط توقيع الفهرس وتستخدم كنوع المعلمة الوحيدة للوظيفة test . تم تمرير المعلمة إلى وظيفة النوع:

  • يمر الكائن الحرفي {} . سلوك متوقع.
  • الثابت الرقمي 1 لا ينجح. السلوك المتوقع (_ الوسيطة من النوع '1' غير قابلة للتخصيص لمعلمة من النوع 'MyType' ._)
  • السلسلة الحرفية '' لا تمر. السلوك المتوقع (_` الوسيطة من النوع '""' غير قابلة للتخصيص لمعلمة من النوع 'MyType' ._)
  • إعلان وظيفة السهم () => {} : يمر. لا يتوقع السلوك. ربما يمر لأن الوظائف هي كائنات؟
  • طريقة الدرجة console.log يمر. لا يتوقع السلوك. على غرار وظيفة السهم.
  • فئة console تمر. لا يتوقع السلوك. ربما لأن الفئات هي كائنات؟

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

نعتذر إذا كان هذا خارج الموضوع. حتى الآن هذه المشكلة هي أقرب تطابق للمشكلة التي شرحتها أعلاه.

@ Janne252 يمكن أن يساعدك هذا الاقتراح بشكل غير مباشر. بافتراض أنك جربت Exact<{[key: string]: any}> الواضح ، فإليك سبب نجاحها:

  • تمر العناصر الحرفية للكائن بالشكل المتوقع ، كما هو الحال بالفعل مع {[key: string]: any} .
  • فشلت الثوابت الرقمية كما هو متوقع ، حيث لا يمكن تعيين القيم الحرفية إلى {[key: string]: any} .
  • تفشل السلاسل الحرفية كما هو متوقع ، نظرًا لأنها غير قابلة للتخصيص إلى {[key: string]: any} .
  • تفشل الدالات ومنشآت الفئات بسبب توقيعها call (إنها ليست خاصية سلسلة).
  • يمر الكائن console لأنه مجرد كائن (وليس فئة). لا يفصل JS بين الكائنات وقواميس المفتاح / القيمة ، ولا يختلف TS هنا بصرف النظر عن الكتابة متعددة الأشكال للصفوف المضافة. أيضًا ، لا يدعم TS الأنواع المعتمدة على القيمة ، و typeof هو مجرد سكر لإضافة بعض المعلمات الإضافية و / أو الأسماء المستعارة - إنه ليس سحريًا كما يبدو.

blakeembreymichalstocki @ ألكسي-بيكوف
هذه طريقتي في عمل الأنواع الدقيقة:

type Exact<A extends object> = A & {__kind: keyof A};

type Foo = Exact<{foo: number}>;
type FooGoo = Exact<{foo: number, goo: number}>;

const takeFoo = (foo: Foo): Foo => foo;

const foo = {foo: 1} as Foo;
const fooGoo = {foo: 1, goo: 2} as FooGoo;

takeFoo(foo)
takeFoo(fooGoo) // error "[ts]
//Argument of type 'Exact<{ foo: number; goo: number; }>' is not assignable to parameter of type 'Exact<{ //foo: number; }>'.
//  Type 'Exact<{ foo: number; goo: number; }>' is not assignable to type '{ __kind: "foo"; }'.
//    Types of property '__kind' are incompatible.
//      Type '"foo" | "goo"' is not assignable to type '"foo"'.
//        Type '"goo"' is not assignable to type '"foo"'."

const takeFooGoo = (fooGoo: FooGoo): FooGoo => fooGoo;

takeFooGoo(fooGoo);
takeFooGoo(foo); // error "[ts]
// Argument of type 'Exact<{ foo: number; }>' is not assignable to parameter of type 'Exact<{ foo: number; // goo: number; }>'.
//  Type 'Exact<{ foo: number; }>' is not assignable to type '{ foo: number; goo: number; }'.
//    Property 'goo' is missing in type 'Exact<{ foo: number; }>'.

إنه يعمل مع معلمات الوظائف والمرتجعات وحتى للتأكيدات.
خطأ const foo: Foo = fooGoo; //
لا وقت التشغيل الزائد. المشكلة الوحيدة هي أنه كلما قمت بإنشاء كائن جديد تمامًا ، يجب عليك وضعه مقابل نوعه ، ولكنه ليس مشكلة كبيرة حقًا.

أعتقد أن المثال الأصلي لديه السلوك الصحيح: أتوقع أن يكون interface s مفتوحًا. في المقابل ، أتوقع إغلاق type s (ويتم إغلاقها في بعض الأحيان فقط). فيما يلي مثال على السلوك المفاجئ عند كتابة نوع MappedOmit :
https://gist.github.com/donabrams/b849927f5a0160081db913e3d52cc7b3

النوع MappedOmit في المثال يعمل فقط على النحو المقصود للنقابات التمييزية. بالنسبة للنقابات غير المميّزة ، يتم تمرير Copycript 3.2 عند تمرير أي تقاطع للأنواع في الاتحاد.

الحلول أعلاه باستخدام as TypeX أو as any للإرسال لها تأثير جانبي لإخفاء الأخطاء في البناء !. نريد أن يساعدنا فاحص الكتابة لدينا في اكتشاف الأخطاء في البناء أيضًا! بالإضافة إلى ذلك ، هناك العديد من الأشياء التي يمكننا إنشاؤها بشكل ثابت من أنواع محددة جيدًا. الحلول البديلة مثل ما سبق (أو الحلول البديلة من النوع الاسمي الموصوفة هنا: https://gist.github.com/donabrams/74075e89d10db446005abe7b1e7d9481) توقف تلك المولدات عن العمل (على الرغم من أنه يمكننا تصفية الحقول الرائدة _ ، إنها اتفاقية مؤلمة هذا يمكن تجنبه تمامًا).

@ aleksey-bykov fyi أعتقد أن تنفيذك هو 99٪ من الطريق إلى هناك ، لقد نجح هذا بالنسبة لي:

type AreSame<A, B> = A extends B
    ? B extends A ? true : false
    : false;
type Exact<A, B> = AreSame<A, B> extends true ? B : never;

const value1 = {};
const value2 = {a:1};

// works
const exactValue1: Exact<{}, typeof value1> = value1;
const exactValue1WithTypeof: Exact<typeof value1, typeof value1> = value1;

// cannot assign {a:number} to never
const exactValue1Fail: Exact<{}, typeof value2> = value2;
const exactValue1FailWithTypeof: Exact<typeof value1, typeof value2> = value2;

// cannot assign {} to never
const exactValue2Fail: Exact<{a: number}, typeof value1> = value1;
const exactValue2FailWithTypeof: Exact<typeof value2, typeof value1> = value1;

// works
const exactValue2: Exact<{a: number}, typeof value2> = value2;
const exactValue2WithTypeof: Exact<typeof value2, typeof value2> = value2;

واو ، من فضلك اترك الزهور هنا ، اذهب الهدايا في ذلك الصندوق

أحد التحسينات الصغيرة التي يمكن إجراؤها هنا:
باستخدام التعريف التالي لـ Exact يُنشئ عملية طرح B من A كـ A & never على كل أنواع B مفاتيح فريدة من نوعها

type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
type Exact<A, B = {}> = A & Record<keyof Omit<B, A>, never>;

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

function makeExactVerifyFn<T>() {
  return <C>(x: C & Exact<T, C>): C => x;
}

استخدام العينة:

interface Task {
  title: string;
  due?: Date;
}

const isOnlyTask = makeExactVerifyFn<Task>();

const validTask_1 = isOnlyTask({
    title: 'Get milk',
    due: new Date()  
});

const validTask_2 = isOnlyTask({
    title: 'Get milk'
});

const invalidTask_1 = isOnlyTask({
    title: 5 // [ts] Type 'number' is not assignable to type 'string'.
});

const invalidTask_2 = isOnlyTask({
    title: 'Get milk',
    procrastinate: true // [ts] Type 'true' is not assignable to type 'never'.
});

danielnmsft يبدو غريبًا ترك B في Exact<A, B> اختياريًا في مثالك ، خاصةً إذا كان مطلوبًا للتحقق المناسب. خلاف ذلك ، تبدو جيدة بالنسبة لي. يبدو أنه من الأفضل تسميته Equal ، على الرغم من ذلك.

drabinowitz لا يمثل نوعك Exact الواقع ما تم اقتراحه هنا وربما يجب إعادة تسميته إلى شيء مثل AreExact . أعني ، لا يمكنك فعل هذا بنوعك:

function takesExactFoo<T extends Exact<Foo>>(foo: T) {}

ومع ذلك ، فإن النوع الخاص بك سهل لتنفيذ نوع المعلمة بالضبط!

type AreSame<A, B> = A extends B
    ? B extends A ? true : false
    : false;
type Exact<A, B> = AreSame<A, B> extends true ? B : never;

interface Foo {
    bar: any
}

function takesExactFoo <T>(foo: T & Exact<Foo, T>) {
                    //  ^ or `T extends Foo` to type-check `foo` inside the function
}

let foo = {bar: 123}
let foo2 = {bar: 123, baz: 123}

takesExactFoo(foo) // ok
takesExactFoo(foo2) // error

UPD1 لن يؤدي هذا إلى إنشاء وظيفة وقت تشغيل +1 كما هو الحال في حل danielnmsft وبالطبع أكثر مرونة.

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

لا يبدو أن تعريف AreSame / Exact مناسب لنوع الاتحاد.
مثال: نتائج Exact<'a' | 'b', 'a' | 'b'> never .
من الواضح أنه يمكن إصلاح ذلك من خلال تحديد type AreSame<A, B> = A|B extends A&B ? true : false;

لقد وجد

زوجان من الخيارات الإضافية مما لديك:

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

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>): typeof state {
   return {
       ...state,
       fullName: action.payload        // THIS IS REPORTED AS AN ERROR
   };
}

2 للمخفضات ، بدلاً من المتغير المؤقت ، قم بتعيينه مرة أخرى إلى نفسه قبل العودة:

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>) {
   return (state = {
       ...state,
       fullName: action.payload        // THIS IS REPORTED AS AN ERROR
   });
}

3 إذا كنت تريد بالفعل متغيرًا مؤقتًا ، فلا تعطه نوعًا صريحًا ، فاستخدم typeof state مرة أخرى

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>) {

   const newState: typeof state = {
       ...state,
       fullName: action.payload         // THIS IS REPORTED AS AN ERROR
   };

   return newState;
}

3 ب إذا كان المخفض الخاص بك لا يحتوي على ...state يمكنك استخدام Partial<typeof state> للنوع:

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>) {

   const newState: Partial<typeof state> = {
       name: 'Simon',
       fullName: action.payload         // THIS IS REPORTED AS AN ERROR
   };

   return newState;
}

أشعر أن هذه المحادثة بأكملها (وقد قرأت للتو الخيط بأكمله) فاتت جوهر المشكلة بالنسبة لمعظم الناس وهذا هو أنه لمنع الأخطاء ، كل ما نريده هو تأكيد نوع لمنع عدم السماح بنوع `` أوسع '':

هذا ما قد يجربه الناس أولاً ، والذي لا يمنع "fullName":

 return <State> {
       ...state,
       fullName: action.payload         // compiles ok :-(
   };

هذا لأن <Dog> cat تخبر المترجم - نعم أنا أعرف ما أفعله ، إنه Dog ! أنت لا تطلب الإذن.

إذن ما سيكون أكثر فائدة بالنسبة لي هو إصدار أكثر صرامة من <Dog> cat والذي من شأنه أن يمنع الخصائص الدخيلة:

 return <strict State> {
       ...state,
       fullName: action.payload     // compiles ok :-(
   };

كل شيء من نوع Exact<T> له العديد من العواقب (هذا خيط طويل!). إنه يذكرني بمناقشة "الاستثناءات المحددة" بالكامل حيث يوجد شيء تعتقد أنك تريده ولكن اتضح أنه يحتوي على العديد من المشكلات (مثل فجأة بعد خمس دقائق الرغبة في الحصول على Unexact<T> ).

من ناحية أخرى ، فإن <strict T> سيكون بمثابة حاجز لمنع الأنواع "المستحيلة" من العبور. إنه في الأساس عامل تصفية نوع يمر عبر النوع (كما تم القيام به أعلاه مع وظائف وقت التشغيل).

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

لذا ، إذا كان عليّ تقديم صيغة اقتراح فسيكون هذا:

/// This syntax is ONLY permitted directly in front of an object declaration:
return <strict State> { ...state, foooooooooooooo: 'who' };

بالعودة إلى OP: نظريًا [1] مع الأنواع المرفوضة ، يمكنك كتابة type Exact<T> = T & not Record<not keyof T, any> . ثم Exact<{x: string}> سيمنع أي أنواع ذات مفاتيح غير x من التخصيص إليها. لست متأكدًا مما إذا كان هذا كافياً لتلبية ما يطلبه الجميع هنا ، ولكن يبدو أنه يناسب تمامًا OP.

[1] أقول نظريًا لأن ذلك يعتمد على تواقيع الفهرس الأفضل أيضًا

أشعر بالفضول لمعرفة ما إذا كانت لدي المشكلة الموضحة هنا. لدي كود مثل:

const Layers = {
  foo: 'foo'
  bar: 'bar'
  baz: 'baz'
}

type Groups = {
  [key in keyof Pick<Layers, 'foo' | 'bar'>]: number
}

const groups = {} as Groups

ثم يسمح لي بتعيين خصائص غير معروفة ، وهو ما لا أريده:

groups.foo = 1
groups.bar = 2
groups.anything = 2 // NO ERROR :(

لا يزال إعداد anything يعمل ، ونوع قيمة المفتاح هو any . كنت أتمنى أن يكون ذلك خطأ.

هل هذا ما سيتم حله بهذه المشكلة؟

تبين ، كان يجب أن أفعل

type Groups = {
  [key in keyof Pick<typeof Layers, 'foo' | 'bar'>]: number
}

لاحظ الاستخدام المضاف لـ typeof .

كان المكون الإضافي Atom atom-typescript يحاول جاهدًا ألا يفشل ، وفي النهاية انهار. عندما أضفت typeof ، عادت الأمور إلى طبيعتها ، ولم يعد مسموحًا بالدعائم غير المعروفة وهو ما كنت أتوقعه.

بمعنى آخر ، عندما لم أكن أستخدم typeof ، كان atom-typescript يحاول تحديد النوع في أماكن أخرى من الكود حيث كنت أستخدم كائنات من النوع Groups ، و كان يسمح لي بإضافة دعائم غير معروفة وإظهار تلميح من النوع any لهم.

لذلك لا أعتقد أن لدي مشكلة في هذا الموضوع.

قد يكون من المضاعفات الأخرى كيفية التعامل مع الخصائص الاختيارية.

إذا كان لديك نوع يحتوي على خصائص اختيارية ، فماذا يعني Exact<T> لهذه الخصائص:

export type PlaceOrderResponse = { 
   status: 'success' | 'paymentFailed', 
   orderNumber: string
   amountCharged?: number
};

هل يعني Exact<T> أنه يجب تحديد كل خاصية اختيارية؟ ماذا ستحدده على أنه؟ ليس "غير محدد" أو "فارغ" لأن هذا له تأثير في وقت التشغيل.

هل يتطلب هذا الآن طريقة جديدة لتحديد "معلمة اختيارية مطلوبة"؟

على سبيل المثال ، ما الذي يتعين علينا تعيينه لـ amountCharged في نموذج الكود التالي للحصول عليه لتلبية "دقة" النوع؟ نحن لسنا "دقيقين" للغاية إذا لم نفرض "الاعتراف" بهذه الخاصية على الأقل بطريقة أو بأخرى. هل هو <never> ؟ لا يمكن أن يكون undefined أو null .

const exactOrderResponse: Exact<PlaceOrderResponse> = 
{
   status: 'paymentFailed',
   orderNumber: '1001',
   amountCharged: ????      
};

لذلك قد تفكر - لا يزال اختياريًا ، وهو الآن اختياري تمامًا والذي يُترجم إلى اختياري . وبالتأكيد في وقت التشغيل ، لن يحتاج الأمر إلى ضبطه ، لكن يبدو لي أننا "كسرنا" Exact<T> خلال التمسك بعلامة استفهام.

ربما فقط عند تعيين قيمة بين نوعين يجب إجراء هذا الفحص؟ (لفرض أن كلاهما يتضمن amountCharged?: number )

دعنا نقدم نوعًا جديدًا هنا لبيانات الإدخال لمربع الحوار:

export type OrderDialogBoxData = { 
   status: 'success' | 'paymentFailed', 
   orderNumber: string
   amountCharge?: number      // note the typo here!
};

لذلك دعونا نجرب هذا:

// run the API call and then assign it to a dialog box.
const serverResponse: Exact<PlaceOrderResponse> = await placeOrder();
const dialogBoxData: Exact<OrderDialogBoxData> = serverResponse;    // SHOULD FAIL

أتوقع فشل هذا بالطبع بسبب الخطأ المطبعي - على الرغم من أن هذه الخاصية اختيارية في كليهما.

ثم عدت إلى السؤال "لماذا نريد هذا في المقام الأول؟" .
أعتقد أنه سيكون لهذه الأسباب (أو مجموعة فرعية حسب الموقف):

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

إذا لم يتم التعامل مع "الخصائص الاختيارية الدقيقة" بشكل صحيح ، فسيتم كسر بعض هذه الفوائد أو حدوث ارتباك كبير!

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

أعتقد أن ما أحتاجه غالبًا ليس في الواقع نوع Exact<T> على الإطلاق ، إنه أحد هذين النوعين:

NothingMoreThan<T> أو
NothingLessThan<T>

حيث "مطلوب اختياري" أصبح شيئًا الآن. الأول لا يسمح بتحديد أي شيء إضافي بواسطة RHS للمهمة ، والثاني يتأكد من أن كل شيء (بما في ذلك الخصائص الاختيارية) محدد في RHS للمهمة.

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

NothingLessThan هو نوع ما لدينا بالفعل في الكتابة المطبوعة - لجميع المهام العادية - باستثناء أنه قد يحتاج إلى النظر في خصائص (optional?: number) الاختيارية.

لا أتوقع أن يكون لهذه الأسماء أي تأثير ، لكنني أعتقد أن المفهوم أوضح وأكثر دقة من Exact<T> ...

ثم ربما (إذا كنا بحاجة إليه حقًا):

Exact<T> = NothingMoreThan<NothingLessThan<T>>;

أو سيكون:

Exact<T> = NothingLessThan<NothingMoreThan<T>>;   // !!

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

ملاحظة أخيرة: NothingLessThan / NothingMoreThan لها "إحساس" مشابه لبعض التعليقات أعلاه حيث يمتد النوع A من النوع B ، أو B يمتد من A. لن تتناول الخصائص الاختيارية (على الأقل لا أعتقد أنها تستطيع ذلك اليوم).

simeyla يمكنك الابتعاد عن الخيار "لا شيء أكثر من".

  • "لا شيء أقل من" أنواع عادية فقط. يقوم TS بهذا الأمر ضمنيًا ، ويتم التعامل مع كل نوع على أنه مكافئ لـ for all T extends X: T .
  • "لا شيء أكثر من" هو العكس تمامًا: إنه for all T super X: T ضمنيًا

ستكون طريقة لاختيار أحدهما أو كليهما بشكل صريح كافية. كأثر جانبي، يمكنك تحديد جاوة T super C ك المقترح T extends NothingMoreThan<C> . لذلك أنا مقتنع تمامًا أن هذا ربما يكون أفضل من الأنواع الدقيقة القياسية.

أشعر أن هذا يجب أن يكون بناء الجملة بالرغم من ذلك. ربما هذا؟

  • extends T - اتحاد جميع الأنواع القابلة للتخصيص لـ T ، أي ما يعادل فقط T .
  • super T - اتحاد جميع أنواع T يمكن التنازل إليه.
  • extends super T ، super extends T - اتحاد جميع الأنواع المكافئة لـ T. يقع هذا خارج الشبكة ، حيث يمكن تخصيص النوع فقط وتعيينه لنفسه.
  • type Exact<T> = extends super T - سكر مدمج للحالة الشائعة أعلاه ، للمساعدة في سهولة القراءة.
  • نظرًا لأن هذا يؤدي فقط إلى تبديل قابلية التعيين ، فلا يزال بإمكانك الحصول على أشياء مثل النقابات الدقيقة أو الأنواع الفائقة.

هذا أيضًا يجعل من الممكن تنفيذ # 14094 في userland من خلال جعل كل متغير Exact<T> ، مثل Exact<{a: number}> | Exact<{b: number}> .


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

أتساءل عما إذا كان هذا أيضًا يجعل الأنواع المنفية ممكنة في userland ، منذ (super T) | (يمتد T) يكافئ مجهول. أعتقد أنه كذلك ، لكني سأحتاج إلى إجراء بعض العمليات الحسابية المعقدة أولاً لتأكيد ذلك ، وليس من الواضح تمامًا إثبات ذلك.

بالنسبة إلى (super T) | (extends T) === unknown يجب أن يكون الاحتفاظ بإمكانية التخصيص أمرًا كاملاً.

@ jack-williams التقاط جيد وثابت (عن طريق إزالة المطالبة). كنت أتساءل لماذا لم تكن الأمور تسير على ما يرام في البداية عندما كنت ألعب قليلاً.

@ جاك ويليامز

"لا شيء أقل من" أنواع عادية فقط. يقوم TS بهذا بشكل ضمني ، ويتم التعامل مع كل نوع على أنه مكافئ

نعم و لا. لكن في الغالب نعم ... ... ولكن فقط إذا كنت في وضع strict !

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

حسنًا ، هذا هو بالضبط ما تحصل عليه بـ lastName: string | undefined بينما كنت قد حصلت في الغالب على lastName?: string ، وبالطبع بدون وضع strict لن يتم تحذيرك من كل التناقضات.

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

كنت أحاول جميع أنواع الأشياء للحصول على ما أريد - بما في ذلك اللعب بـ Required<A> extends Required<B> ، ومحاولة إزالة علامات الملكية الاختيارية ? . لقد أرسلني ذلك إلى حفرة أرنب مختلفة تمامًا - (وكان هذا كله قبل أن أقوم بتشغيل وضع strict ).

النقطة المهمة هي أنه إذا كنت تحاول الحصول على شيء قريب من الأنواع "الدقيقة" اليوم ، فأنت بحاجة إلى البدء بتمكين وضع strict (أو أي مجموعة من العلامات تعطي الشيكات الصحيحة). وإذا احتجت إلى إضافة middleName: string | undefined لاحقًا ، فحينئذٍ ، ستجد فجأة كل مكان أحتاج إليه "لأفكر فيه" :-)

ملاحظة. شكرا لتعليقاتكم - كانت مفيدة جدا. أدرك أنني رأيت الكثير من التعليمات البرمجية التي من الواضح أنها لا تستخدم الوضع strict - ثم يصطدم الناس بالجدران كما فعلت. أتساءل ما الذي يمكن فعله لتشجيع استخدامه أكثر؟

simeyla أعتقد أنه يجب توجيه ملاحظاتك وشكرك إلىisiahmeadows!

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

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

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

دراسة بعض حالات الاستخدامات المحتملة للأنواع الدقيقة:

كتابة قوية لـ keys و for ... in .

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

تصلب توسيع خاصية اختيارية.

قاعدة التخصيص { ... } <: { ...; x?: T } غير صحيحة لأن النوع الأيسر قد يتضمن خاصية x غير متوافقة تم تسميتها بعيدًا. عند التعيين من نوع دقيق ، تصبح هذه القاعدة سليمة. من الناحية العملية ، لا أستخدم هذه القاعدة مطلقًا ؛ يبدو أكثر ملاءمة للأنظمة القديمة التي ليس لها أنواع محددة لتبدأ بها.

رد فعل و HOC

كنت قد علقت أملي الأخير على الأنواع الدقيقة لتحسين تمرير الدعائم ، وتبسيط أنواع الانتشار. الحقيقة هي أن الأنواع الدقيقة هي نقيض تعدد الأشكال المحدود ، وهي في الأساس غير تركيبية.

يتيح لك عام مقيد تحديد الدعائم التي تهتم بها ، وتمرير الباقي من خلالها. بمجرد أن يصبح الحد دقيقًا ، تفقد التصنيف الفرعي للعرض تمامًا ويصبح العام أقل فائدة بشكل ملحوظ. مشكلة أخرى هي أن التقاطع هو أحد الأدوات الرئيسية للتكوين في TypeScript ، لكن أنواع التقاطع غير متوافقة مع الأنواع الدقيقة. أي نوع تقاطع غير تافه مع مكون محدد سيكون فارغًا: _ الأنواع الدقيقة لا تؤلف_. للتفاعل والدعائم ، ربما تريد أنواع الصفوف وتعدد الأشكال ، ولكن هذا ليوم آخر.

تقريبًا يتم حل جميع الأخطاء المثيرة للاهتمام التي يمكن حلها بأنواع دقيقة عن طريق التحقق من الملكية الزائدة ؛ المشكلة الأكبر هي أن فحص الملكية الزائد لا يعمل مع النقابات بدون خاصية تمييزية ؛ حل هذه المشكلة وتقريبًا تختفي جميع المشكلات المثيرة للاهتمام ذات الصلة بالأنواع الدقيقة ، IMO.

@ jack-williams أوافق على أنه ليس من المفيد عمومًا أن يكون لديك أنواع محددة. يتم تغطية مفهوم التحقق من الخاصية الزائدة فعليًا من خلال مقترح المشغل super T الخاص بي ، وذلك فقط بشكل غير مباشر لأن اتحاد جميع أنواع T يمكن تخصيصه بشكل ملحوظ لا يتضمن الأنواع الفرعية المناسبة من T.

أنا لا أؤيد بشدة هذا الأمر شخصيًا بصرف النظر عن ربما T super U * ، نظرًا لأن حالة الاستخدام الوحيدة التي واجهتها على الإطلاق لفحص الممتلكات الزائدة كانت التعامل مع خوادم معطلة ، وهو شيء يمكنك عادةً التغلب عليه باستخدام وظيفة المجمع لإنشاء الطلبات يدويًا وإزالة القمامة الزائدة. كل مشكلة أخرى وجدتها تم الإبلاغ عنها في هذا الموضوع حتى الآن يمكن حلها ببساطة عن طريق استخدام اتحاد بسيط مميز.

* سيكون هذا في الأساس T extends super U باستخدام اقتراحي - تكون الحدود السفلية مفيدة في بعض الأحيان لتقييد الأنواع العامة المتعارضة ، وعادة ما تنتهي الحلول البديلة بإدخال الكثير من النوع المعياري الإضافي في تجربتي.

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

@ jack-williams أعتقد أنك فاتتك الفروق الدقيقة في أنني كنت أشير في المقام الأول إلى الأنواع الدقيقة والجزء ذي الصلة من فحص الممتلكات الزائدة. كان الجزء المتعلق بالأنواع ذات الحدود الدنيا عبارة عن حاشية سفلية لسبب ما - لقد كان استطرادًا مرتبطًا بشكل عرضي فقط.

تمكنت من كتابة تنفيذ لهذا الذي سيعمل مع حجج الوظيفة التي تتطلب درجات متفاوتة من الدقة:

// Checks that B is a subset of A (no extra properties)
type Subset<A extends {}, B extends {}> = {
   [P in keyof B]: P extends keyof A ? (B[P] extends A[P] | undefined ? A[P] : never) : never;
}

// This can be used to implement partially strict typing e.g.:
// ('b?:' is where the behaviour differs with optional b)
type BaseOptions = { a: string, b: number }

// Checks there are no extra properties (Not More, Less fine)
const noMore = <T extends Subset<BaseOptions, T>>(options: T) => { }
noMore({ a: "hi", b: 4 })        //Fine
noMore({ a: 5, b: 4 })           //Error 
noMore({ a: "o", b: "hello" })   //Error
noMore({ a: "o" })               //Fine
noMore({ b: 4 })                 //Fine
noMore({ a: "o", b: 4, c: 5 })   //Error

// Checks there are not less properties (More fine, Not Less)
const noLess = <T extends Subset<T, BaseOptions>>(options: T) => { }
noLess({ a: "hi", b: 4 })        //Fine
noLess({ a: 5, b: 4 })           //Error
noLess({ a: "o", b: "hello" })   //Error
noLess({ a: "o" })               //Error  |b?: Fine
noLess({ b: 4 })                 //Error
noLess({ a: "o", b: 4, c: 5 })   //Fine

// We can use these together to get a fully strict type (Not More, Not Less)
type Strict<A extends {}, B extends {}> = Subset<A, B> & Subset<B, A>;
const strict = <T extends Strict<BaseOptions, T>>(options: T) => { }
strict({ a: "hi", b: 4 })        //Fine
strict({ a: 5, b: 4 })           //Error
strict({ a: "o", b: "hello" })   //Error
strict({ a: "o" })               //Error  |b?: Fine
strict({ b: 4 })                 //Error
strict({ a: "o", b: 4, c: 5 })   //Error

// Or a fully permissive type (More Fine, Less Fine)
type Permissive<A extends {}, B extends {}> = Subset<A, B> | Subset<B, A>;
const permissive = <T extends Permissive<BaseOptions, T>>(options: T) => { }
permissive({ a: "hi", b: 4 })        //Fine
permissive({ a: 5, b: 4 })           //Error
permissive({ a: "o", b: "hello" })   //Error
permissive({ a: "o" })               //Fine
permissive({ b: 4 })                 //Fine
permissive({ a: "o", b: 4, c: 5 })   //Fine


النوع الدقيق للتخصيص المتغير الذي أدركت أنه لا يفعل شيئًا في الواقع ...

// This is a little unweildy, there's also a shortform that works in many cases:
type Exact<A extends {}> = Subset<A, A>
// The simpler Exact type works for variable typing
const options0: Exact<BaseOptions> = { a: "hi", b: 4 }        //Fine
const options1: Exact<BaseOptions> = { a: 5, b: 4 }           //Error
const options2: Exact<BaseOptions> = { a: "o", b: "hello" }   //Error
const options3: Exact<BaseOptions> = { a: "o" }               //Error |b?: Fine
const options4: Exact<BaseOptions> = { b: 4 }                 //Error
const options5: Exact<BaseOptions> = { a: "o", b: 4, c: 5 }   //Error

// It also works for function typing when using an inline value
const exact = (options: Exact<BaseOptions>) => { }
exact({ a: "hi", b: 4 })        //Fine
exact({ a: 5, b: 4 })           //Error
exact({ a: "o", b: "hello" })   //Error
exact({ a: "o" })               //Error  |b?: Fine
exact({ b: 4 })                 //Error
exact({ a: "o", b: 4, c: 5 })   //Error

// But not when using a variable as an argument even of the same type
const options6 = { a: "hi", b: 4 }
const options7 = { a: 5, b: 4 }
const options8 = { a: "o", b: "hello" }
const options9 = { a: "o" }
const options10 = { b: 4 }
const options11 = { a: "o", b: 4, c: 5 }
exact(options6)                 //Fine
exact(options7)                 //Error
exact(options8)                 //Error
exact(options9)                 //Error |b?: Fine
exact(options10)                //Error
exact(options11)                //Fine  -- Should not be Fine

// However using strict does work for that
// const strict = <T extends Strict<BaseOptions, T>>(options: T) => { }
strict(options6)                //Fine
strict(options7)                //Error
strict(options8)                //Error
strict(options9)                //Error |b?: Fine
strict(options10)               //Error
strict(options11)               //Error -- Is correctly Error

ارى

https://www.npmjs.com/package/ts-strictargs
https://github.com/Kotarski/ts-strictargs

أشعر أن لدي حالة استخدام لهذا عند تغليف مكونات React ، حيث أحتاج إلى "المرور عبر" الدعائم: https://github.com/Microsoft/TypeScript/issues/29883. @ جاك ويليامز أي أفكار حول هذا؟

OliverJAsh تبدو ذات صلة ، لكن يجب أن أعترف أنني لا أعرف React مثل معظم. أعتقد أنه سيكون من المفيد العمل من خلال كيفية مساعدة الأنواع الدقيقة بدقة هنا.

type MyComponentProps = { foo: 1 };
declare const MyComponent: ComponentType<MyComponentProps>;

type MyWrapperComponent = MyComponentProps & { myWrapperProp: 1 };
const MyWrapperComponent: ComponentType<MyWrapperComponent> = props => (
    <MyComponent
        // We're passing too many props here, but no error!
        {...props}
    />
);

الرجاء تصحيح لي في أي وقت أقول شيئا خاطئا.

أعتقد أن البداية ستكون تحديد MyComponent لقبول النوع الدقيق؟

declare const MyComponent: ComponentType<Exact<MyComponentProps>>;

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

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

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

الأشياء التي لم أقم بتغطيتها لأنني لا أعرف مدى احتمالية ظهورها في الحالة العامة ، حيث يكون props نوعًا عامًا ، وحيث تحتاج إلى دمج الدعائم مثل { ...props1, ...props2 } . هل هذا شائع؟

Kotarski هل

لدي حالة الاستخدام هذه:

type AB = { a: string, b: string }
type CD = { c: string, d: string }
type ABCD = AB & CD

// I want this to error, because the 'c' should mean it prevents either AB or ABCD from being satisfied.
const foo: AB | ABCD = { a, b, c };

// I presume that I would need to do this:
const foo: Exact<AB> | Exact<ABCD> = { a, b, c };

@ ryami333 هذا لا يحتاج لأنواع محددة ؛ الذي يحتاج فقط إلى إصلاح لفحص الملكية الزائدة: # 13813.

@ ryami333 إذا كنت على استعداد لاستخدام نوع إضافي ، نوع سيفعل ما تريده ، أي فرض نسخة أكثر صرامة من النقابات:

type AB = { a: string, b: string }
type CD = { c: string, d: string }
type ABCD = AB & CD


type UnionKeys<T> = T extends any ? keyof T : never;
type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>

// Error now.
const foo: StrictUnion<AB | ABCD> = { a: "", b: "", c: "" };

تضمين التغريدة من الغريب بالنسبة لي لماذا

type KeyofV1<T extends object> = keyof T

ينتج عن نتيجة مختلفة عن

type KeyofV2<T> = T extends object ? keyof T : never

هل يستطيع احد ان يشرح هذا لي؟

type AB = { a: string, b: string }
type CD = { c: string, d: string }
type ABCD = AB & CD

KeyofV1< AB | ABCD > // 'a' | 'b'
KeyofV2< AB | ABCD > // 'a' | 'b' | 'c' | 'e'

يحصل V1 على المفاتيح المشتركة للنقابة ، ويحصل V2 على مفاتيح كل عضو نقابي والنقابات النتيجة.

weswigham هل هناك سبب يدعوهم إلى إعادة نتائج مختلفة؟

نعم؟ كما قلت - يحصل V1 على _المفاتيح الشائعة_ لكل عضو نقابي ، لأن الوسيطة إلى keyof تنتهي بـ keyof (AB | ABCD) ، وهي "A" | "B" ، بينما يتلقى الإصدار داخل الشرط عضوًا واحدًا فقط في كل مرة ، وذلك بفضل التوزيع الشرطي على مدخلاته ، لذلك فهو أساسًا keyof AB | keyof ABCD .

weswigham إذن ، يقيّم الشرطي الأمر على هذا

type Union =
    (AB extends object ? keyof AB : never) |
    (ABCD extends object ? keyof ABCD : never)

عندما أقرأ هذا الرمز ، أتوقع عادةً أن يعمل الشيك (AB | ABCD) extends object كوحدة واحدة ، مع التحقق من أن (AB | ABCD) قابل للتخصيص لـ object ، ثم يعود keyof (AB | ABCD) كوحدة ، 'a' | 'b' . يبدو التخطيط الضمني غريبًا حقًا بالنسبة لي.

isiahmeadows يمكنك النظر إلى الأنواع الشرطية التوزيعية باعتبارها foreach للنقابات. يطبقون النوع الشرطي على كل عضو في الاتحاد بدوره والنتيجة هي اتحاد كل نتيجة جزئية.

لذلك UnionKeys<A | B> = UnionKeys<A> | UnionKeys<B> =(keyof A) | (keyof B)

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

type A<T> = T extends object ? keyof T : never // distributive
type B<T> = [T] extends [object] ? keyof T : never // non distributive the type parameter is not naked
type B<T> = object extends T ? keyof T : never // non distributive the type parameter is not the tested type

شكرا يا شباب ، أعتقد أنني حصلت عليه. أعدت ترتيبها لفهمي ؛ أعتقد أن NegativeUncommonKeys مفيد بحد ذاته أيضًا. هذا في حالة ما إذا كان مفيدًا لشخص آخر أيضًا.

type UnionKeys<T> = T extends any ? keyof T : never;
type NegateUncommonKeys<T, TAll> = (
    Partial<
        Record<
            Exclude<
                UnionKeys<TAll>,
                keyof T
            >,
            never
        >
    >
) 

type StrictUnion<T, TAll = T> = T extends any 
  ? T & NegateUncommonKeys<T, TAll>
  : never;

أتفهم أيضًا سبب وجود كل من T و TAll . "تأثير الحلقة" ، حيث يتم اختبار T وإخراجه ، يعني أنه يتم تطبيق كل عنصر في الاتحاد مقابل T بينما يحتوي TAll الذي لم يتم اختباره على الاتحاد الأصلي والكامل لجميع العناصر.

weswigham نعم .. إلا أنني أشعر أن هذا القسم يقرأ كما لو أنه كتبه مهندس مترجم لمهندس مترجم آخر.

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

ما هي معلمات النوع المجرد؟ (ولماذا لا يرتدون بعض الملابس 😄)

أي يشير T إلى المكونات الفردية بعد توزيع النوع الشرطي على نوع الاتحاد)

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

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

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

تتبع بشكل طبيعي من منظور نظري ونوع نظري

خذ كل عنصر في مجموعة وقسمه حسب المسند.

هذه عملية توزيعية!

خذ كل عنصر في مجموعة وقسمه حسب المسند.

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

RyanCavanaugh حسنًا ، لذا دعني أوضح: قرأت بشكل حدسي T extends U ? F<T> : G<T> كـ T <: U ⊢ F(T), (T <: U ⊢ ⊥) ⊢ G(T) ، مع إجراء المقارنة ليس متعدد الأجزاء ، ولكن كخطوة كاملة. هذا يختلف بشكل واضح عن "اتحاد كل {if t ∈ U then F({t}) else G({t}) | t ∈ T} ، وهو ما هو حاليًا دلالات.

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

تحديد العملية الأكثر سهولة هو أمر متاح للنقاش اللامتناهي ، ولكن مع القواعد الحالية ، من السهل جعل النوع التوزيعي غير موزع باستخدام [T] extends [C] . إذا كانت القيمة الافتراضية غير توزيعية ، فستحتاج إلى بعض التعويذات الجديدة على مستوى مختلف لتسبب التوزيع. هذا أيضًا سؤال منفصل عن السلوك المفضل في كثير من الأحيان ؛ محرر أسلوب الإدخال (IME) لا أريد أبدًا نوعًا غير موزع.

لا يوجد أساس نظري قوي للتوزيع لأنه عملية نحوية.

الحقيقة هي أنها مفيدة للغاية ومحاولة تشفيرها بطريقة أخرى ستكون مؤلمة.

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

هناك العديد من المشكلات حول distrubutivness بالفعل ، فلماذا لا نواجه أن بناء الجملة الجديد مطلوب؟

30572

هنا مثال على مشكلة:

أريد أن أحدد أن نقطة نهاية / خدمة API للمستخدمين يجب ألا تُرجع أي خصائص إضافية (مثل كلمة المرور) بخلاف تلك المحددة في واجهة الخدمة. إذا قمت بإرجاع كائن بخصائص إضافية عن طريق الخطأ ، فأنا أريد خطأ في وقت الترجمة ، بغض النظر عما إذا كان الكائن الناتج قد تم إنتاجه بواسطة كائن حرفي أو غير ذلك.

يمكن أن يكون فحص وقت التشغيل لكل كائن مرتجع مكلفًا ، خاصة بالنسبة للمصفوفات.

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

babakness إن نوعك NoExcessiveProps غير متاح. أعتقد أنهم يقصدون شيئًا كهذا:

interface API {
    username: () => { username: string }
}

const api: API = {
    username: (): { username: string } => {
        return { username: 'foobar', password: 'secret'} // error, ok
    }
}

const api2: API = {
    username: (): { username: string } => {
        const id: <X>(x: X) => X = x => x;
        const value = id({ username: 'foobar', password: 'secret' });
        return value  // no error, bad?
    }
}

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

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

فحص الملكية الزائد لا يساعد في هذه الحالة. بصراحة ، أعتقد أنه حل متزعزع بخدعة واحدة. من الناحية النظرية ، كان يجب عليهم تقديم نوع من الخبرة "يعمل فقط"

EPC هو خيار تصميم معقول وخفيف الوزن يغطي مجموعة كبيرة من المشاكل. الحقيقة هي أن الأنواع الدقيقة لا "تعمل فقط". يتطلب التنفيذ بطريقة سليمة تدعم القابلية للتوسعة نظام نوع مختلف تمامًا.

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

أيضًا ، لم أقصد أن الأنواع الدقيقة "تعمل فقط". قصدت أن EPC كان من المفترض أن "تعمل فقط" ولكن من الناحية العملية فهي محدودة ومربكة وغير آمنة. بشكل أساسي لأنه إذا حاولت استخدامه "بشكل متعمد" ينتهي بك الأمر بإطلاق النار على قدمك.

تحرير: نعم ، لقد قمت بتحريرها لاستبدال كلمة "هم" بكلمة "هو" لأنني أدركت أنها مربكة.

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

أيضًا ، لم أقصد أن الأنواع الدقيقة "تعمل فقط". قصدت أن EPC كان من المفترض أن "تعمل فقط" ولكن من الناحية العملية فهي محدودة ومربكة وغير آمنة. بشكل أساسي لأنه إذا حاولت استخدامه "بشكل متعمد" ينتهي بك الأمر بإطلاق النار على قدمك.

غلطتي. اقرأ التعليق الأصلي كـ

نظريًا ، كان يجب عليهم تقديم نوع من الخبرة "يعمل فقط" [والذي كان من الممكن أن يكون أنواعًا دقيقة بدلاً من EPC]

التعليق في [] كونها قراءتي.

البيان المعدل:

من الناحية النظرية ، كان يجب أن تقدم نوعًا من الخبرة "إنها تعمل فقط"

أكثر وضوحا. آسف لسوء التفسير!

type NoExcessiveProps<O> = {
  [K in keyof O]: K extends keyof O ? O[K] : never 
}

// no error
const getUser1 = (): {username: string} => {
  const foo = {username: 'foo', password: 'bar' }
  return foo 
} 

// Compile-time error, OK
const foo: NoExcessiveProps<{username: string}>  = {username: 'a', password: 'b' }

// No error? 🤔
const getUser2 = (): NoExcessiveProps<{username: string}> => {
  const foo = {username: 'foo', password: 'bar' }
  return foo 
}


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

babakness يتم تقييم NoExcessiveProps مرة أخرى إلى T (نوع له نفس مفاتيح T ). في [K in keyof O]: K extends keyof O ? O[K] : never ، سيكون K دائمًا مفتاح O نظرًا لأنك تقوم بتعيين أكثر من keyof O . أخطاء المثال const الخاصة بك لأنها تؤدي إلى تشغيل EPC تمامًا كما لو كنت ستكتبها كـ {username: string} .

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

function checked<T extends E, E>(o: T & Record<Exclude<keyof T, keyof E>, never>): E {
    return o;
}

const getUser2 = (): { username: string } => {
    const foo = { username: 'foo', password: 'bar' }
    return checked(foo) //error
}
const getUser3 = (): { username: string } => {
    const foo = { username: 'foo' }
    return checked(foo) //ok
}

dragomirtitian آه ... الحق ... نقطة جيدة! لذلك أحاول فهم دالة checked . أنا في حيرة خاصة

const getUser2 = (): { username: string } => {
    const foo = { username: 'foo', password: 'bar' }
    const bar = checked(foo) // error
    return checked(foo) //error
}
const getUser3 = (): { username: string } => {
    const foo = { username: 'foo' }
    const bar = checked(foo) // error!?
    return checked(foo) //ok
}

فشل تعيين bar في getUser3 . يبدو أن الخطأ هو foo
image

تفاصيل الخطأ

image

نوع bar هنا هو {} ، والذي يبدو كما لو أنه بسبب checked

function checked<T extends E, E>(o: T & Record<Exclude<keyof T, keyof E>, never>): E {
    return o;
}

E غير مخصص في أي مكان. ومع ذلك ، إذا استبدلنا typeof E بـ typeof {} ، فلن يعمل.

ما هو نوع E؟ هل هناك نوع من الأشياء المدركة للسياق تحدث؟

babakness إذا لم يكن هناك مكان آخر للاستدلال على معلمة النوع منه ، فسيتم الاستدلال على الكتابة المطبوعة من نوع الإرجاع. لذلك عندما نقوم بتعيين نتيجة checked لإرجاع getUser* ، سيكون E هو نوع الإرجاع للوظيفة ، و T سيكون هو النوع الفعلي للقيمة التي تريد إرجاعها. إذا لم يكن هناك مكان للاستدلال على E منه ، فسيكون افتراضيًا فقط {} ولذا ستحصل دائمًا على خطأ.

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

function checked<E>() {
    return function <T extends E>(o: T & Record<Exclude<keyof T, keyof E>, never>): E {
        return o;
    }
}

const getUser2 = (): { username: string } => {
    const foo = { username: 'foo', password: 'bar' }
    return checked<{ username: string }>()(foo) //error
}
const getUser3 = (): { username: string } => {
    const foo = { username: 'foo' }
    return checked<{ username: string }>()(foo) //ok
}

ملاحظة: يعتبر أسلوب الدالة curried أمرًا ضروريًا نظرًا لأنه ليس لدينا حتى الآن استنتاج جزئي للوسيطة (https://github.com/Microsoft/TypeScript/pull/26349) لذلك لا يمكننا تحديد بعض معلمات النوع واستدلال البعض الآخر في نفس المكالمة. للتغلب على هذا ، نحدد E في المكالمة الأولى ونترك T يتم استنتاجه في المكالمة الثانية. يمكنك أيضًا تخزين الوظيفة cache مؤقتًا لنوع معين واستخدام النسخة المخبأة

function checked<E>() {
    return function <T extends E>(o: T & Record<Exclude<keyof T, keyof E>, never>): E {
        return o;
    }
}
const checkUser = checked<{ username: string }>()

const getUser2 = (): { username: string } => {
    const foo = { username: 'foo', password: 'bar' }
    return checkUser(foo) //error
}
const getUser3 = (): { username: string } => {
    const foo = { username: 'foo' }
    return checkUser(foo) //ok
}

FWIW هذه إحدى قواعد WIP / sketch tslint التي تحل المشكلة المحددة المتمثلة في عدم إرجاع الخصائص الإضافية بطريق الخطأ من الطرق "المكشوفة".

https://gist.github.com/spion/b89d1d2958f3d3142b2fe64fea5e4c32

بالنسبة لحالة استخدام الانتشار - راجع https://github.com/Microsoft/TypeScript/issues/12936#issuecomment -300382189 - هل يمكن أن يكتشف linter نمطًا كهذا ويحذر من أنه ليس آمنًا من النوع؟

مثال على نسخ التعليمات البرمجية من التعليق المذكور أعلاه:

interface State {
   name: string;
}
function nameReducer(state: State, action: Action<string>): State {
   return {
       ...state,
       fullName: action.payload // compiles, but it's an programming mistake
   }
}

سم مكعبJamesHenry / @ armano2

أود أن أرى ذلك يحدث. نحن نستخدم تعريفات TypeScript المُنشأة لنقاط نهاية GraphQL ، وهي مشكلة ألا تسبب TypeScript في حدوث خطأ عندما أمرر كائنًا يحتوي على عدد أكبر من الحقول المطلوبة إلى استعلام لأن GraphQL ستفشل في تنفيذ مثل هذا الاستعلام في وقت التشغيل.

ما مقدار هذا الذي يتم معالجته الآن من خلال التحديث 3.5.1 مع فحص أفضل للخصائص الإضافية أثناء التعيين؟ لقد حصلنا على مجموعة من مناطق المشكلات المعروفة التي تم تمييزها على أنها أخطاء بالطريقة التي أردناها بعد الترقية إلى 3.5.1

إذا كانت لديك مشكلة وتعتقد أن الأنواع الدقيقة هي الحل الصحيح ، فيرجى وصف المشكلة الأصلية هنا

https://github.com/microsoft/TypeScript/issues/12936#issuecomment -284590083

إليك واحدة تتضمن مراجع React: https://github.com/microsoft/TypeScript/issues/31798

/ ccRyanCavanaugh

حالة استخدام واحدة بالنسبة لي هي

export const mapValues =
  <T extends Exact<T>, V>(object: T, mapper: (value: T[keyof T], key: keyof T) => V) => {
    type TResult = Exact<{ [K in keyof T]: V }>;
    const result: Partial<TResult> = { };
    for (const [key, value] of Object.entries(object)) {
      result[key] = mapper(value, key);
    }
    return result as TResult;
  };

هذا غير سليم إذا لم نستخدم الأنواع الدقيقة ، لأنه إذا كان لدى object خصائص إضافية ، فليس من الآمن استدعاء mapper على تلك المفاتيح والقيم الإضافية.

الدافع الحقيقي هنا هو أنني أريد الحصول على قيم التعداد في مكان ما يمكنني إعادة استخدامه في الكود:

const choices = { choice0: true, choice1: true, choice2: true };
const callbacksForChoices = mapValues(choices, (_, choice) => () => this.props.callback(choice));

حيث this.props.callback نوع (keyof typeof choices) => void .

إذن ، يتعلق الأمر حقًا بقدرة نظام الكتابة على تمثيل حقيقة أن لديّ قائمة مفاتيح في كود لاند تتطابق تمامًا مع مجموعة (على سبيل المثال ، اتحاد) من المفاتيح في نوع الأرض ، حتى نتمكن من كتابة وظائف تعمل على هذا قائمة المفاتيح وقم بعمل تأكيدات نوع صالحة حول النتيجة. لا يمكننا استخدام كائن ( choices في المثال السابق) لأنه بقدر ما يعرف نظام النوع ، يمكن أن يكون للكائن code-land خصائص إضافية تتجاوز أي نوع كائن مستخدم. لا يمكننا استخدام مصفوفة ( ['choice0', 'choice1', 'choice2'] as const ، لأنه بقدر ما يعرف نظام النوع ، قد لا تحتوي المصفوفة على جميع المفاتيح التي يسمح بها نوع المصفوفة.

ربما لا ينبغي أن يكون exact نوعًا ، ولكن فقط معدل على مدخلات و / أو مخرجات الوظيفة؟ شيء ما مثل معدل تباين التدفق ( + / - )

أريد أن أضيف إلى ما قالتهphaux للتو. الاستخدام الحقيقي الذي أملكه مقابل Exact هو أن يكون المحول البرمجي يضمن شكل الوظائف. عندما يكون لدي إطار عمل ، قد أرغب في أيٍّ مما يلي: (T, S): AtMost<T> ، أو (T, S): AtLeast<T> ، أو (T, S): Exact<T> حيث يمكن للمترجم أن يتحقق من أن الوظائف التي يحددها المستخدم ستكون مناسبة تمامًا.

بعض الأمثلة المفيدة:
AtMost مفيدًا للتكوين (لذلك لا نتجاهل المعلمات الإضافية / الأخطاء المطبعية ونفشل مبكرًا).
AtLeast رائعًا لأشياء مثل مكونات التفاعل والبرامج الوسيطة حيث يمكن للمستخدم دفع أي شيء إضافي يريده على كائن.
Exact مفيدًا للتسلسل / إلغاء التسلسل (يمكننا ضمان عدم إسقاط البيانات وهي متشابهة).

هل سيساعد هذا في منع حدوث ذلك؟

interface IDate {
  year: number;
  month: number;
  day: number;
}

type TBasicField = string | number | boolean | IDate;

 // how to make this generic stricter?
function doThingWithOnlyCorrectValues<T extends TBasicField>(basic: T): void {
  // ... do things with basic field of only the exactly correct structures
}

const notADate = {
  year: 2019,
  month: 8,
  day: 30,
  name: "James",
};

doThingWithOnlyCorrectValues(notADate); // <- this should not work! I want stricter type checking

نحتاج حقًا إلى طريقة في TS لنقول T extends exactly { something: boolean; } ? xxx : yyy .

أو بخلاف ذلك ، شيء مثل:

const notExact = {
  something: true,
  name: "fred",
};

سيستمر إرجاع xxx هناك.

ربما يمكن استخدام الكلمة الرئيسية const ؟ على سبيل المثال ، T extends const { something: boolean }

pleerock قد يكون غامضًا بعض الشيء ، كما هو الحال في JavaScript / TypeScript ، يمكننا تعريف متغير على أنه const ولكن لا يزال بإمكانك إضافة / إزالة خصائص الكائن. أعتقد أن الكلمة الرئيسية exact جميلة للغاية.

لست متأكدًا مما إذا كان مرتبطًا تمامًا ، لكنني أتوقع خطأين على الأقل في هذه الحالة:
ملعب
Screen Shot 2019-08-08 at 10 15 34

mityok أعتقد أن هذا مرتبط. أعتقد أنك ترغب في القيام بشيء على غرار:

class Animal {
  makeSound(): exact Foo {
     return { a: 5 };
  }
}

إذا جعل exact النوع أكثر صرامة - فلا ينبغي أن يكون قابلاً للتمديد بخاصية إضافية ، كما فعلت في Dog .

الاستفادة من const ( as const ) واستخدام واجهات وأنواع سابقة ، مثل

const type WillAcceptThisOnly = number

function f(accept: WillAcceptThisOnly) {
}

f(1) // error
f(1 as WillAcceptThisOnly) // ok, explicit typecast

const n: WillAcceptThisOnly = 1
f(n) // ok

سيكون مطولًا حقًا في الاضطرار إلى تعيين متغيرات ثابتة ، ولكن سيتجنب الكثير من حالات الحافة عندما تمر بنمط لم يكن بالضبط ما كنت تتوقعه

لقد توصلت إلى حل TypeScript خالص لمشكلة Exact<T> والتي ، على ما أعتقد ، تتصرف تمامًا مثل ما تم طلبه في المنشور الرئيسي:

// (these two types MUST NOT be merged into a single declaration)
type ExactInner<T> = <D>() => (D extends T ? D : D);
type Exact<T> = ExactInner<T> & T;

function exact<T>(obj: Exact<T> | T): Exact<T> {
    return obj as Exact<T>;
};

يجب عدم تضمين السبب ExactInner في Exact لأن الإصلاح # 32824 لم يتم إصداره بعد (ولكن تم دمجه بالفعل في 32924! ).

من الممكن فقط تعيين قيمة للمتغير أو وسيطة الوظيفة من النوع Exact<T> ، إذا كان تعبير اليد اليمنى أيضًا Exact<T> ، حيث T هو نوع متطابق تمامًا في كلا الجزأين من الاحالة.

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

إنه يعمل من خلال استغلال حقيقة أن TypeScript يستخدم extend فحص العلاقة لتحديد ما إذا كان يمكن تعيين نوع اليد اليمنى إلى النوع الأيسر - يمكن فقط إذا كان نوع اليد اليمنى (المصدر) _extends_ النوع الأيسر (الوجهة) .

نقلا عن checker.ts ،

// نوعان شرطيان 'T1 يمتد إلى U1؟ X1: Y1 'و' T2 يمتد U2؟ X2: Y2 'مرتبطة إذا
// أحد T1 و T2 مرتبط بالآخر ، U1 و U2 نوعان متطابقان ، X1 مرتبط بـ X2 ،
// و Y1 متعلقان بـ Y2.

يستخدم ExactInner<T> العام الأسلوب الموصوف ، مع استبدال U1 و U2 بالأنواع الأساسية التي تتطلب التحقق من الدقة. يضيف Exact<T> تقاطعًا مع النوع الأساسي البسيط ، مما يسمح لـ TypeScript بإرخاء النوع الدقيق عندما لا يكون متغير الهدف أو وسيطة الوظيفة من النوع الدقيق.

من منظور المبرمج ، يتصرف Exact<T> كما لو أنه يعين علامة exact على T ، دون فحص T أو تغييره ، وبدون إنشاء نوع مستقل.

هنا رابط الملعب ورابط جوهره .

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

عمل مذهلtoriningen!

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

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

https://www.typescriptlang.org/play/#code/KYOwrgtgBAyg9gJwC4BECWDgGMlriKAbwCgooBBAZyygF4oByAQ2oYBpSoVhq7GATHlgbEAvsWIAzMCBx4CTfvwDyCQQgBCATwAU -DNlz4AXFABE5GAGEzUAD7mUAUWtmAlEQnjiilWuCauvDI6Jhy + AB0VFgRSHAAqgAOiQFWLMA6bm4A3EA

enum SortDirection {
  Asc = 'asc',
  Desc = 'desc'
}
function addOrderBy(direction: "ASC" | "DESC") {}
addOrderBy(SortDirection.Asc.toUpperCase());

lookfirst هذا مختلف. هذا يطلب ميزة للأنواع التي لا تسمح بخصائص إضافية ، مثل بعض الأنواع exact {foo: number} حيث لا يمكن {foo: 1, bar: 2} لها. هذا فقط يطلب تحويلات نصية لتطبيقها على قيم التعداد ، والتي من المحتمل ألا تكون موجودة.

لست متأكدًا مما إذا كانت هذه هي المشكلة الصحيحة ، ولكن [...]

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

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

محرر : تحقق من حل aigoncharov أدناه ، لأنني أعتقد أنه أسرع.

type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

لا أعرف ما إذا كان يمكن تحسين هذا أكثر.

type Exact<T, Shape> =
    // Check if `T` is matching `Shape`
    T extends Shape
        // Does match
        // Check if `T` has same keys as `Shape`
        ? Exclude<keyof T, keyof Shape> extends never
            // `T` has same keys as `Shape`
            ? T
            // `T` has more keys than `Shape`
            : never
        // Does not match at all
        : never;

type InexactType = {
    foo: string
}

const obj = {
    foo: 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}
function test2(t: InexactType) {}

test1(obj) // $ExpectError
test2(obj)

بدون تعليقات

type ExactKeys<T1, T2> = Exclude<keyof T1, keyof T2> extends never
    ? T1
    : never

type Exact<T, Shape> = T extends Shape
    ? ExactKeys<T, Shape>
    : never;

لا أعرف ما إذا كان يمكن تحسين هذا أكثر.

type Exact<T, Shape> =
    // Check if `T` is matching `Shape`
    T extends Shape
        // Does match
        // Check if `T` has same keys as `Shape`
        ? Exclude<keyof T, keyof Shape> extends never
            // `T` has same keys as `Shape`
            ? T
            // `T` has more keys than `Shape`
            : never
        // Does not match at all
        : never;

type InexactType = {
    foo: string
}

const obj = {
    foo: 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}
function test2(t: InexactType) {}

test1(obj) // $ExpectError
test2(obj)

بدون تعليقات

type ExactKeys<T1, T2> = Exclude<keyof T1, keyof T2> extends never
    ? T1
    : never

type Exact<T, Shape> = T extends Shape
    ? ExactKeys<T, Shape>
    : never;

أحب هذه الفكرة!

الحيلة الأخرى التي يمكن أن تؤدي المهمة هي التحقق من قابلية التعيين في كلا الاتجاهين.

type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

type A = {
  prop1: string
}
type B = {
  prop1: string
  prop2: string
}
type C = {
  prop1: string
}

type ShouldBeNever = Exact<A, B>
type ShouldBeA = Exact<A, C>

http://www.typescriptlang.org/play/#code/C4TwDgpgBAogHgQwMbADwBUA0UBKA + KAXinSgjmAgDsATAZ1wCgooB + XMi6 + k5lt3vygAuKFQgA3CACc + o8VNmNQkKAEEiUAN58w0gPZgAjKLrBpASyoBzRgF9l4aACFNOlnsMmoZyzd0GYABMpuZWtg4q0ADCbgFeoX4RjI6qAMoAFvoArgA2NM4QAHKSMprwyGhq2M54qdCZOfmFGsQVKKjVUNF1QkA

ملعب آخر منiamandrewluca https://www.typescriptlang.org/play/؟ssl=7&ssc=6&pln=7&pc=17#code/C4TwDgpgBAogHgQwMbADwBUA0UBKA + KAXinSgjmAgDsATAZ1wCgooB + XMi6 + k5lt3vygAuKFQgA3CACc + o8VNmNQkKAElxiFOnDRiAbz4sAZgHtTousGkBLKgHNGAX0aMkpqlaimARgCsiKEMhMwsoAHJQ8MwjKB8EaVFw + Olw51djAFcqFBsPKEorAEYMPAAKYFF4ZDQsdU0anUg8AEoglyyc4DyqAogrACYK0Q1yRt02-RdlfuAist8-NoB6ZagAEnhIFBhpaVNZQuAhxZagA

فارق بسيط هنا هو ما إذا كان يجب Exact<{ prop1: 'a' }> إلى Exact<{ prop1: string }> . في حالات الاستخدام الخاصة بي ، يجب أن يكون.

jeremybparagon حالتك مغطاة. فيما يلي بعض الحالات الأخرى.

type InexactType = {
    foo: 'foo'
}

const obj = {
    // here foo is infered as `string`
    // and will error because `string` is not assignable to `"foo"`
    foo: 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}
function test2(t: InexactType) {}

test1(obj) // $ExpectError
test2(obj) // $ExpectError
type InexactType = {
    foo: 'foo'
}

const obj = {
    // here we cast to `"foo"` type
    // and will not error
    foo: 'foo' as 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}
function test2(t: InexactType) {}

test1(obj) // $ExpectError
test2(obj)
type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

أعتقد أن أي شخص يستخدم هذه الخدعة (وأنا لا أقول أنه لا توجد استخدامات صالحة لها) يجب أن يدرك تمامًا أنه من السهل جدًا الحصول على المزيد من الدعائم من النوع "الدقيق". نظرًا لأن InexactType قابل للتخصيص إلى Exact<T, InexactType> إذا كان لديك شيء كهذا ، فإنك تنفصل عن الدقة دون أن تدرك ذلك:

function test1<T>(t: Exact<T, InexactType>) {}

function test2(t: InexactType) {
  test1(t); // inexactType assigned to exact type
}
test2(obj) // but 

رابط الملعب

هذا هو السبب (واحد منهم على الأقل) لأن TS ليس لديه أنواع محددة ، لأنه يتطلب تفرغًا كاملاً لأنواع الكائنات في أنواع دقيقة مقابل أنواع غير دقيقة حيث لا يمكن أبدًا تخصيص نوع غير دقيق لنوع محدد ، حتى لو فهي متوافقة في ظاهرها. قد يحتوي النوع غير الدقيق دائمًا على المزيد من الخصائص. (على الأقل هذا كان واحدا من الأسباب المذكورةahejlsberg كما tsconf).

إذا كان asExact طريقة نحوية لتمييز مثل هذا الكائن الدقيق ، فهذا ما قد يبدو عليه هذا الحل:

declare const exactMarker: unique symbol 
type IsExact = { [exactMarker]: undefined }
type Exact<T extends IsExact & R, R> =
  Exclude<keyof T, typeof exactMarker> extends keyof R? T : never;

type InexactType = {
    foo: string
}
function asExact<T>(o: T): T & IsExact { 
  return o as T & IsExact;
}

const obj = asExact({
  foo: 'foo',
});


function test1<T extends IsExact & InexactType>(t: Exact<T, InexactType>) {

}

function test2(t: InexactType) {
  test1(t); // error now
}
test2(obj) 
test1(obj);  // ok 

const obj2 = asExact({
  foo: 'foo',
  bar: ""
});
test1(obj2);

const objOpt = asExact < { foo: string, bar?: string }>({
  foo: 'foo',
  bar: ""
});
test1(objOpt);

رابط الملعب

dragomirtitian لهذا السبب توصلت إلى الحل قبل ذلك بقليل https://github.com/microsoft/TypeScript/issues/12936#issuecomment -524631270 الذي لا يعاني من هذا.

dragomirtitian إنها مسألة كيفية كتابة
إذا قمت بذلك بشكل مختلف قليلاً ، فستنجح.

type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

type InexactType = {
    foo: string
}

const obj = {
    foo: 'foo',
    bar: 'bar'
}

function test1<T>(t: Exact<T, InexactType>) {}

function test2<T extends InexactType>(t: T) {
  test1(t); // fails
}
test2(obj)

https://www.typescriptlang.org/play/#code/C4TwDgpgBAogHgQwMbADwBUA0UBKA + KAXinSgjmAgDsATAZ1wCgooB + XMi6 + k5lt3vygAuKFQgA3CACc + o8VNmNQkKAElxiFOnDRiAbz4sAZgHtTousGkBLKgHNGAX0aMkpqlaimARgCsiKEMhMwsoAHJQ8MwjKB8EaVFw + Olw51djAFcqFBsPKEorAEYMPAAKYFF4ZDQsdU0anUg8AEogl0YsnOA8qgKIKwAmDE5KWgYNckbdcsqSNuD + 4oqWgG4oAHoNqGMEGwAbOnTC4EGy3z82oA

jeremybparagon حالتك مغطاة.

iamandrewluca أعتقد أن الحلول هنا و هنا تختلف حول كيفية تعامل بلدي على سبيل المثال .

type Exact<T, R> = T extends R
  ? R extends T
    ? T
    : never
  : never

type A = {
  prop1: 'a'
}
type C = {
  prop1: string
}

type ShouldBeA = Exact<A, C> // This evaluates to never.

const ob...

رابط الملعب

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

toriningen نعم يبدو الحل أفضل ، كنت أشير فقط إلى آخر حل تم نشره. إن الحل الخاص بك له حقيقة أنك لست بحاجة إلى معلمة نوع الوظيفة الإضافية ، ولكن لا يبدو أنها تعمل بشكل جيد مع الخصائص الاختيارية:

// (these two types MUST NOT be merged into a single declaration)
type ExactInner<T> = <D>() => (D extends T ? D : D);
type Exact<T> = ExactInner<T> & T;
type Unexact<T> = T extends Exact<infer R> ? R : T;

function exact<T>(obj: Exact<T> | T): Exact<T> {
    return obj as Exact<T>;
};

////////////////////////////////
// Fixtures:
type Wide = { foo: string, bar?: string };
type Narrow = { foo: string };
type ExactWide = Exact<Wide>;
type ExactNarrow = Exact<Narrow>;

const ew: ExactWide = exact<Wide>({ foo: "", bar: ""});
const assign_en_ew: ExactNarrow = ew; // Ok ? 

رابط الملعب

jeremybparagon لست متأكدًا من أن حل aigoncharov يقوم بعمل جيد في الخصائص الاختيارية. أي حل يعتمد على T extends S و S extends T سيعاني من حقيقة بسيطة

type A = { prop1: string }
type C = { prop1: string,  prop2?: string }
type CextendsA = C extends A ? "Y" : "N" // Y 
type AextendsC = A extends C ? "Y" : "N" // also Y 

رابط الملعب

أعتقد أن iamandrewluca باستخدام Exclude<keyof T, keyof Shape> extends never جيد ، ونوعي مشابه تمامًا (لقد قمت بتحرير إجابتي الأصلية لإضافة &R لضمان T extends R بدون أي شيكات إضافية).

type Exact<T extends IsExact & R, R> =
  Exclude<keyof T, typeof exactMarker> extends keyof R? T : never;

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

يجب أن يكون لدينا علم حيث يتم تمكين هذا على مستوى العالم. بهذه الطريقة ، من يريد أن يفقد الكتابة يمكنه الاستمرار في فعل الشيء نفسه. طريقة الكثير من الأخطاء التي تسببها هذه المشكلة. الآن أحاول تجنب عامل انتشار واستخدام pickKeysFromObject(shipDataRequest, ['a', 'b','c'])

إليك حالة استخدام للأنواع الدقيقة التي عثرت عليها مؤخرًا:

type PossibleKeys = 'x' | 'y' | 'z';
type ImmutableMap = Readonly<{ [K in PossibleKeys]?: string }>;

const getFriendlyNameForKey = (key: PossibleKeys) => {
    switch (key) {
        case 'x':
            return 'Ecks';
        case 'y':
            return 'Why';
        case 'z':
            return 'Zee';
    }
};

const myMap: ImmutableMap = { x: 'foo', y: 'bar' };

const renderMap = (map: ImmutableMap) =>
    Object.keys(map).map(key => {
        // Argument of type 'string' is not assignable to parameter of type 'PossibleKeys'
        const friendlyName = getFriendlyNameForKey(key);
        // No index signature with a parameter of type 'string' was found on type 'Readonly<{ x?: string | undefined; y?: string | undefined; z?: string | undefined; }>'.    
        return [friendlyName, map[key]];
    });
;

نظرًا لأن الأنواع غير دقيقة افتراضيًا ، يجب على Object.keys إرجاع string[] (راجع https://github.com/microsoft/TypeScript/pull/12253#issuecomment-263132208) ، ولكن في هذه الحالة ، إذا كان ImmutableMap دقيقًا ، فلا يوجد سبب لعدم إرجاعه PossibleKeys[] .

لاحظ dallonf أن هذا المثال يتطلب وظائف إضافية إلى جانب الأنواع الدقيقة فقط - Object.keys مجرد وظيفة ويجب أن تكون هناك آلية ما لوصف دالة تقوم بإرجاع keyof T للأنواع الدقيقة و string للأنواع الأخرى. مجرد وجود خيار التصريح عن نوع دقيق لن يكون كافيًا.

RyanCavanaugh أعتقد أن هذا هو

حالة الاستخدام لكتابة رد الفعل:

forwardRef<T, P>(render: (props: P, ref: Ref<T>) => ReactElement<P> & { displayName?: string }) => ComponentType<P> .

من المغري تمرير مكون عادي إلى forwardRef وهذا هو السبب في أن React يصدر تحذيرات وقت التشغيل إذا اكتشف propTypes أو defaultProps على الوسيطة render . نود التعبير عن هذا على مستوى النوع ولكن يتعين علينا الرجوع إلى never :

- forwardRef<T, P>(render: (props: P, ref: Ref<T>) => ReactElement<P> & { displayName?: string }) => ComponentType<P>
+ forwardRef<T, P>(render: (props: P, ref: Ref<T>) => ReactElement<P> & { displayName?: string, propTypes?: never, defaultProps?: never }) => ComponentType<P>

رسالة الخطأ مع never غير مفيدة ("{} غير قابل للتخصيص إلى غير محدد").

هل يمكن لأي شخص مساعدتي في معرفة كيف toriningen مع اتحاد أشكال كائن الحدث المختلفة؟ أرغب في تقييد أشكال الحدث الخاص بي في مكالمات إعادة الإرسال ، على سبيل المثال:

type StoreEvent =
  | { type: 'STORE_LOADING' }
  | { type: 'STORE_LOADED'; data: unknown[] }

من غير الواضح كيف يمكنني إنشاء دالة إرسال مكتوبة () تقبل فقط الشكل الدقيق للحدث.

(تحديث: لقد اكتشفت ذلك: https://gist.github.com/sarimarton/d5d539f8029c01ca1c357aba27139010)

حالة الاستخدام:

يؤدي عدم وجود دعم Exact<> إلى مشاكل وقت تشغيل طفرات GraphQL. تقبل GraphQL القائمة الدقيقة للخصائص المسموح بها. إذا قدمت دعائمًا مفرطة ، فسيؤدي ذلك إلى حدوث خطأ.

لذلك عندما نحصل على بعض البيانات من النموذج ، لا يمكن لـ Typescript التحقق من صحة الخصائص الزائدة (الإضافية). وسوف نحصل على خطأ في وقت التشغيل.

يوضح المثال التالي الأمان التخيلي

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

جرب في ساحة اللعب

Screen Shot 2020-03-05 at 13 04 38

وفقًا للمقال https://fettblog.eu/typescript-match-the-exact-object-shape/ والحلول المماثلة المذكورة أعلاه يمكننا استخدام الحل القبيح التالي:

Screen Shot 2020-03-05 at 12 26 57

لماذا هذا الحل savePerson<T>(person: ValidateShape<T, Person>) قبيح؟

افترض أن لديك نوع إدخال متداخل بعمق على سبيل المثال:

// Assume we are in the ideal world where implemented Exact<>

type Person {
  name: string;
  address: Exact<Address>;
}

type Address {
   city: string
   location: Exact<Location>
}

type Location {
   lon: number;
   lat: number; 
}

savePerson(person: Exact<Person>)

لا أستطيع أن أتخيل نوع السباغيتي الذي يجب أن نكتبه للحصول على نفس السلوك مع الحل المتاح حاليًا:

savePerson<T, TT, TTT>(person: 
  ValidateShape<T, Person keyof ...🤯...
     ValidateShape<TT, Address keyof ...💩... 
         ValidateShape<TTT, Location keyof ...🤬... 
> > >)

لذلك ، في الوقت الحالي ، لدينا ثغرات كبيرة في التحليل الثابت في التعليمات البرمجية الخاصة بنا ، والتي تعمل مع بيانات الإدخال المتداخلة المعقدة.

الحالة الموصوفة في الصورة الأولى ، حيث لا تتحقق TS من صحة الخصائص الزائدة بسبب فقدان "النضارة" ، كانت أيضًا نقطة ألم لنا.

كتابة

doSomething({
  /* large object of options */
})

غالبًا ما يبدو أقل قابلية للقراءة من

const options = {
  /* large object of options */
}
doSomething(options)

يساعد التعليق بشكل صريح على const options: DoSomethingOptions = { ، لكنه مرهق بعض الشيء ويصعب تحديده وفرضه في مراجعات الكود.

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

RyanCavanaugh شكرًا لشرح EPC ... هل تمت مناقشة الفرق بين EPC والأنواع الدقيقة بمزيد من التفاصيل في أي مكان؟ أشعر الآن أنني يجب أن أحصل على فهم أفضل لسبب سماح EPC ببعض الحالات التي لا تسمح بها الأنواع الدقيقة.

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

أعتقد أن لدي نفس المشكلة مثل مثال طفرات GraphQL (كتابة متداخلة دقيقة ، لا ينبغي السماح بخصائص إضافية). في حالتي ، أفكر في كتابة استجابات واجهة برمجة التطبيقات في وحدة مشتركة (مشتركة بين الواجهة الأمامية والخلفية):

export type ProductsSlashResponse = {
  products: Array<{
    id: number;
    description: string;
  }>,
  total: number;
};

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

router.get("products/", async () =>
  assertType<ProductsSlashResponse>(getProducts())));

لقد جربت الحلول من هنا. واحد يبدو أنه يعمل هو T extends U ? U extends T ? T : never : never ، جنبًا إلى جنب مع وظيفة كاري التي ليست مثالية. المشكلة الرئيسية في ذلك هي أنك لا تحصل على أي تعليقات على الخصائص المفقودة أو الإضافية (ربما يمكننا تحسين ذلك ، ولكن يصبح من الصعب القيام بذلك عندما ندخل في خصائص متداخلة). لا تعمل الحلول الأخرى مع الكائنات المتداخلة بعمق.

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

@ fer22f لا ترسل GraphQL حقولاً لم يطلبها العميل ... إلا إذا كنت تستخدم نوعًا قياسيًا من نوع JSON مقابل products أو لعناصر المصفوفة ، فلا داعي للقلق هناك.

آسف لقد أخطأت في القراءة ، أعتقد أنك تعني أنك كنت تستخدم GraphQL

ذكر شخص ما بالفعل GraphQL ، ولكن فقط من حيث "جمع حالات الاستخدام" (ذكر DanielRosenwasser منذ عدة سنوات في الموضوع :-) من "عدم وجود أي حالات استخدام غير يدوية") ، حالتا استخدام أردت استخدام Exact هي:

  1. تمرير البيانات إلى مخازن البيانات / قواعد البيانات / ORMs - أي حقول إضافية يتم تمريرها سيتم إسقاطها / عدم تخزينها بصمت.

  2. تمرير البيانات إلى مكالمات سلكية / RPCs / REST / GraphQL - مرة أخرى ، سيتم إسقاط / عدم إرسال أي حقول إضافية يتم تمريرها بصمت.

(حسنًا ، ربما لم يتم إسقاطها بصمت ، يمكن أن تكون أخطاء وقت التشغيل.)

في كلتا الحالتين ، أود أن أخبر نفسي / المبرمج (عن طريق خطأ تجميع) "... لا يجب عليك حقًا إعطائي هذه الخاصية الإضافية ، b / c إذا كنت تتوقع أن يتم تخزينها أو" أرسل "، لن يكون".

هذا مطلوب بشكل خاص في واجهات برمجة تطبيقات نمط "التحديث الجزئي" ، أي الأنواع الضعيفة:

type Data = { firstName:? string; lastName?: string; children?: [{ ... }] };
const data = { firstName: "a", lastNmeTypo: "b" };
await saveDataToDbOrWireCall(data);

اجتياز التحقق من النوع الضعيف b / c على الأقل معلمة واحدة مطابقة ، firstName ، لذا فهي ليست منفصلة بنسبة 100٪ ، ومع ذلك لا يزال هناك خطأ إملائي "واضح" lsatNmeTypo لم يتم اكتشافه.

ممنوح ، يعمل EPC إذا قمت بما يلي:

await saveDataToDbOrWireCall({ firstName, lastNmeTypo });

لكن الاضطرار إلى إتلاف + إعادة كتابة كل حقل أمر شاق للغاية.

حلول مثل jcalz 's Exactify تعمل على خاصية المستوى الأول ، لكن الحالة العودية (على سبيل المثال ، children مصفوفة ويجب أن تكون عناصر المصفوفة دقيقة) أواجه صعوبة بمجرد وصولها حالات استخدام "العالم الحقيقي" مع الأدوية الجنيسة / مثل Exact<Foo<Bar<T>> .

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

(FWIW https://github.com/stephenh/joist-ts/pull/35/files لديه محاولتي الحالية عند Exact عميقًا وأيضًا Exact.test.ts الذي يمر حالات تافهة ، لكن تحتوي العلاقات العامة نفسها على أخطاء تجميعية في الاستخدامات الأكثر سرية. إخلاء المسؤولية لا أتوقع حقًا أن ينظر أي شخص في هذه العلاقات العامة المحددة ، ولكني أقدمها فقط على أنها "هنا حيث يكون Exact مفيدًا" + "AFAICT من الصعب القيام بذلك في نقطة بيانات المستخدم الأرضي.)

مهلا،

هل كنت تتساءل ما هي أفكار فريق TS فيما يتعلق بسجلات الأنواع الدقيقة ومقترح المجموعات هنا؟ https://github.com/tc39/proposal-record-tuple

هل يعقل تقديم أنواع دقيقة لأولئك الأوائل الجدد؟

slorber ليس TS ، لكن هذا متعامد. يتعلق هذا الاقتراح بالثبات ، والمخاوف متطابقة تقريبًا بين ذلك وبين مكتبات مثل Immutable.js.

لقد تكررت على النسخة العودية

export type Exact<Expected, Actual> = Expected &
  Actual & // Needed to infer `Actual`
  (null extends Actual
    ? null extends Expected
      ? Actual extends null // If only null stop here, because NonNullable<null> = never
        ? null
        : CheckUndefined<Expected, Actual>
      : never // Actual can be null but not Expected: forbid the field
    : CheckUndefined<Expected, Actual>);

type CheckUndefined<Expected, Actual> = undefined extends Actual
  ? undefined extends Expected
    ? Actual extends undefined // If only undefined stop here, because NonNullable<undefined> = never
      ? undefined
      : NonNullableExact<NonNullable<Expected>, NonNullable<Actual>>
    : never // Actual can be undefined but not Expected: forbid the field
  : NonNullableExact<NonNullable<Expected>, NonNullable<Actual>>;

type NonNullableExact<Expected, Actual> = {
  [K in keyof Actual]: K extends keyof Expected
    ? Actual[K] extends (infer ActualElement)[]
      ? Expected[K] extends (infer ExpectedElement)[] | undefined | null
        ? Exact<ExpectedElement, ActualElement>[]
        : never // Not both array
      : Exact<Expected[K], Actual[K]>
    : never; // Forbid extra properties
};

ملعب

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

const response = { companies };

res.json(exact<GetCompaniesResponse, typeof response>(response));
export function exact<S, T>(object: Exact<S, T>) {
  return object;
}

هنا النوع Exact هو ما ذكرArnaudBarre أعلاه.

شكرًا لك ArnaudBarre على وتعليمي بعض الأمور.
التعمق في الحل الخاص بك:

export type Exact<Expected, Actual> =
  keyof Expected extends keyof Actual
    ? keyof Actual extends keyof Expected
      ? Expected extends ExactElements<Expected, Actual>
        ? Expected
        : never
      : never
    : never;

type ExactElements<Expected, Actual> = {
  [K in keyof Actual]: K extends keyof Expected
    ? Expected[K] extends Actual[K]
      ? Actual[K] extends Expected[K]
        ? Expected[K]
        : never
      : never
    : never
};

// should succeed (produce exactly the Expected type)
let s1: Exact< { a: number; b: string }, { a: number; b: string } >;
let s2: Exact< { a?: number; b: string }, { a?: number; b: string } >;
let s3: Exact< { a?: number[]; b: string }, { a?: number[]; b: string } >;
let s4: Exact< string, string >;
let s5: Exact< string[], string[] >;
let s6: Exact< { a?: number[]; b: string }[], { a?: number[]; b: string }[] >;

// should fail (produce never)
let f1: Exact< { a: string; b: string }, { a: number; b: string } >;
let f2: Exact< { a: number; b: string }, { a?: number; b: string } >;
let f3: Exact< { a?: number; b: string }, { a: number; b: string } >;
let f4: Exact< { a: number[]; b: string }, { a: string[]; b: string } >;
let f5: Exact< { a?: number[]; b: string }, { a: number[]; b: string } >;
let f6: Exact< { a?: number; b: string; c: string }, { a?: number; b: string } >;
let f7: Exact< { a?: number; b: string }, { a?: number; b: string; c: string } >;
let f8: Exact< { a?: number; b: string; c?: string }, { a?: number; b: string } >;
let f9: Exact< { a?: number; b: string }, { a?: number; b: string; c?: string } >;
let f10: Exact< never, string >;
let f11: Exact< string, never >;
let f12: Exact< string, number >;
let f13: Exact< string[], string >;
let f14: Exact< string, string[] >;
let f15: Exact< string[], number[] >;
let f16: Exact< { a?: number[]; b: string }[], { a?: number[]; b: string } >;

الحل السابق "نجح" لـ f6 و f8 و f9.
يعرض هذا الحل أيضًا نتائج "أنظف" ؛ عندما تتطابق ، تحصل على النوع "متوقع" مرة أخرى.
كما هو الحال مع تعليق ArnaudBarre ... لست متأكدًا مما إذا تم التعامل مع جميع حالات الحافة ، لذلك ymmv ...

heystewart لا يعطي Exact نتيجة متماثلة:

let a: Exact< { foo: number }[], { foo: number, bar?: string }[] >;
let b: Exact< { foo: number, bar?: string }[], { foo: number }[] >;

a = [{ foo: 123, bar: 'bar' }]; // error
b = [{ foo: 123, bar: 'bar' }]; // no error

تحرير: إصدار ArnaudBarre به نفس المشكلة أيضًا

papb نعم ، لا تعمل variables دائمًا كائنًا.

لحلها ، تحتاج إلى عزل ExactObject و ExactArray ولديك نقطة دخول تدخل في أحدهما أو الآخر.

إذن ما هي أفضل طريقة للتأكد من أن هذا الكائن له خصائص دقيقة ، لا أقل ، لا أكثر؟

@ captain-yossarian اقنع فريق TypeScript بتنفيذ ذلك. لا يوجد حل معروض هنا يصلح لجميع الحالات المتوقعة ، وكلها تقريبًا تفتقر إلى الوضوح.

لا يستطيع toriningen تخيل عدد المشكلات التي سيتم إغلاقها إذا قام فريق TS بتنفيذ هذه الميزة

تضمين التغريدة
في الوقت الحالي ، لدي حالة استخدام واحدة أوصلتني إلى هنا ، وهي تدخل مباشرة في موضوعك "منوعات". أريد وظيفة:

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

هذه الأهداف الفورية تخدم هذه الغايات:

  1. أحصل على خاصية فحص فائض للمدخلات
  2. أحصل على الإكمال التلقائي وسلامة نوع الخاصية للمخرجات

مثال

لقد اختزلت حالتي إلى هذا:

type X = {
    red?: number,
    green?: number,
    blue?: number,
}

function y<
    Y extends X
>(
    y: (X extends Y ? Y : X)
) {
    if ((y as any).purple) throw Error('bla')

    return y as Y
}

const z = y({
    blue: 1,
    red: 3,
    purple: 4, // error
})
z.green // error

type Z = typeof z

يعمل هذا الإعداد ويحقق جميع الأهداف المرجوة ، لذا من وجهة نظر الجدوى البحتة وحتى الآن ، فأنا جيد. ومع ذلك ، يتم تحقيق EPC من خلال المعلمة التي تكتب (X extends Y ? Y : X) . لقد عثرت على ذلك بالصدفة ، وفوجئت إلى حد ما أنه نجح.

عرض

وهذا هو السبب في أنني أرغب في الحصول على كلمة رئيسية implements يمكن استخدامها بدلاً من extends أجل تحديد النية بأن النوع هنا ليس من المفترض أن يحتوي على خصائص زائدة. مثل ذلك:

type X = {
    red?: number,
    green?: number,
    blue?: number,
}

function x<
    Y implements X
>( y: Y ) {
    if ((y as any).purple) throw Error('bla')

    return y as Y
}

const z = y({
    blue: 1,
    red: 3,
    purple: 4, // error
})
z.green // error

type Z = typeof z

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

قد يؤدي ذلك أيضًا إلى تمكين المزيد من حالات الاستخدام المستحيلة أو غير العملية حاليًا ، ولكن هذا في الوقت الحالي مجرد شعور داخلي.

اكتشاف النوع الضعيف كبديل

والجدير بالذكر أن اكتشاف النوع الضعيف وفقًا لـ # 3842 يجب أن يعمل على إصلاح ذلك أيضًا ، وقد يكون مناسبًا بسبب عدم الحاجة إلى بناء جملة إضافي ، إذا كان يعمل فيما يتعلق بـ extends ، وفقًا لحالة الاستخدام الخاصة بي.

بخصوص Exact<Type> إلخ.

أخيرًا ، يجب أن يكون implements ، كما أتخيله ، صريحًا جدًا فيما يتعلق بنقطة function f<T extends Exact<{ n: number }>(p: T) نظرًا لأنه لا يحاول حل الحالة الأكثر عمومية وهي Exact<Type> .

بشكل عام ، يبدو أن Exact<Type> ذو فائدة قليلة إلى حد ما بجوار EPC ، ولا يمكنني تصور حالة صالحة مفيدة بشكل عام تقع خارج هذه المجموعات:

  • استدعاءات الوظائف: يمكن التعامل معها بسهولة الآن وفقًا لمثالي ، وستستفيد من implements
  • المهام: فقط استخدم القيم الحرفية ، لذلك يتم تطبيق EPC
  • البيانات من خارج مجال التحكم الخاص بك: لا يمكن لفحص النوع أن يحميك من ذلك ، عليك التعامل مع ذلك في وقت التشغيل ، وعند هذه النقطة تعود إلى المصبوبات الآمنة

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

  • إذا تم إعطاؤك بيانات التخصيص في إحدى الوظائف ، فقم بالتعامل مع فحص النوع في توقيع المكالمة
  • إذا قمت بدمج عدة كائنات ، وفقًا لـ OP ، فقم بتأكيد نوع كل كائن مصدر بشكل صحيح ويمكنك استخدام as DesiredType بأمان

ملخص: implements سيكون أمرًا رائعًا ولكن بخلاف ذلك فنحن جيدون

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

سؤال لجميع الأطراف المهتمة: هل هناك أي شيء مفتوح بالفعل هنا؟

بعد النظر في حالات الاستخدام هنا ، أعتقد أنه يتم التعامل مع جميع repros بشكل صحيح الآن ، ويمكن جعل الباقي للعمل مع المثال الصغير أعلاه. هذا يطرح السؤال: هل لا يزال لدى أي شخص مشكلات تتعلق بهذا الأمر اليوم مع TS المحدّث؟

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

يساوي تمامًا ، أي لا أكثر ولا أقل:

function foo(p:{|x:any,y:any|})

//it matched 
foo({x,y})
//no match
foo({x})
foo({y})
foo({x,y,z})
foo({})

أكثر وليس أقل:

function foo(p:{|x:any,y:any, ...|})

//it matched 
foo({x,y})
foo({x,y,z})

//no matched
foo({x})
foo({y})
foo({x,z})

لا أكثر ولكن أقل:

function foo(p:{x:any,y:any})

//it matched 
foo({x,y})
foo({x})
foo({y})

//no match
foo({x,z})
foo({x,y,z})

الى حد ما:

function foo(p:{x:any,y:any, ...})

//it matched 
foo({x,y})
foo({x})
foo({y})
foo({x,z})
foo({x,y,z})

استنتاج:

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

function foo(p:[|x,y|]) // p.length === 2
function foo(p:[|x,y, ... |]) // p.length >= 2
function foo(p:[x,y]) // p.length >= 0
function foo(p:[x,y,...]) // p.length >= 0

rasenplanscher باستخدام

const x = { blue: 1, red: 3, purple: 4 };
const z = y(x);

ولكن مع الأنواع الدقيقة ، لا ينبغي. أي أن السؤال هنا هو عدم الاعتماد على EPC.

@ xp44mm "أكثر ولكن ليس أقل" هو السلوك بالفعل و "أكثر أو أقل" هو السلوك إذا قمت بتمييز كل الخصائص بشكل اختياري

function foo(p:{x?: any, y?: any}) {}
const x = 1, y = 1, z = 1
// all pass
foo({x,y})
foo({x})
foo({y})
const p1 = {x,z}
foo(p1)
const p2 = {x,y,z}
foo(p2)

وبالمثل ، إذا كانت لدينا أنواع محددة ، فسيكون النوع الدقيق + جميع الخصائص الاختيارية في الأساس "ليس أكثر ولكن أقل".

مثال آخر لهذه القضية. مظاهرة جيدة لهذا الاقتراح على ما أعتقد. في هذه الحالة ، أستخدم rxjs للعمل مع الموضوع ولكني أرغب في إرجاع طريقة ("مقفلة") يمكن ملاحظتها (والتي لا تحتوي على طريقة next ، error ، إلخ. القيمة.)

someMethod(): Observable<MyType> {
  const subject = new Subject<MyType>();

  // This works, but should not. (if this proposal is implemented.)
  return subject;

  // Only Observable should be allowed as return type.
  return subject.asObservable();
}

أريد دائمًا فقط إرجاع النوع الدقيق Observable وليس Subject الذي يمتد إليه.

عرض:

// Adding exclamation mark `!` (or something else) to match exact type. (or some other position `method(): !Foo`, ...)
someMethod()!: Observable<MyType> {
  // ...
}

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

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