Typescript: اقتراح: خيار لتضمين غير معرف في فهرس التوقيعات

تم إنشاؤها على ٣١ يناير ٢٠١٧  ·  242تعليقات  ·  مصدر: microsoft/TypeScript

تحديث : للحصول على أحدث اقتراحي ، راجع التعليق https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -406316164

مع تمكين strictNullChecks ، لا يتضمن TypeScript undefined في تواقيع الفهرس (على سبيل المثال ، على كائن أو مصفوفة). هذا تحذير معروف جيدًا وتمت مناقشته في العديد من المشكلات ، وهي https://github.com/Microsoft/TypeScript/issues/9235 ، https://github.com/Microsoft/TypeScript/issues/13161 ، https: // github .com / Microsoft / TypeScript / issues / 12287 و https://github.com/Microsoft/TypeScript/pull/7140#issuecomment -192606629.

مثال:

const xs: number[] = [1,2,3];
xs[100] // number, even with strictNullChecks

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

مثال على تواقيع الفهرس بما في ذلك undefined :

const xs: number[] = [1,2,3];
xs[100] // number | undefined

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

Committed Suggestion

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

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

أليس أحد أهداف TypeScript هو السماح باكتشاف الأخطاء في وقت "الترجمة" بدلاً من الاعتماد على المستخدم لتذكر / معرفة القيام بشيء محدد؟ يبدو أن هذا يتعارض مع هذا الهدف ؛ مطالبة المستخدم بفعل شيء ما لتجنب الأعطال. يمكن قول الشيء نفسه بالنسبة للعديد من الميزات الأخرى ؛ ليست هناك حاجة إليها إذا كان المطور يفعل دائمًا x. الهدف من TypeScript (على الأرجح) هو تسهيل المهمة والقضاء على هذه الأشياء.

لقد صادفت هذا الخطأ لأنني كنت أقوم بتمكين strictNullChecks على الكود الحالي ولدي بالفعل مقارنة لذلك حصلت على الخطأ. إذا كنت أكتب رمزًا جديدًا ، فربما لم أدرك المشكلة هنا (كان نظام الكتابة يخبرني دائمًا أنني أحصل على قيمة دائمًا) وانتهى الأمر بفشل وقت التشغيل. الاعتماد على مطوري TS لتذكر (أو ما هو أسوأ ، حتى يعرفون) أنه من المفترض أن يعلنوا عن جميع خرائطهم بـ | undefined يبدو أن TypeScript يفشل في فعل ما يريده الناس بالفعل.

ال 242 كومينتر

مع استثناءrictNullChecks ، ليس لدينا علامات تغير سلوك نظام النوع. تعمل العلامات عادةً على تمكين / تعطيل الإبلاغ عن الأخطاء.
يمكنك دائمًا الحصول على إصدار مخصص من المكتبة يحدد جميع المفهرسات بـ | undefined . يجب أن تعمل كما هو متوقع.

mhegazy هذه فكرة مثيرة للاهتمام. أي إرشادات حول كيفية تجاوز توقيعات النوع للمصفوفة / الكائن؟

يوجد interface Array<T> في lib.d.ts . لقد بحثت عن طريق regexp \[\w+: (string|number)\] للعثور على توقيعات فهرسة أخرى أيضًا.

مثير للاهتمام ، لذلك جربت هذا:

{
    // https://github.com/Microsoft/TypeScript/blob/1f92bacdc81e7ae6706ad8776121e1db986a8b27/lib/lib.d.ts#L1300
    declare global {
        interface Array<T> {
            [n: number]: T | undefined;
        }
    }

    const xs = [1,2,3]
    const x = xs[100]
    x // still number :-(
}

أيه أفكار؟

انسخ lib.d.ts محليًا ، قل lib.strict.d.ts ، قم بتغيير توقيع الفهرس إلى [n: number]: T | undefined; ، وقم بتضمين الملف في التجميع الخاص بك. يجب أن ترى التأثير المقصود.

رائع ، شكرا على ذلك.

تكمن المشكلة في الإصلاح المقترح هنا في أنه يتطلب إنشاء ملف منفصل lib والحفاظ عليه.

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

في ملاحظة جانبية ، من المثير للاهتمام أن نوع التوقيع للطريقة get في مجموعات ES6 ( Map / Set ) يُرجع T | undefined عندما Array / Object توقيعات الفهرس لا تفعل ذلك.

هذا قرار واع. سيكون مزعجًا جدًا أن يكون هذا الرمز خطأ:

var a = [];
for (var i =0; i< a.length; i++) {
    a[i]+=1; // a[i] is possibly undefined
}

وسيكون من غير المعقول مطالبة كل مستخدم باستخدام ! . أو أن يكتب

var a = [];
for (var i =0; i< a.length; i++) {
    if (a[i]) {
        a[i]+=1; // a[i] is possibly undefined
    }
}

بالنسبة للخريطة ليس هذا هو الحال بشكل عام.

وبالمثل بالنسبة لأنواعك ، يمكنك تحديد | undefined على جميع تواقيع الفهرس الخاصة بك ، وستحصل على السلوك المتوقع. لكن بالنسبة لـ Array فهذا ليس معقولاً. نرحب بك لتقسيم المكتبة وإجراء أي تغييرات تحتاج إلى القيام بها ، ولكن ليس لدينا أي خطط لتغيير الإعلان في المكتبة القياسية في هذه المرحلة.

لا أعتقد أن إضافة علم لتغيير شكل الإعلان هو أمر سنفعله.

mhegazy لكن للصفائف مع الثقوب a[i] هو في الواقع ربما غير معروف:

let a: number[] = []
a[0] = 0
a[5] =0
for (let i = 0; i < a.length; i++) {
  console.log(a[i])
}

الإخراج هو:

undefined
undefined
undefined
undefined
0

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

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

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

أليس أحد أهداف TypeScript هو السماح باكتشاف الأخطاء في وقت "الترجمة" بدلاً من الاعتماد على المستخدم لتذكر / معرفة القيام بشيء محدد؟ يبدو أن هذا يتعارض مع هذا الهدف ؛ مطالبة المستخدم بفعل شيء ما لتجنب الأعطال. يمكن قول الشيء نفسه بالنسبة للعديد من الميزات الأخرى ؛ ليست هناك حاجة إليها إذا كان المطور يفعل دائمًا x. الهدف من TypeScript (على الأرجح) هو تسهيل المهمة والقضاء على هذه الأشياء.

لقد صادفت هذا الخطأ لأنني كنت أقوم بتمكين strictNullChecks على الكود الحالي ولدي بالفعل مقارنة لذلك حصلت على الخطأ. إذا كنت أكتب رمزًا جديدًا ، فربما لم أدرك المشكلة هنا (كان نظام الكتابة يخبرني دائمًا أنني أحصل على قيمة دائمًا) وانتهى الأمر بفشل وقت التشغيل. الاعتماد على مطوري TS لتذكر (أو ما هو أسوأ ، حتى يعرفون) أنه من المفترض أن يعلنوا عن جميع خرائطهم بـ | undefined يبدو أن TypeScript يفشل في فعل ما يريده الناس بالفعل.

ما زلنا متشككين تمامًا في أن أي شخص سيحصل على أي فائدة من هذا العلم في الممارسة العملية. يمكن بالفعل الاشتراك في الخرائط والأشياء المشابهة للخرائط في | غير محدد في مواقع تعريفهم
أليس أحد أهداف TypeScript هو السماح باكتشاف الأخطاء في وقت "الترجمة" بدلاً من الاعتماد على المستخدم لتذكر / معرفة القيام بشيء محدد؟

في الواقع الهدف هو:

1) حدد إحصائيًا التركيبات التي من المحتمل أن تكون أخطاء.

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

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

ذهبت إلى التفكير أكثر قليلاً في هذا التعليق https://github.com/Microsoft/TypeScript/issues/11238#issuecomment -250562397

فكر في نوعي المفاتيح في العالم: تلك التي تعرف أنها تمتلك خاصية مقابلة في كائن ما (آمن) ، تلك التي لا تعرف أن لها خاصية مقابلة في كائن ما (خطير).

تحصل على النوع الأول من المفتاح ، وهو مفتاح "آمن" ، عن طريق كتابة رمز صحيح مثل

for (let i = 0; i < arr.length; i++) {
  // arr[i] is T, not T | undefined

أو

for (const k of Object.keys(obj)) {
  // obj[k] is T, not T | undefined

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

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

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i].name);

و TypeScript يشكو منك أن arr[i] قد يكون undefined على الرغم من أني أنظر فقط @ #٪ # ing اختبرت ذلك . الآن أصبحت معتادًا على كتابة كود مثل هذا ، وشعرت بالغباء:

for (let i = 0; i < arr.length; i++) {
  // TypeScript makes me use ! with my arrays, sad.
  console.log(arr[i]!.name);

أو ربما تكتب رمزًا مثل هذا:

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k].name);
  }
}

ويقول TypeScript "مرحبًا ، قد يكون تعبير الفهرس هذا | undefined ، لذا عليك إصلاحه وفقًا للواجب لأنك رأيت هذا الخطأ 800 مرة بالفعل:

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k]!.name); // Shut up TypeScript I know what I'm doing
  }
}

لكنك لم تصلح الخلل . كنت تقصد كتابة Object.keys(yourObj) ، أو ربما myObj[k] . هذا هو أسوأ نوع من أخطاء المترجم ، لأنه لا يساعدك في الواقع في أي سيناريو - إنه تطبيق نفس الطقوس فقط على كل نوع من التعبيرات ، بغض النظر عما إذا كان في الواقع أكثر خطورة من أي تعبير آخر من نفس النموذج أم لا.

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

حدد بشكل إحصائي التركيبات التي من المحتمل أن تكون أخطاء.

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

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

الإصلاح بـ ! أنا لا أحب ذلك أيضًا - ماذا لو أتى شخص ما وأجرى تغييرًا بحيث أصبح الافتراض غير صالح الآن؟ لقد عدت إلى المربع الأول (فشل محتمل في وقت التشغيل لشيء يجب أن يلتقطه المترجم). يجب أن تكون هناك طرق آمنة لتعداد المصفوفات التي لا تعتمد على الكذب بشأن الأنواع أو استخدام ! (على سبيل المثال ، ألا يمكنك فعل شيء مثل array.forEach(i => console.log(i.name) ؟).

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

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

على أي حال ، هذا فقط سنتان .. لا أريد سحب هذا - أنا متأكد من أنك تفهم من أين أتينا وأنت في وضع أفضل بكثير لاتخاذ قرارات بشأن هذا مني :-)

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

for (let i = 0; i < a.length; i++) {
  const value = a[i];
}

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

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

لذلك تصبح مقايضة ... بدون تحسينات CFA ، قم بتعقيد 90 ٪ من التعليمات البرمجية باستخدام Red herrings للقبض على رمز سيئ بنسبة 10 ٪. وإلا فإنها تستثمر في تحسينات CFA الرئيسية ، والتي قد تحدث مع عواقبها الخاصة بالاستقرار والقضايا مرة أخرى ، وإيجاد ما يمكن أن يكون رمزًا غير آمن.

لا يوجد سوى الكثير الذي يمكن لـ TypeScript القيام به لإنقاذنا من أنفسنا.

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

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

إذا كان هذا هو نوعك ، أضف | undefined إلى توقيع الفهرس. من الخطأ بالفعل الفهرسة في نوع لا يحتوي على توقيع فهرس تحت --noImplicitAny .
ES6 Map تم تعريفه بالفعل مع get كـ get(key: K): V | undefined; .

أعدت كتابة جميع تعريفات المصفوفات والخرائط لعمل توقيعات الفهرس بإرجاع | undefined ، ولم يندم أبدًا منذ ذلك الحين ، وجدت بعض الأخطاء ، ولا يسبب أي إزعاج لأنني أعمل مع المصفوفات بشكل غير مباشر عبر lib مصنوع يدويًا يحتفظ بالشيكات للغير معرّف أو ! بداخله

سيكون أمرًا رائعًا إذا كان بإمكان TypeScript التحكم في تدفق عمليات التحقق مثل C # (لإزالة فحوصات نطاق الفهرس لتوفير بعض وقت المعالج) ، على سبيل المثال:

declare var values: number[];
for (let index = 0, length = values.length; index< length; index ++) {
   const value = value[index]; // always defined, because index is within array range and only controlled by it
}

(لأولئك الذين يستخدمون المصفوفات المتناثرة - اقتل نفسك بنيران مشتعلة ساخنة)

بالنسبة إلى Object.keys ، يتطلب الأمر نوعًا خاصًا لنقل allkeysof T للسماح لتحليل تدفق التحكم بإجراء عمليات تضييق آمنة

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

هناك (على الأقل) مشكلتان أخريان تتعلقان بوضع |undefined في تعريفات نوع الكائن لديك:

  • هذا يعني أنه يمكنك تعيين غير معرف في هذه الكائنات ، وهو بالتأكيد غير مقصود
  • تتطلب العمليات الأخرى ، مثل Object.values (أو _.values ) التعامل مع undefined في النتائج

تبلغ tslint عن تحذير إيجابي خاطئ لحالة ثابتة ، لأن الكتابة المطبوعة ترجع معلومات نوع خاطئة (= تفتقر إلى | undefined ).
https://github.com/palantir/tslint/issues/2944

أحد الأخطاء التي يتم تخطيها بانتظام مع عدم وجود | undefined في فهرسة المصفوفة هو هذا النمط عند استخدامه بدلاً من find :

const array = [ 1, 2, 3 ];
const firstFour = array.filter((x) => (x === 4))[0];
// if there is no `4` in the `array`,
// `firstFour` will be `undefined`, but TypeScript thinks `number` because of the indexer signature.
const array = [ 1, 2, 3 ];
const firstFour = array.find((x) => (x === 4));
// `firstFour` will be correctly typed as `number | undefined` because of the `find` signature.

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

for (let i = 0; i < arr.length; i++) {
  foo(arr[i]!)
}

أيضًا ، هذه المشكلة ليست مشكلة مع الحلقات الأحدث والأفضل for of وحتى أن هناك قاعدة TSLint prefer-for-of تخبرك بعدم استخدام النمط القديم for الحلقات أي أكثر من ذلك.

أشعر حاليًا أن نظام الكتابة غير متناسق بالنسبة للمطور. array.pop() يتطلب if تحقق أو ! التأكيد، ولكن الوصول عبر [array.length - 1] لا. ES6 map.get() يتطلب شيك if أو تأكيد ! ، لكن تجزئة الكائن لا. ومثالsompylasar جيد أيضًا.

مثال آخر هو التدمير:

const specifier = 'Microsoft/TypeScript'
const [repo, revision] = specifier.split('@') // types of repo and revision are string
console.log('Repo: ' + repo)
console.log('Short rev: ' + revision.slice(0, 7)) // Error: Cannot call function 'slice' on undefined

كنت أفضل إذا أجبرني المترجم على القيام بذلك:

const specifier = 'Microsoft/TypeScript'
const [repo, revision] = specifier.split('@') // types of repo and revision are string | undefined
console.log('Repo: ', repo || 'no repo')
console.log('Short rev:', revision ? revision.slice(0, 7) : 'no revision')

هذه أخطاء فعلية رأيتها كان من الممكن أن يمنعها المترجم.

لا يجب أن ينتمي هذا Imo إلى ملفات الكتابة ، ولكن يجب أن يكون ميكانيكي نظام من النوع - عند الوصول إلى أي شيء بتوقيع فهرس ، يمكن أن يكون undefined . إذا كان منطقك يؤكد أنه ليس كذلك ، فما عليك سوى استخدام ! . وإلا أضف if وستكون جيدًا.

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

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

pelotom ما هو مصدر

@ aleksey-bykov في الغالب توقيعات فهرس الكائنات ، والتي تحدث على نطاق واسع في مكتبات الجهات الخارجية. أرغب في الوصول إلى عقار على { [k: string]: A } لتحذيرني من أن النتيجة ربما تكون غير محددة. لقد ذكرت فقط فهرسة المصفوفة لأنه تم طرحها كحالة تفسر سبب إزعاج العلم للعمل معها.

هل تعلم أنه يمكنك إعادة كتابتها بالطريقة التي تريدها بالضبط؟ (مع القليل من العمل الإضافي)

نعم ، يمكنني إعادة كتابة كتابات الجميع نيابة عنهم ، أو يمكنني تشغيل علامة المترجم

استمر في لعب الكابتن O ...: يمكنك إعادة كتابة lib.d.ts اليوم وتكون مالكًا سعيدًا لمزيد من قاعدة الرموز الصوتية أو يمكنك انتظار العلم للسنوات N القادمة

@ aleksey-bykov كيف يتم ذلك بإعادة كتابة lib.d.ts ؟

declare type Keyed<T> = { [key: string]: T | undefined; }

ثم في تعريف Array في lib.es2015.core.d.ts ، استبدل

[n: number]: T;

مع

[n: number]: T | undefined;

@ aleksey-bykov ربما فاتك الجزء الذي قلت فيه إنني لا أهتم بالمصفوفات. أنا مهتم بالمكان الذي أعلنت فيه مكتبات الطرف الثالث أن شيئًا ما من النوع { [k: string]: T } ، وأريد الوصول إلى مثل هذا الكائن لإرجاع شيء ربما غير محدد. لا توجد طريقة لتحقيق ذلك ببساطة عن طريق تحرير lib.d.ts ؛ يتطلب تغيير توقيعات المكتبة المعنية.

هل لديك سيطرة على ملفات تعريف الطرف الثالث؟ إذا كان الأمر كذلك يمكنك إصلاحها

والآن عدنا إلى

نعم ، يمكنني إعادة كتابة كتابات الجميع نيابة عنهم ، أو يمكنني تشغيل علامة المترجم

الوقت دائرة مسطحة.

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

نعم ، لا بد لي من تعديل كتابات الآخرين طوال الوقت وأود أن أقوم بذلك أقل.

وستكون في غضون N سنوات ، ربما ، في الوقت الحالي يمكنك أن تتألم أو تتعافى

شكرا لمساهمتك البناءة بشكل لا يصدق 👍

المدخلات البناءة لهذا هي كما يلي ، يجب إغلاق هذه القضية ، للأسباب التالية:
أ. إما أن القرار بشأن ما إذا كان [x] يمكن أن يكون undefined أم لا متروك للمطورين بواسطة

  • السماح لهم بالاحتفاظ بكل شيء في رؤوسهم كما فعلوا دائمًا من قبل
  • أو عن طريق تعديل lib.d.ts و 3rd-party.d.ts كما تم اقتراحه

ب. أو يستغرق بناء جملة خاص / أنواع / تحليل التدفق / سنوات N للتخفيف من شيء يمكن القيام به بسهولة باليد في #a

المشكلة هي اقتراح لـ (ب) ، باستثناء عدم اقتراح بناء جملة جديد ، إنها مجرد علامة للمترجم.

يعود الأمر إلى أن النوع { [x: string]: {} } يكاد يكون دائمًا كذبة ؛ باستثناء استخدام Proxy ، لا يوجد كائن يمكن أن يحتوي على عدد لا نهائي من الخصائص ، ناهيك عن كل سلسلة ممكنة. الاقتراح هو أن يكون لديك علم مترجم يعترف بذلك. قد يكون من الصعب جدًا تنفيذ هذا على ما يتم اكتسابه ؛ سأترك هذا النداء للمنفذين.

النقطة هي أن أيا منهما

  • T | undefined
  • ولا T

مناسب للحالة العامة

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

T | undefined صحيح للحالة العامة ، للأسباب التي قدمتها للتو. سوف نتجاهل مشوراتك غير المنطقية حول الأنواع التابعة ، أتمنى لك يومًا سعيدًا.

يمكنك تجاهلي بقدر ما تريد ولكن T | undefined هو تجاوز ل

declare var items: number[];
for (var index = 0; index < items.length; index ++) {
   void items[index];
}

أفضل أن يكون هناك T | undefined هناك افتراضيًا وأخبر المترجم أن index هو نطاق فهرس رقمي من items وبالتالي لا يخرج إذا كانت الحدود مطبقة على items ؛ في الحالات البسيطة مثل مجموعة الأشكال الحلقية المستخدمة بشكل متكرر for / while ، يمكن للمجمع استنتاج ذلك تلقائيًا ؛ في الحالات المعقدة ، آسف ، يمكن أن يكون هناك undefined s. ونعم ، ستكون الأنواع القائمة على القيمة مناسبة هنا ؛ أنواع السلاسل الحرفية مفيدة جدًا ، فلماذا لا يوجد أنواع منطقية حرفية وأنواع عدد ونطاق / مجموعة نطاقات؟ بقدر ما يذهب TypeScript ، فإنه يحاول تغطية كل ما يمكن التعبير عنه باستخدام JavaScript (على عكس ، على سبيل المثال ، Elm الذي يحد من ذلك).

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

@ aleksey-bykov ، فضولي ما هي تجربتك بعد هذا التغيير؟ كم مرة يجب عليك استخدام ! ؟ وكم مرة تجد المترجم يقوم بالإبلاغ عن الأخطاء الفعلية؟

mhegazy بصراحة ، لم ألاحظ فرقًا كبيرًا في الانتقال من T إلى T | undefined ، كما أنني لم ألاحظ أي أخطاء ، أعتقد أن مشكلتي هي أنني أعمل مع المصفوفات عبر وظائف الأداة التي تحافظ على ! فيها ، لذلك لم يكن هناك تأثير للكود الخارجي حرفيًا:

https://github.com/aleksey-bykov/basic/blob/master/array.ts

في أي ملف lib يمكنني العثور على تعريف نوع الفهرس للكائنات؟ لقد حددت موقع Array وقمت بتحديثه من [n: number]: T إلى [n: number]: T | undefined . الآن أود أن أفعل الشيء نفسه مع الأشياء.

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

تحتاج إلى البحث عن تعريفات دقيقة لكل حالة في التعليمات البرمجية الخاصة بك وإصلاحها

ماذا عن البحث المباشر عن مفتاح؟ على سبيل المثال

const xs = { foo: 'bar' }
xs['foo']

هل هناك أي طريقة لفرض T | undefined بدلاً من T هنا؟ أستخدم حاليًا هؤلاء المساعدين في قاعدة التعليمات البرمجية الخاصة بي في كل مكان ، كنوع بدائل آمنة لفهرسة عمليات البحث على المصفوفات والكائنات:

// TS doesn't return the correct type for array and object index signatures. It returns `T` instead
// of `T | undefined`. These helpers give us the correct type.
// https://github.com/Microsoft/TypeScript/issues/13778
export const getIndex = function<X> (index: number, xs: X[]): X | undefined {
  return xs[index];
};
export const getKeyInMap = function<X> (key: string, xs: { [key: string]: X }): X | undefined {
  return xs[key];
};

mhegazy أثناء كتابة هذا ، أقوم بإصلاح خطأ في الإنتاج على https://unsplash.com كان من الممكن اكتشافه بأنواع توقيع مؤشر أكثر صرامة.

انظر ، ضع في اعتبارك عامل تشغيل النوع المعين:

const xs = { foo: 'bar' };
type EachUndefined<T> = { [P in keyof T]: T[P] | undefined; }
const xu : EachUndefined<typeof xs> = xs;
xu.foo; // <-- string | undefined

إذا كان العلم مثل --strictArrayIndex ليس خيارًا لأن العلامات غير مصممة لتغيير ملفات lib.d.ts . ربما يمكنكم يا رفاق إصدار نسخ صارمة من ملفات lib.d.ts مثل " lib": ['strict-es6'] ؟

يمكن أن يحتوي على تحسينات متعددة ، وليس فقط فهرس صفيف صارم. على سبيل المثال ، Object.keys :

interface ObjectConstructor {
    // ...
    keys(o: {}): string[];
}

ممكن ان يكون:

interface ObjectConstructor {
    // ...
    keys<T>(o: T): (keyof T)[];
}

تحديث من SBS اليوم: صرخنا على بعضنا البعض لمدة 30 دقيقة ولم يحدث شيء. 🤷‍♂️

RyanCavanaugh ما هي

radix "Slog Backlog Slog"

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

RyanCavanaugh لقد كنت أفكر في هذا ، يبدو أن الحيلة التالية ستغطي 87٪ من جميع الحالات:

  • values[index] يعطي T إذا تم الإعلان عن الفهرس بـ for (HERE; ...)
  • values[somethingElse] يعطي T | undefined لجميع المتغيرات المعلنة خارج for

@ aleksey-bykov ناقشنا شيئًا أكثر ذكاءً - يمكن أن يكون هناك آلية حراسة فعلية لـ " arr[index] تم اختبارها بواسطة index < arr.length . لكن هذا لن يساعد في الحالة التي تكون فيها pop 'd في منتصف الحلقة ، أو مررت المصفوفة الخاصة بك إلى وظيفة أزالت العناصر منها. لا يبدو حقًا أن الناس يبحثون عن آلية تمنع أخطاء OOB بنسبة 82.9٪ من الوقت - بعد كل شيء ، فإن هذا الجزء تقريبًا من كود فهرسة المصفوفة صحيح على أي حال. إن إضافة مراسم لتصحيح الكود مع عدم اكتشاف الأخطاء في الحالات غير الصحيحة هي نتيجة سيئة.

لا يعني ضمنيًا أن أليكسي سوف يغير مصفوفة

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

  • لا يمكنك تتبع الطفرات في الحاوية
  • لا يمكنك تتبع التلاعب بالفهرس

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

كما قلت عادة

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

طالما تم الإعلان عن الفهرس بطريقة ما (لا يمكننا قول شيء عنه بثقة) ، فمن العدل الاعتقاد بأن المؤشر صحيح مع جسم الحلقة

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

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

إضافة حفل لتصحيح الكود

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

بينما لا يصطاد البق

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

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

نحن لا نفعل أي شيء غريب والأنواع خاطئة.

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

... رمز العمل الذي فشل في التجميع

لست على علم بأي منها - هل يمكنك الإشارة إليها على وجه التحديد؟

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

ما هو الاختلاف المفيد بين هذا وقاعدة TSLint التي تنص على "يجب أن تحتوي كل تعبيرات وصول المصفوفة على ! " لاحقًا؟

RyanCavanaugh افترض أن قاعدة TSLint لن تكون قادرة على تضييق الأنواع أو استخدام تحليل نوع تدفق التحكم (على سبيل المثال ، التفاف الوصول في if ، طرح استثناء ، return ing أو continue ing https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -336265143؟ لفرض ! ، يجب إنشاء جدول لتتبع أنواع هذه المتغيرات على الأرجح undefined . وهو ما يبدو لي وكأنه شيء يجب أن يقوم به مدقق الكتابة ، وليس الوبر.

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

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

... رمز العمل الذي فشل في التجميع

لست على علم بأي منها - هل يمكنك الإشارة إليها على وجه التحديد؟

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

يُسمح دائمًا بمقارنة شيء ما بـ undefined

وهو عار

const canWeDoIt = null === undefined; // yes we can!

يُسمح دائمًا بمقارنة شيء ما بـ undefined

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

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

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

export type Chooser = (context?: Context) => number | string;
export interface Choices {
    [choice: number]: Struct;
    [choice: string]: Struct;
}

export const Branch = (chooser: Chooser, choices: Choices, context?: Context): Struct => {
    return choices[chooser(context)];  // Could be undefined
}

فيما يتعلق بالكائنات وتغيير التوقيع ببساطة ليشمل | undefined ، @types/node يفعل ذلك مقابل process.env :

    export interface ProcessEnv {
        [key: string]: string | undefined;
    }

لكنها لا تسمح بتضييق الكتابة على الإطلاق:

process.env.SOME_CONFIG && JSON.parse(process.env.SOME_CONFIG)

يعطي

[ts]
Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.

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

أعتقد أن هذا مرتبط بهذا الخطأ: https://github.com/Microsoft/TypeScript/issues/17960

يمكنك حلها عن طريق تعيين المتغير env إلى متغير ، ثم حماية ما يلي:

const foo = process.env.SOME_CONFIG
foo && JSON.parse(foo);

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

interface ProcessEnv {
  foo?: string;
  bar?: string;
}

بدون | undefined في توقيع الفهرس يشتكي TSC مع Property 'foo' of type 'string | undefined' is not assignable to string index type 'string'. .

أعلم أن هذا له تكلفة إذا كان عليك العمل مع الأنواع التي لديها | undefined الإضافي والأنواع التي لا تملكها.

سيكون من الرائع حقًا إذا تمكنا من التخلص من هذا.

أحاول جمع أفكاري حول هذه المسألة.

الكائنات عبارة عن حقيبة مختلطة في JavaScript. يمكن استخدامها لغرضين :

  • القواميس الملقبة بالخرائط ، حيث تكون المفاتيح غير معروفة
  • السجلات ، حيث تُعرف المفاتيح

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

بالنسبة للكائنات المستخدمة كقواميس (تُعرف أيضًا باسم الخرائط) والمصفوفات ، يتم استخدام تواقيع الفهرس ، ويمكننا اختيار تضمين | undefined في نوع القيمة. على سبيل المثال ، { [key: index]: string | undefined } . جميع عمليات البحث عن المفاتيح صالحة (لأن المفاتيح غير معروفة في وقت الترجمة) ، وجميع المفاتيح ترجع نفس النوع (في هذا المثال ، T | undefined ).

يجب استخدام توقيعات الفهرس فقط لنمط كائنات القاموس والمصفوفات ، فمن المرغوب فيه أن يقوم TypeScript بفرض | undefined في نوع قيمة توقيع الفهرس: إذا كانت المفاتيح غير معروفة ، ومن المحتمل أن تؤدي عمليات البحث عن المفاتيح إلى إرجاع undefined .

هناك أمثلة جيدة للأخطاء التي قد تظهر غير مرئية بدون ذلك ، مثل Array.prototype.find إرجاع undefined ، أو عمليات البحث الرئيسية مثل array[0] إرجاع undefined . (التدمير هو مجرد بناء جملة سكر لعمليات البحث الرئيسية.) من الممكن كتابة وظائف مثل getKey لتصحيح نوع الإرجاع ، لكن علينا الاعتماد على الانضباط لفرض استخدام هذه الوظائف عبر قاعدة بيانات.

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

هل هذه هي القضية المعلقة ، أم أنني أسأت الفهم؟

ما هي حالات الاستخدام والمشكلات التي لم أذكرها؟

هناك (على الأقل) مشكلتان أخريان تتعلقان بوضع | غير محدد في تعريفات نوع الكائن لديك:

  • هذا يعني أنه يمكنك تعيين غير معرف في هذه الكائنات ، وهو بالتأكيد غير مقصود
  • تتطلب العمليات الأخرى ، مثل Object.values ​​(أو _.values) معالجة غير محددة في النتائج

أعتقد أن هذه نقطة مهمة للغاية.

في الوقت الحالي ، أقوم بتجربة النهج التالي:

حدد const safelyAccessProperty = <T, K extends keyof T>(object: T, key: K): T[K] | undefined => object[key];

ثم قم بالوصول إلى خصائص مثل safelyAccessProperty(myObject, myKey) ، بدلاً من myObject[myKey] .

plul التقاط جيد. تركز المناقشة حاليًا على عمليات القراءة ، ولكن تعريف نوع المفهرس في الواقع ذو شقين ، وإضافة | undefined سيسمح لكتابة قيم undefined .

و safelyAccessProperty وظيفة كنت تجريب ( المذكورة أعلاه و getKey من قبلOliverJAsh) تتطلب الانضباط و / أو قاعدة اللنت لتمنع عمليات الفهرسة على جميع المصفوفات والكائنات.

يمكن جعل هذا قابلاً للتطوير إذا تم توفير الوظيفة في جميع مثيلات المصفوفة والكائن (كل نوع يوفر عمليات مفهرس) ، كما هو الحال في C ++ std::vector لديه .at() والذي يطرح استثناء في وقت التشغيل للوصول OOB ، ومشغل [] الذي لم يتم تحديده والذي في أفضل الأحوال يتعطل مع SEGFAULT عند الوصول إلى OOB ، في أسوأ الحالات يفسد الذاكرة.

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

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

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

انها تبدو مثل هذا:

type NewWay = {[key: string]?: string};
const n: NewWay = {};

// Has type string | undefined
n['foo']

// Has type Array<string>
Object.values(n)

// Doesn't work
n['foo'] = undefined;

// Works
delete n['foo'];

مقارنة بالنهج السابق | undefined :

type OldWay = {[key: string]: string | undefined};
const o: OldWay = {};

// Has type string | undefined
o['foo']

// Has type Array<string | undefined>
Object.values(o)

// Works
o['foo'] = undefined;

// Works
delete o['foo'];

لقد جئت إلى هنا من خلال رفض إضافة | undefined في DT PR أعلاه ، حيث سيؤدي ذلك إلى كسر جميع المستخدمين الحاليين لواجهة برمجة التطبيقات تلك - هل يمكن النظر إلى هذا بشكل أفضل على أنه يسمح للمستخدم باختيار مدى الانغماس الذي يريده ، بدلاً من ذلك من المكتبة؟


سألاحظ أن الخصائص الاختيارية تضيف | undefined أيضًا ، وقد أثار ذلك ضغري عدة مرات - لا يميز TS بشكل أساسي بين خاصية مفقودة ومجموعة خاصية غير محددة. أود فقط { foo?: T, bar?: T } في أن يعامل نفس { [name: 'foo' | 'bar']: T } ، أيا كانت الطريقة التي يذهب (انظر أيضا process.env التعليقات أعلاه)


هل TS ضد كسر التناظر هنا على مفهرسات الأرقام والسلسلة؟

foo[bar] && foo[bar].baz() هو نمط JS شائع جدًا ، فهو يبدو غير لائق عندما لا يكون مدعومًا من TS (بمعنى تذكيرك أنك بحاجة إلى إذا لم تضيف | undefined ، وتحذير عندما يكون من الواضح أنه ليس مطلوبًا إذا قمت بذلك).


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

class Foo {
    foo: string | number = 123

    bar() {
        this.foo = 'bar'
    }

    broken() {
        if (typeof this.foo === 'number') {
            this.bar();
            this.foo.toLowerCase(); // right, but type error
            this.foo.toExponential(); // wrong, but typechecks
        }
    }
}

لكنني أعتقد أن هذا أقل احتمالًا في الكود الحقيقي أن الحلقات القديمة تحور التكرار.

من الواضح أن هناك طلبًا على هذه الميزة. آمل حقًا أن يجد فريق TS بعض الحلول. ليس فقط بإضافة | undefined إلى المفهرس لأنه يحتوي على مشكلات خاصة به (مذكورة بالفعل) ولكن بطريقة "ذكية" أكثر (تُرجع القراءة T|undefined ، تتطلب الكتابة T ، مترجم جيد التحقق من وجود حلقة for ، وما إلى ذلك ، كما تم ذكر الاقتراح الجيد بالفعل.)

نحن على ما يرام مع خطأ وقت التشغيل عندما نتحول ونعمل مع المصفوفات بطريقة غير تافهة ، يصعب التحقق منها بطريقة المترجم. نريد فقط التحقق من الأخطاء لمعظم الحالات ، ولا مانع من استخدام ! أحيانًا.

بعد قولي هذا ، إذا تم تنفيذ هذه المعالجة الأكثر صرامة للمصفوفات ، الآن مع # 24897 سيكون من الممكن تنفيذ تضييق نوع لطيف عند التحقق من طول المصفوفة بثابت. يمكننا فقط تضييق المصفوفة لتتضاعف مع عنصر الراحة.

let arr!: string[];
if (arr.length == 3) {
  //arr is of type [string, string, string]
}

if (arr.length > 3) {
  //arr is of type [string, string, string, string, ...string[]]
}

if (arr.length) {
  //arr is of type [string, ...string[]]
}

if (arr.length < 3) {
  //arr is of type [string?, string?, string?]
  if (arr.length > 0) {
    //arr is of type [string, string?, string?]
  }
}

سيكون مفيدًا عند فهرسة مصفوفة ثابتة أو إتلافها.

let someNumber = 55;
if (arr.length) {
  let el1 = arr[0]; //string
  let el2 = arr[1]; //string | undefined
  let el3 = arr[someNumber]; //string | undefined
}

if(arr.length >= 3){
    let [el1, el2, el3, el4] = arr;
    //el1, el2, el3 are string
    // el4 is string | undefined    
}

if (arr.length == 2){
    let [el1, el2, el3] = arr; //compiler error: "Tuple type '[string, string]' with length '2' cannot be assigned to tuple with length '3'.",
}

سؤال آخر هو ماذا سنفعل بالأرقام الكبيرة مثل:

if(arr.length >= 99999){
    // arr is [string, string, ... , string, ...string[]]
}

لا يمكننا إظهار نوع هذه المجموعة الضخمة في IDE أو رسائل المترجم.

أعتقد أنه يمكن أن يكون لدينا بعض بناء الجملة لتمثيل "مجموعة ذات طول معين بنفس النوع لجميع العناصر". لذلك على سبيل المثال ، مجموعة 1000 سلسلة هي string[10000] ونوع المصفوفة الضيقة من المثال أعلاه يمكن أن يكون [...string[99999], ...string[]] .

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

شاء

أريد دائمًا نوع فهرس [key: string (or number, symbol)]: V | undefined ، وأحيانًا فقط أنسى حالة undefined . عندما يتعين على أحد المطورين أن يخبر المترجم بوضوح "صدقني ، هذا هو نوع هذا الشيء الحقيقي" ، فأنت تعلم أنه غير آمن.
من غير المنطقي كتابة Map.get بشكل صحيح (بشكل صارم) ولكن الكائنات العادية بطريقة ما تحصل على تصريح مجاني.
ومع ذلك ، يمكن إصلاح هذا بسهولة في أرض المستخدم ، لذا فهو ليس سيئًا للغاية. ليس لدي حل بأي حال من الأحوال.

المصفوفات

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

أميل إلى التفكير في أن المزيد والمزيد من الأشخاص يتبعون هاتين أفضل الممارسات:

  • استخدم أساليب أو مكتبات أصلية وظيفية لتكرار المصفوفات أو تحويلها. لا يوجد وصول قوس هنا.
  • لا تغير المصفوفات في مكانها

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

هذا يبدو وكأنه لا يحتاج إلى تفكير ولا أعتقد أن نسخ ولصق lib.d.ts محليًا هو حل بديل مقبول.

عندما نقوم بالفهرسة صراحة في مصفوفة / كائن ، على سبيل المثال للحصول على العنصر الأول من المصفوفة ( array[0] ) ، نريد أن تتضمن النتيجة undefined .

هذا ممكن عن طريق إضافة undefined إلى توقيع الفهرس.

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

يتم استخدام أنواع الفهرس / التوقيعات لكل من عمليات البحث عن الفهرس (على سبيل المثال ، array[0] ) والتعيين (على سبيل المثال for الحلقات و Array.prototype.map ) ، لكننا نطلب أنواعًا مختلفة لكل حالة من هذه الحالات.

هذه ليست مشكلة مع Map لأن Map.get دالة ، وبالتالي يمكن فصل نوع الإرجاع عن نوع القيمة الداخلية ، على عكس الفهرسة في مصفوفة / كائن ليس دالة ، وبالتالي يستخدم توقيع الفهرس مباشرة.

إذن ، يصبح السؤال: كيف يمكننا تلبية كلتا الحالتين؟

// Manually adding `undefined` to the index signature
declare const array: (number | undefined)[];

const first = array[0]; // number | undefined, as desired :-)
type IndexValue = typeof array[0]; // number | undefined, as desired! :-)

array.map(x => {
  x // number | undefined, not desired! :-(
})

عرض

خيار مترجم يتعامل مع عمليات البحث في الفهرس (على سبيل المثال ، array[0] ) مشابه لكيفية كتابة Set.get و Map.get ، بتضمين undefined في نوع قيمة الفهرس (لا توقيع الفهرس نفسه). توقيع الفهرس الفعلي نفسه لن يشتمل على undefined ، لذلك لا يتم تنفيذ وظائف التعيين.

مثال:

declare const array: number[];

// The compiler option would include `undefined` in the index value type
const first = array[0]; // number | undefined, as desired :-)
type IndexValue = typeof array[0]; // number | undefined, as desired :-)

array.map(x => {
  x // number, as desired :-)
})

ومع ذلك ، فإن هذا لن يحل حالة التكرار عبر المصفوفة / الكائنات باستخدام حلقة for ، حيث تستخدم هذه التقنية عمليات البحث عن الفهرس.

for (let i = 0; i < array.length; i++) {
  const x = array[i];
  x; // number | undefined, not desired! :-(
}

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

for (let i = 0; i < array.length; i++) {
  const x = array[i]!;
  x; // number, as desired :-)
}

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

إلغاء الاشتراك (يتم تمكين خيار المترجم افتراضيًا ، وإلغاء الاشتراك عند الحاجة):

declare const array: { [index: number]!!: string };

declare const dictionary: { [index: string]!!: string }

الاشتراك (لا يوجد خيار مترجم ، فقط قم بالاشتراك عند الحاجة):

declare const array: { [index: string]!!: string };

declare const dictionary: { [index: string]??: string }

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

على سبيل المثال:

const balls = [1, 2 ,3];

بشكل افتراضي ، سيتم التعامل مع [number, number, number] balls على أنه [number, number, number] . يمكن تجاوز هذا عن طريق كتابة:

const balls: number[] = [1, 2 ,3];

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

const balls: [number, number, number] = [1, 2 ,3];
const n = balls[100];

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

@ buu700 مرحبًا بك في TypeScript 3.0: https://blogs.msdn.microsoft.com/typescript/2018/07/30/announcing-typescript-3-0/#richer -tuple-types

لطيف - جيد! حسنًا ، هذا توقيت مثير للاهتمام. كان إعلان الإصدار مفتوحًا ، لكنني لم أقرأه بعد ؛ لقد جئت إلى هنا فقط بعد أن واجهت موقفًا احتجت فيه إلى القيام ببعض عمليات الصب الغريبة ( (<(T|undefined)[]> arr).slice(-1)[0] ) لجعل TypeScript (2.9) يفعل ما أريده أن يفعله.

أردت فقط إعادة الأمور إلى هذا الاقتراح: https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -383072468

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

mhegazyRyanCavanaugh هل من أفكار حول اقتراحي؟ https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -406316164

بالنسبة لي ، هناك حل بسيط. تمكين العلم الذي يتحقق من هذا. ثم بدلاً من:

const array = [1, 2, 3];
for (var i =0; i< array.length; i++) {
    array[i]+=1; // array[i] is possibly undefined
}

أنت تفعل:

const array = [1, 2, 3];
array.forEach((value, i) => array[i] = value + 1);

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

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

بصفتي مبرمجًا جديدًا في TypeScript ، وجدت أن الموقف حول فهرسة الكائنات في الوضع الصارم غير بديهي. كنت أتوقع أن تكون نتيجة البحث T | undefined ، على غرار Map.get . على أي حال ، لقد واجهت هذا مؤخرًا ، وفتحت إصدارًا في مكتبة:

سالب 10

ربما سأغلقه الآن ، لأنه يبدو أنه لا يوجد حل جيد. أعتقد أنني سأحاول "الاشتراك" في T | undefined باستخدام وظيفة مساعدة صغيرة:

export function lookup<T>(map: {[index: string]: T}, index: string): T|undefined {
  return map[index];
}

كانت هناك بعض الاقتراحات هنا لتعيين T | undefined بشكل صريح كنوع إرجاع عملية فهرس الكائن ، ولكن يبدو أن هذا لا يعمل:

const obj: {[key: string]: number | undefined} = {
  "a": 1,
  "b": 2,
};

const test = obj["c"]; // const test: number

هذا هو الإصدار VSCode 1.31.1

yawaramin تأكد من تمكين strictNullChecks في tsconfig.json (والذي يتم تمكينه أيضًا بواسطة علامة strict )

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

const words = ... // some string[] that could be empty
const x = words[0] as string | undefined
console.log(x.length) // TS error

تعمل المجموعات مع المصفوفات الصغيرة ذات الأطوال المعروفة. ربما يمكننا الحصول على شيء مثل string[5] كاختصار لـ [string, string, string, string, string] ؟

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

ركض في هذا مع تدمير مصفوفة لمعامل دالة:

function foo([first]: string[]) { /* ... */ }

هنا أتوقع أن يكون a من النوع string | undefined لكنه فقط string ، ما لم أفعل

function foo([first]: (string | undefined)[]) { /* ... */ }

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

هذه هي الطريقة التي أعمل بها لحل هذه المشكلة: https://github.com/danielnixon/total-functions/

حل جيد ، آمل حقًا أن يتم توضيح ذلك بواسطة فريق TypeScript.

عندما يقوم المبرمج بعمل افتراضات في كود مكتوب ، ولا يستطيع المترجم أن يستنتج أنه تم حفظه ، يجب أن يؤدي ذلك إلى حدوث خطأ في المترجم ما لم يتم إسكات IMHO.

سيكون هذا السلوك مفيدًا جدًا أيضًا مع عامل التشغيل التسلسلي الاختياري الجديد.

واجهت مشكلة في استخدام | undefined مع الخريطة اليوم أثناء استخدام Object.entries() .

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

لذلك قمت بتغييره إلى {[key: string]: string[] | undefined} كما هو موصى به ، ولكن هذا يؤدي الآن إلى مشكلات في استخدامي لـ Object.entries() . تفترض TypeScript الآن (بشكل معقول ، بناءً على مواصفات النوع) أن الفهرس قد يحتوي على مفاتيح لها قيمة غير محددة محددة ، وبالتالي تفترض أن نتيجة استدعاء Object.entries() عليها قد تحتوي على قيم غير محددة.

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

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

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

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

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

src={savedAdsItem.advertImageList.advertImage[0].mainImageUrl || undefined}
return advert.advertImageList.advertImage.length ? advert.advertImageList.advertImage[0].mainImageUrl : ''
birthYear: profileData.birthYear !== null ? profileData.birthYear : allowedYears[0].value,
upsellingsList.upsellingProducts[0].upsellingProducts[0].selected = true
const latitude = parseFloat(coordinates.split(',')[0])
const advert = Object.values(actionToConfirm.selectedItems)[0]
await dispatch(deactivateMyAd(advert))

في هذه الحالة ، سيكون الأمر مزعجًا لأن ArticleIDs extends articleNames[] يتضمن undefined في القيم الناتجة ، بينما يجب أن يسمح فقط بمجموعات فرعية محددة تمامًا. يمكن إصلاحه بسهولة باستخدام ReadonlyArray<articleNames> بدلاً من articleNames[] .

export enum articleNames {
    WEB_AGB = 'web_agb',
    TERMS_OF_USE = 'web_terms-of-use',
}
export const getMultipleArticles = async <ArticleIDs extends articleNames[], ArticleMap = { [key in ArticleIDs[number]]: CmsArticle }>(ids: ArticleIDs): Promise<ArticleMap> => {...}

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

ذهبت إلى التفكير أكثر قليلاً في هذا التعليق # 11238 (تعليق)

فكر في نوعين من المفاتيح في العالم: تلك التي تعرف _do_ لها خاصية مقابلة في كائن ما (آمن) ، تلك التي _لا_ تعرف أن لها خاصية مقابلة في كائن ما (خطير).

تحصل على النوع الأول من المفتاح ، وهو مفتاح "آمن" ، عن طريق كتابة رمز صحيح مثل

for (let i = 0; i < arr.length; i++) {
  // arr[i] is T, not T | undefined

أو

for (const k of Object.keys(obj)) {
  // obj[k] is T, not T | undefined

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

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

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i].name);

و TypeScript يشكو منك أن arr[i] قد يكون undefined على الرغم من أنهم يبدون أنني فقط @ #٪ # جربت ذلك. الآن أصبحت معتادًا على كتابة كود مثل هذا ، وشعرت بالغباء:

for (let i = 0; i < arr.length; i++) {
  // TypeScript makes me use ! with my arrays, sad.
  console.log(arr[i]!.name);

أو ربما تكتب رمزًا مثل هذا:

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k].name);
  }
}

ويقول TypeScript "مرحبًا ، قد يكون تعبير الفهرس هذا | undefined ، لذا عليك إصلاحه وفقًا للواجب لأنك رأيت هذا الخطأ 800 مرة بالفعل:

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k]!.name); // Shut up TypeScript I know what I'm doing
  }
}

لكنك لم تصلح الخلل . كنت تقصد كتابة Object.keys(yourObj) ، أو ربما myObj[k] . هذا هو أسوأ نوع من أخطاء المترجم ، لأنه لا يساعدك في الواقع في أي سيناريو - إنه تطبيق نفس الطقوس فقط على كل نوع من التعبيرات ، بغض النظر عما إذا كان في الواقع أكثر خطورة من أي تعبير آخر من نفس النموذج أم لا.

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

أنا أتفق مع تشبيه مربع حوار حذف الملف ، ولكن أعتقد أن هذا القياس يمكن أيضًا أن يمتد إلى إجبار المستخدم على التحقق من شيء ربما يكون غير محدد أو لاغٍ ، لذا فإن هذا التفسير لا معنى له حقًا ، لأنه إذا كان هذا التفسير صحيحًا ، strictNullChecks الخيار document.getElementById .

ولكن ليس هذا هو الحال ، يريد الكثير من مستخدمي TypeScript أن يقوم المترجم برفع علامة حول مثل هذا الرمز بحيث يمكن التعامل مع حالات الحافة هذه بشكل مناسب بدلاً من إلقاء الخطأ Cannot access property X of undefined الذي يصعب جدًا تتبعه.

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

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

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i].name);

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

يمكن تحسين تحليل نوع التحكم المستند إلى التدفق في TypeScript للتعرف على هذه الحالة لتكون آمنة

حقا لا يمكن.

declare function someFunc(arr: number[], i: number): void;
let arr = [1, 2, 3, 4];
for (let i = 0; i < arr.length; i++) {
  someFunc(arr, arr[i]);
}

هل تمرر هذه الوظيفة undefined إلى someFunc في المرة الثانية تمر عبر الحلقة ، أم لا؟ هناك الكثير من الأشياء التي يمكنني كتابتها بـ someFunc والتي ستؤدي إلى ظهور undefined لاحقًا.

ماذا عن هذا؟

declare function someFunc(arr: number[], i: number): void;
let arr = [1, 2, 3, 4];
let alias = arr;
for (let i = 0; i < arr.length; i++) {
  someFunc(alias, arr[i]);
}

fabb اسمحوا لي أن

""
عقدة $

كونست arr = []
غير معرف
arr [7] = 7
7
arr
[<7 عناصر فارغة> ، 7]
لـ (let i = 0؛ i <arr.length؛ i ++) {
... console.log (arr [i])
...}
غير معرف
غير معرف
غير معرف
غير معرف
غير معرف
غير معرف
غير معرف
7
غير محدد ""

RyanCavanaugh بما أنك هنا ، item :: T لـ arr :: T[] في for (const item of arr) ... ، وبخلاف ذلك استنتاج arr[i] :: T | undefined عند استخدام بعض --strict-index ؟ ماذا عن الحالة التي أهتم بها ، obj[key] :: V | undefined لكن Object.values(obj) :: V[] مقابل obj :: { [key: string]: V } ؟

yawaramin إذا كنت تستخدم المصفوفات المتفرقة ، فإن Typescript لا تفعل الشيء الصحيح بالفعل. يمكن لعلم --strict-index إصلاح ذلك. هذا يجمع:

const arr = []
arr[7] = 7

for (let i = 0; i < arr.length; i++) {
    console.log(Math.sqrt(arr[i]));
}

RyanCavanaugh هناك مثال شائع جدًا يمكنني أن

const getBlock = (unitNumber: string): string => unitNumber.split('-')[0]

يجب ألا يمر الكود أعلاه بفحص المحول البرمجي بأمان تحت strictNullChecks ، لأن بعض استخدامات getBlock سترجع غير محدد ، على سبيل المثال getBlock('hello') ، في مثل هذه الحالات أريد بشدة ترجمة علامة رفع حتى أتمكن من التعامل مع الحالات غير المحددة بأمان دون تفجير طلبي.

وهذا ينطبق أيضًا على الكثير من التعابير الشائعة مثل الوصول إلى العنصر الأخير من المصفوفة باستخدام arr.slice(-1)[0] ، والوصول إلى العنصر الأول arr[0] إلخ.

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

هل تقوم هذه الوظيفة بتمرير undefined إلى someFunc في المرة الثانية التي تمر عبر الحلقة ، أم لا؟ هناك الكثير من الأشياء التي يمكنني كتابتها في SomeFunc والتي من شأنها أن تؤدي إلى ظهور غير محدد لاحقًا.

RyanCavanaugh نعم ، جافا سكريبت لا تصلح لثباتها. في هذه الحالة ، يجب أن يكون إما ! ضروريًا ، أو مصفوفة ReadonlyArray : someFunc(arr: ReadonlyArray<number>, i: number) .

yawaramin بالنسبة إلى المصفوفات المتفرقة ، ربما يحتاج نوع العنصر إلى تضمين undefined ما لم يستنتج TypeScript أنه يُستخدم مثل tuple. في الكود الذي ربط danielnixon بـ (https://github.com/microsoft/TypeScript/issues/13778#issuecomment-536248028) ، يتم أيضًا التعامل مع tuples بشكل خاص ولا تتضمن undefined في نوع العنصر الذي تم إرجاعه لأن المترجم يضمن الوصول إلى المؤشرات المحددة فقط.

هذا قرار واع. سيكون مزعجًا جدًا أن يكون هذا الرمز خطأ:

var a = [];
for (var i =0; i< a.length; i++) {
    a[i]+=1; // a[i] is possibly undefined
}

مهلا ، أنا أدرك أن بناء الجملة ؛ أكتب حلقات مثل هذه ربما مرة واحدة في السنة!

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

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

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

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

في الجمعة 25 أكتوبر 2019 الساعة 11:59 صباحًا كتب brunnerh [email protected] :

هذا قرار واع. سيكون مزعجًا جدًا لهذا الرمز
كن خطأ:

var a = [] ؛ لـ (var i = 0 ؛ i <a.length ؛ i ++) {
أ [i] + = 1 ؛ // a [i] ربما غير محدد
}

مهلا ، أنا أدرك أن بناء الجملة ؛ أكتب حلقات مثل هذه ربما مرة واحدة في السنة!

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

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

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

-
أنت تتلقى هذا لأنك علقت.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/microsoft/TypeScript/issues/13778؟email_source=notifications&email_token=ACAJU3DQ7U6Y3MUUM26J4JDQQM62XA5CNFSM4C6KEKAKYY3PNVWWK3TUL52HS4DFVREXG43VM47
أو إلغاء الاشتراك
https://github.com/notifications/unsubscribe-auth/ACAJU3EWVM3CUFG25UF5PGDQQM62XANCNFSM4C6KEKAA
.

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

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

أنا مندهش لم يتحدث أحد عن as const حتى الآن.

const test = [1, 2, 3] as const;

(test[100]).toFixed(5);
// Tuple type 'readonly [1, 2, 3]' of length '3' has no element at index '100'.

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

const xs: Array<number | undefined> = [1,2,3];

// for objects but kind of related as well
Record<string, User | undefined>

interface Something {
  [key: string]: User | undefined
}

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

martpie as const رائع إذا كان بإمكانك استخدامه ، أي.

هناك سببان على الأقل لعدم استخدامي Array<T | undefined> :

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

أريد أيضًا أن أتحدث عن أن هذا يتسبب في الكثير من الإيجابيات الخاطئة في eslint / مطبوعة الآن:

const a: string[] = [];
const foo = a[1000];
if (foo) { // eslint says this is an unnecessary conditional
  console.log(foo.length);
}

يستنتج Eslint (بشكل صحيح) من النوع أن هذا التحقق غير ضروري ، لأن foo لا يمكن أبدًا أن يكون فارغًا. لذا لا يتعين علي الآن فقط أن أتذكر بوعي القيام بفحص فارغ للأشياء التي أعود إليها من المصفوفة ، بل يتعين علي أيضًا إضافة سطر تعطيل eslint! والوصول إلى أشياء خارج حلقة for مثل هذه هي الطريقة الوحيدة تقريبًا للوصول إلى المصفوفات ، نظرًا (مثل معظم مطوري TS هذه الأيام) ، نستخدم وظائف forEach / map / filter / etc عند تكرار المصفوفات.

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

هذا هو مصدر أخطاء إنتاج وقت التشغيل _only_ التي رأيتها في الذاكرة الحديثة والتي كانت بمثابة فشل في نظام الكتابة.

بالنظر إلى بيئة العمل الخاصة بمشغل التسلسل الاختياري المتاح الآن ، فربما حان الوقت لإعادة النظر في القرار بعدم إتاحة هذا السلوك عبر العلم؟

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

مثال:

// `things` is `Thing[]`, but is empty, i.e., `[]`
const { things } = data; 

// We are accessing `things[-1]`, which is obviously `undefined`, 
// but TypeScript thinks `latestThing` is a `Thing`
const latestThing = things[things.length - 1];

// TypeError: Cannot read property 'foo' of undefined
return latestThing.foo; 

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

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

نعم ، يتفق الجميع إلى حد كبير على أنه قرار تصميم سيء للغاية ، قرار معاصر للوقت البعيد عندما كان TS غير آمن تمامًا.

كحل بديل ، أستخدم قرصنة صغيرة قذرة. لقد أضفت للتو البرنامج النصي البسيط لما بعد التثبيت في package.json :

{
...
  "scripts": {
    "postinstall": "sed -i 's/\\[n: number\\]: T;/[n: number]: T | undefined;/g' node_modules/typescript/lib/lib.es5.d.ts",
    ...
  },
...
}

بالطبع ، لن يعمل على النوافذ ، لكن هذا أفضل من لا شيء.

يبدو أنه قد تمت إعادة توجيه جميع المشكلات الأخرى المجاورة لهذه المشكلة ، لذا سأفترض أن هذا هو أفضل مكان لطرحه: هل صيغة الاختزال لتوقيعات الفهرس الاختيارية خارج الطاولة؟ كما يشير martpie ، عليك كتابة interface Foo { [k: string]: Bar | undefined; } وهو أقل راحة من interface Foo { [k: string]?: Bar; } .

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

type Foo = { [_ in string]?: Bar } يعمل أيضًا. ليست جميلة ، لكنها مقتضبة إلى حد ما. يمكنك أيضًا إنشاء نوع Dict بك إذا أردت. لن يجادل ضد تمديد ?: ، رغم ذلك

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

ts type Foo1 = { [_ in string]?: Bar } // Yup type Foo2 = { [_: string]?: Bar } // Nope interface Foo3 { k?: Bar } // Yup interface Foo4 { [_: string]?: Bar } // Nope

إن استخدام T | undefined لتوقيع النوع ليس مفيدًا حقًا. نحتاج إلى طريقة لجعلها بحيث أن عامل الفهرس [n] له نوع T|undefined ، لكن على سبيل المثال ، استخدام map على مصفوفة يجب ألا يمنحنا T|undefined القيم ، لأنه في هذه الحالة يجب أن نعرف أنها موجودة.

radix بالنسبة للوظائف الفعلية في Array ، لا أعتقد أن هذه ستكون مشكلة لأن لديهم جميعًا تعريفات النوع الخاصة بهم والتي map : https://github.com/microsoft /TypeScript/blob/master/lib/lib.es5.d.ts#L1331

استخدام الكود الشائع الوحيد الذي يشكل فيه بناء | undefined انحدارًا فعليًا للتجربة هو في حلقات for ... of . لسوء الحظ (من منظور هذه المسألة) ، هذه بنيات شائعة إلى حد ما.

riggs أعتقد أنك تتحدث عن جعل interface Array<T> { } يملك [index: number]: T | undefined ، لكن من المحتمل أن radix يتحدث عما يبدو أنه التوصية الحالية ، وهي استخدام Array<T | undefined> في رمزك الخاص.

هذا الأخير سيء لعدة أسباب ، ليس أقلها أنك لا تتحكم في أنواع الحزم الأخرى ، لكن الأول لديه أيضًا بعض المشكلات ، وهي أنه يمكنك تخصيص undefined للمصفوفة ، وأنه يمنحك undefined حتى في الحالات المعروفة بأنها آمنة. 🤷‍♂️

آه ، نعم ، سوء فهمي. في الواقع ، كنت أشير فقط إلى استخدام التعريف [index: number]: T | undefined . أوافق تمامًا على أن تحديد النوع على أنه Array<T | undefined> هو حل رهيب يسبب مشاكل أكثر مما يحله.

هل هناك طريقة رائعة لتجاوز lib.es5.d.ts أم أن البرنامج النصي لما بعد التثبيت هو أفضل طريقة؟

@ nicu-chiciuc https://www.npmjs.com/package/patch-package يناسب تمامًا صندوق أدوات الزنديق إلى جانب https://www.npmjs.com/package/yalc ✌️

هل هناك طريقة رائعة لتجاوز lib.es5.d.ts أم أن البرنامج النصي لما بعد التثبيت هو أفضل طريقة للذهاب؟

في مشروعنا ، لدينا ملف global.d.ts نستخدمه لـ 1) إضافة تعريفات أنواع لواجهات برمجة التطبيقات المضمنة التي لم توجد بعد في تعريفات النوع الافتراضي للطباعة (مثل واجهات برمجة التطبيقات المتعلقة بـ WebRTC والتي تتغير باستمرار وتتعارض عبر المتصفحات ) و 2) تجاوز بعض تعريفات النوع الافتراضي للطباعة (على سبيل المثال ، تجاوز نوع Object.entries بحيث تُرجع مصفوفات تحتوي على unknown بدلاً من any ).

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

يعمل ما سبق عندما يعطي دمج الإعلان النتيجة التي تريدها ، ولكن لتبسيطها تتقاطع مع الواجهات ، ولكن هنا الخيار الأقل تقييدًا هو ما تريده.

يمكنك بدلاً من ذلك محاولة نسخ ولصق جميع ملفات lib.*.d.ts التي تستخدمها في مشروعك ، المضمنة في tsconfig.json 's files أو include ، ثم تعديلها لما تريد. نظرًا لأنها تتضمن /// <reference no-default-lib="true"/> ، فلن تحتاج إلى أي سحر آخر ، لكنني لست متأكدًا مما إذا كان عليك إزالة /// <reference lib="..."/> لديهم إلى تبعياتهم. هذا أمر سيء لجميع أسباب الصيانة الواضحة ، بالطبع.

butchler نحن أيضًا نستخدم هذا الأسلوب ولكن يبدو أن تجاوز عامل الفهرسة لم يكن ممكنًا ، على الرغم من أننا تجاوزنا بعض وظائف المصفوفة الأخرى ( filter مع حراس النوع) بنجاح.

لدي حالة غريبة مع تجاوز عامل الفهرسة:

function test() {
  const arr: string[] = [];

  const [first] = arr;
  const zero = arr[0];

  const str1: string = first;
  const str2: string = zero;
}

Screenshot 2020-02-05 at 10 39 20 AM

التخصيص الثاني يخطئ (كما ينبغي) ، لكن التعيين الأول لا يخطئ.
والأغرب من ذلك أن التحليق فوق first حيث يتم تدميره يظهر أنه يحتوي على نوع string | undefined ومع ذلك فإن التمرير فوقه أثناء تعيينه يظهر أنه يحتوي على نوع string .
Screenshot 2020-02-05 at 10 40 25 AM
Screenshot 2020-02-05 at 10 40 32 AM

هل يوجد تعريف نوع مختلف لتدمير المصفوفة؟

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

تحديد نوع مثل { [index: string]: string | undefined } ليس حلاً ، لأنه يعبث بالكتابة لمكررات مثل Object.values(x).forEach(...) والتي لن تتضمن قيم undefined .

أرغب في رؤية TypeScript يُنشئ أخطاء عندما لا أتحقق من وجود أخطاء غير محددة بعد تنفيذ someObject[someKey] ، لكن ليس عند تنفيذ Object.values(someObject).forEach(...) .

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

بينما يمكنني متابعة حجج كونترا من RyanCavanaugh مع:

for (let i = 0; i < arr.length; i++) {
  // TypeScript makes me use ! with my arrays, sad.
  console.log(arr[i]!.name);

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

1. حدد إحصائيًا التركيبات التي يحتمل أن تكون أخطاء.

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

من الناحية الفنية ، تراكيب مثل:

const x = ['a', 'b', 'c']
console.log(x[3]) // type: string, reality: undefined

يكسر الهدف الأول من TypeScript. لكن هذا لا يحدث إذا عرفت TypeScript الأنواع الأكثر دقة:

const x = ['a', 'b', 'c'] as const
console.log(x[3]) // compile error: Tuple type 'readonly ["a", "b", "c"]' of length '3' has no element at index '3'.ts(2493)

من وجهة نظر عملية ، إنها مقايضة yet . تظهر هذه المشكلة والعديد من القضايا المغلقة أن هناك طلبًا كبيرًا من المجتمع على هذا التغيير: ATM 238 مؤيدًا مقابل 2 من المصوتين المؤيدين. بالطبع من المؤسف أن الحلقة for أعلاه لا تستدعي النوع "الصحيح" ، ولكن حسنًا ، أنا متأكد تمامًا من أن معظم المصوتين يمكنهم العيش مع ! و ? علامة على الانتباه وإجبارها في حالات آمنة. ولكن على الجانب الآخر ، احصل على الأنواع الصحيحة في الوصول "الخطير".

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

هذا ليس حلاً مقترحًا ، ولكنه مجرد تجربة: لقد حاولت تعديل lib.es5.d.ts داخل node_modules الخاص بي في مشروع حالي يستخدم TypeScript فقط لمعرفة ما سيكون عليه الحال إذا _did_ لدينا مترجم خيار لهذا. لقد قمت بتعديل Array و ReadonlyArray :

interface ReadonlyArray<T> {
  ...
  [n: number]: T | undefined; // was just T
}

interface Array<T> {
  ...
  [n: number]: T | undefined; // was just T
}

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

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

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

  3. حدث خطأ في النوع داخل حلقة for (let i = 0; i < array.length; i++) كانت تتكرر على Array<T> تعسفي. لم أتمكن فقط من إضافة نوع حارس للتحقق من undefined ، لأن T نفسه قد يتضمن undefined . كانت الحلول الوحيدة التي استطعت التفكير فيها هي أ) استخدام تأكيد النوع أو @ts-ignore لإسكات خطأ النوع أو B) استخدام حلقة for-of بدلاً من ذلك. أنا شخصياً لا أجد أن هذا سيء للغاية (ربما تكون هناك حالات قليلة لا يكون فيها استخدام حلقة for-of للتكرار عبر مصفوفة أفضل على أي حال) ، ولكن قد يكون الأمر مثيرًا للجدل.

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

  5. كانت بعض التعليمات البرمجية تستخدم A[number] للحصول على نوع العناصر من نوع مصفوفة عامة. ومع ذلك ، فقد أعاد هذا الآن T | undefined بدلاً من T ، مما تسبب في حدوث أخطاء في الكتابة في مكان آخر. لقد صنعت مساعدًا للتغلب على هذا:

    type ArrayValueType<A extends { [n: number]: unknown }> = (
      A extends Array<infer T> ? T :
      A extends ReadonlyArray<infer T> ? T :
      A[number] // Fall back to old way of getting array element type
    );
    

    ولكن حتى مع هذا المساعد لتسهيل الانتقال ، لا يزال هذا تغييرًا كبيرًا. ربما يكون لدى TypeScript نوع من الحالات الخاصة مقابل A[number] لتجنب هذه المشكلة ، ولكن سيكون من الجيد تجنب الحالات الخاصة الغريبة مثل هذه إذا أمكن.

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

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

butchler لقد مررت أيضًا ببعض هذه المشكلات ، لقد بدأت في عرض something[i] كـ something(i) ، أي ، لا يمكنني استخدام شيء مثل if (Meteor.user() && Meteor.user()._id) {...} ، لذلك لا يمكنني ' نتوقع أن يكون لديك هذا الأسلوب لفهرسة الصفيف؛ يجب أن أحصل أولاً على القيمة التي أريد فحصها خارج المصفوفة. ما أحاول قوله هو أن الاعتماد على TS لفهم أن التحقق من خاصية length وتقديم بعض التأكيد بناءً على ذلك قد يضع الكثير من الضغط على نظام الكتابة. الشيء الوحيد الذي جعلني أوقف الانتقال (إلى جانب حقيقة أن بعض المكتبات الأخرى بها أخطاء أيضًا ، كما قلت) هو أن إتلاف المصفوفات لا يعمل بشكل صحيح بعد وأن القيمة المدمرة لن تكون T | undefined لكن T (انظر تعليقي الآخر).
بخلاف ذلك ، أعتقد أن تجاوز lib.es5.d.ts طريقة جيدة. حتى لو تغير شيء ما في المستقبل ، فإن الرجوع عن التغيير لن يضيف أخطاء إضافية ، ولكنه سيضمن تغطية بعض حالات الحافة بالفعل.
لقد بدأنا بالفعل في استخدام patch-package لتغيير بعض تعريفات الأنواع في react ويبدو أن هذا الأسلوب يعمل بشكل جيد.

لقد مررت أيضًا ببعض هذه المشكلات ، وبدأت في عرض شيء ما [i] كشيء (i) ، أي ، لا يمكنني استخدام شيء مثل if (Meteor.user () && Meteor.user () ._ id) { ...} ، لذلك لا أتوقع أن يكون لدي هذا الأسلوب لفهرسة الصفيف ؛ يجب أن أحصل أولاً على القيمة التي أريد فحصها خارج المصفوفة.

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

if (array[i]) {
  array[i].doSomething(); // causes a type error with our modified Array types
}

يجب تغييره إلى شيء مثل:

const arrayValue = array[i]
if (arrayValue) {
  arrayValue.doSomething();
}

ما أحاول قوله هو أن الاعتماد على TS لفهم أن التحقق من خاصية length وتقديم بعض التأكيد بناءً على ذلك قد يضع الكثير من الضغط على نظام الكتابة.

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

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

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

تجاهل المصفوفات والتركيز على Record<string, T> للحظة ، فإن قائمة أمنياتي الشخصية هي أن الكتابة تسمح فقط بـ T لكن القراءة قد تكون T|undefined .

declare const obj : Record<string, T>;
declare const t : T;
obj["k"] = t; //ok
obj["k"] = undefined; //error, undefined not assignable to T

//T|undefined inferred,
//since we don't know if "k2" is an "ownProperty" of obj
const v = obj["k2"];

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

//Shouldn't just be string[]
//Should also be something like (keyof valueof obj)[],
//A dependent type
const keys = Object.keys(obj);

بالعودة إلى المصفوفات ، تكمن المشكلة في أن توقيع فهرس المصفوفات ليس له نفس القصد مثل Record<number, T> .

لذلك ، قد تحتاج إلى حراس من نوع تابع مختلف تمامًا مثل ،

for (let i=0; i<arr.length; ++i) {
  //i is not just number
  //i should also be something like keyof valueof arr 
}

لذا ، فإن توقيع فهرس المصفوفات ليس في الحقيقة Record<number, T> . إنه يشبه Record<(int & (0 <= i < this["length"]), T> (نطاق ونوع عدد صحيح مدعوم بالأرقام)


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

TL ؛ DR يتحدث المنشور الأصلي والعنوان عن أشياء مختلفة ، ويبدو أن التنفيذ يعتمد بشكل كبير (ha) على الكتابة المعتمدة.

نظرًا لوجود وظائف مثل forEach ، map ، filter ، وما إلى ذلك (وهي أفضل بكثير من IMHO) ، لا أتوقع أن يقوم فريق TS بتعقيد محرك الاستدلال بشكل كبير حلقات الدعم عبر مصفوفة مع حلقة for منتظمة. لدي شعور بأن هناك الكثير من التعقيد غير المتوقع من محاولة تحقيق ذلك. على سبيل المثال ، بما أن i ليس ثابتًا ، فماذا يحدث إذا قام شخص ما بتغيير قيمة i داخل الحلقة؟ بالتأكيد ، إنها حالة متطرفة ، لكنها شيء يحتاجون إلى التعامل معه بطريقة بديهية (نأمل).

ومع ذلك ، يجب أن يكون تثبيت توقيعات الفهرس بسيطًا نسبيًا (ish) ، كما أظهرت التعليقات أعلاه.

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

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

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

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

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

const noNext = !items[currentIndex + 1];

ينتج عن هذا تعريف noNext على أنه false . وهذا خطأ. يمكن أن يكون صحيحا.
لا أريد أيضًا تعريف items كـ Array<Item | undefined> لأن هذا يعطي توقعًا خاطئًا.
إذا كان هناك فهرس فلا يجب أن يكون undefined أبدًا. ولكن إذا كنت تستخدم فهرسًا خاطئًا ، فسيكون _is_ undefined .
بالتأكيد ، ربما يمكن حل ما سبق باستخدام شيك .length بدلاً من ذلك أو تحديد noNext صراحة كـ boolean .
لكن في النهاية ، هذا شيء يزعجني منذ أن بدأت في استخدام TypeScript ولم أفهم أبدًا سبب عدم تضمين | undefined افتراضيًا.

نظرًا لأنني أتوقع أن يستخدم معظمهم print-eslint ، فهل هناك أي قاعدة يمكن أن تفرض فحص القيم بعد الفهرسة قبل استخدامها؟ قد يكون هذا مفيدًا قبل تنفيذ الدعم من TS.

في حالة قد تكون ذات فائدة لشخص ما. ذكرت في تعليق سابق أن أسلوب تصحيح تعريف الفهرسة Array في lib.es5.d.ts له سلوك غريب في إتلاف المصفوفات حيث بدا أنه لم يتأثر بالتغيير. يبدو أنه يعتمد على الخيار target في tsconfig.json ويعمل بشكل صحيح عندما يكون es5 (https://github.com/microsoft/TypeScript/issues/37045 ).

بالنسبة لمشروعنا ، هذه ليست مشكلة لأننا نستخدم الخيار noEmit ويتم التعامل مع الترجمة بواسطة meteor . ولكن بالنسبة للمشاريع التي قد تكون فيها مشكلة ، فإن الحل الذي قد ينجح هو safe-array-destructuring (https://github.com/typescript-eslint/typescript-eslint/pull/1645).
لا تزال مسودة وقد لا تكون ذات قيمة عندما يتم إصلاح المشكلة بأكملها في مترجم TypeScript ولكن إذا كنت تعتقد أنها قد تكون مفيدة ، فلا تتردد في معالجة أي مخاوف / تحسينات في typescript-eslint rule PR

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

const example = (args: string[]) => {
  const [userID, nickname] = args
}

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

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

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

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

caseyhoward كما لوحظ سابقًا في هذه المشكلة ، يتسبب هذا في سلوك غير مرغوب فيه مع وظائف Array.prototype المختلفة:

x.forEach( (i: string) => { ... } )  // Error because i has type string | undefined

هذا لا يحتاج إلى إصلاح في دوال array.prototype. هذه مشكلة كبيرة لتدمير المصفوفة!

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

حالة استخدام أخرى لهذه المشكلة - واجهت هذه المشكلة عند استخدام print-eslint وتمكين no-unnecessary-condition . في الماضي ، عند الوصول إلى مصفوفة عن طريق الفهرس للقيام ببعض العمليات على العنصر في هذا الفهرس ، نستخدم تسلسلًا اختياريًا لضمان تعريف هذا الفهرس (في حالة خروج الفهرس عن الحدود) ، كما في array[i]?.doSomething() . مع المشكلة الموضحة في هذه المشكلة ، مع ذلك ، يشير no-unnecessary-condition أن التسلسل الاختياري غير ضروري (لأن النوع غير قابل للإلغاء وفقًا للطباعة) ويزيل التصحيح التلقائي التسلسل الاختياري ، مما يؤدي إلى حدوث أخطاء في وقت التشغيل عند وصول المصفوفة في الفهرس i غير معرّف في الواقع.

بدون هذه الميزة ، أصبح تطبيقي عربات التي تجرها الدواب للغاية لأنني أتعامل مع مصفوفة متعددة الأبعاد ، يجب أن أذكر نفسي دائمًا بالوصول إلى العناصر باستخدام xs[i]?.[j] بدلاً من xs[i][j] ، كما يجب أن ألقي صراحةً العنصر الذي تم الوصول إليه مثل const element = xs[i]?.[j] as Element | undefined لضمان أمان النوع.

كانت مواجهة هذا أول wtf كبير لي في تنقيط. لقد وجدت أن اللغة بخلاف ذلك يمكن الاعتماد عليها بشكل رائع وهذا خذلني ، ومن المرهق جدًا إضافة "كـ T | غير محدد" إلى عمليات الوصول إلى الصفيف. أتمنى أن أرى أحد المقترحات منفذة.

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

لست متأكدًا مما تقصده ، هل يمكنك ربط المشكلة أو إظهار مثال؟

يوم الأربعاء 29 أبريل 2020 الساعة 2:41 صباحًا Kirill Groshkov [email protected]
كتب:

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

-
أنت تتلقى هذا لأنك علقت.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/microsoft/TypeScript/issues/13778#issuecomment-621096030 ،
أو إلغاء الاشتراك
https://github.com/notifications/unsubscribe-auth/AAAGFJJT37N54I7EH2QLBYDRO7Y4NANCNFSM4C6KEKAA
.

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

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

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

type AlwaysDefined = {[key: string]!: string};
const t: AlwaysDefined = {};

t['foo'] // Has type string

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

type MaybeUndefined = {[key: string]: string};
const t: MaybeUndefined = {};

t['foo'] // Has type string | undefined

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

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

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

لم أتمكن من الحصول على index.d.ts للعمل مع مشروعي ولم أرغب حقًا في التعامل معه.

لقد صنعت هذا الاختراق الصغير الذي يفرض نوعًا من الحراس:

 const firstNode = +!undefined && nodes[0];

على الرغم من أن النوع ينتهي على النحو التالي: 0 | T ، إلا أنه لا يزال يقوم بالمهمة.

ألن يعمل const firstNode = nodes?.[0] ؟

ricklove لأي سبب كنت تفضل +!undefined على 1 ؟

ألن يعمل const firstNode = nodes?.[0] ؟

لا ، لأن الكتابة المطبوعة (بشكل غير صحيح ، في رأيي) لن تعاملها على أنها اختيارية (انظر # 35139).

وفقًا لوثائق Flow ، فإن السلوك مع توقيعات الفهرس هو نفسه حاليًا مع TypeScript:

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

var obj: { [number]: string } = {};
obj[42].length; // No type error, but will throw at runtime

لذلك في هذا الصدد ، يتطلب كل من TS و Flow من المستخدم التفكير في مفاتيح غير محددة بدلاً من مساعدة المترجم:

ستكون ميزة TS إذا منع المترجم المستخدمين من صنع هذه الأنواع من الأخطاء. من خلال قراءة جميع التعليقات في هذا الموضوع ، يبدو أن الغالبية العظمى هنا ستحب هذه الميزة. هل فريق TS لا يزال 100٪ ضده؟

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

[نقل المناقشة هنا من # 38470]
قبول جميع الحجج التي تفسر لماذا لن يكون هذا عمليًا للمصفوفات ..
أعتقد أنه يجب بالتأكيد معالجة هذا الأمر بالنسبة إلى Tuples:

// tuple 
var str = '';
var num = 100
var aa = [str, num] as const

// Awesome
var shouldBeString = aa[0] // string
var shouldBeNumber = aa[1] // number
var shouldError = aa[10000]; // type error

// Not so awesome 
var foo = aa[num] // string | number

لماذا لا تجعل foo string | number | undefined ؟

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

يمكن أن تساعد أيضًا في هذه

var notString1 = aa[Infinity]; // no error, not undefined
var notString2 = aa[NaN]; // no error, not undefined

الذي تسبب في الأصل في خطأ وقت التشغيل حيث أصبح number NaN ، ثم أعاد undefined من المجموعة ..
كل ذلك كان من النوع الآمن

لست متأكدًا من أن مجموعات tuple مختلفة حقًا. يجب أن تستوعب أنواع الراحة (على سبيل المثال [str, ...num[]] ) ، ومن الناحية الفنية ، فإن aa[Infinity] هو Javascript صالح تمامًا ، لذلك يمكنني أن أرى أنه من المعقد إنشاء حالة خاصة لهم.

جعلني منشورك أيضًا أفكر في الشكل الذي سيبدو عليه إذا حصلنا على بعض الدعم للتعامل مع عوائد الفهرس على أنها غير محددة. كما في المثال الخاص بك ، إذا كتب aa[num] بالفعل string | number | undefined ، فهل سأضطر إلى كتابة for (let i = 0; i < 2; i++) { foo(aa[i]!); } على الرغم من أنني أعرف أن الفهرس سيبقى في الحدود؟ بالنسبة لي ، عندما تحدد عمليات التحقق من القيمة الفارغة شيئًا ما كتبته ، أود أن أكون قادرًا على إصلاحه إما عن طريق كتابة الأشياء بشكل أفضل في المقام الأول ، أو استخدام حراس وقت التشغيل - إذا اضطررت إلى اللجوء إلى تأكيد غير فارغ ، فأنا عادةً أعتبرها فشلًا من جانبي. لا أرى طريقة للتغلب عليها في هذه الحالة ، رغم ذلك ، وهذا يزعجني.

إذا كان tuple[number] يكتب دائمًا على T | undefined ، فكيف تريد التعامل مع الحالة التي تعرف أن الفهرس مقيد بها بشكل صحيح؟

@ thw0rted لا أتذكر آخر مرة استخدمت فيها حلقة "كلاسيكية" for في Typescript ( .map و .reduce ftw). هذا هو السبب في أن خيار المترجم لهذا سيكون رائعًا imo (والذي يمكن إيقافه افتراضيًا). لا تواجه العديد من المشاريع أي شيء مثل الحالة التي قدمتها ، وتستخدم فقط توقيعات الفهرس لتدمير المصفوفة ، وما إلى ذلك.

(تعليقي الأصلي: https://github.com/microsoft/TypeScript/issues/13778#issuecomment-517759210)

أوه بكل صدق ، نادرًا ما أقوم بالفهرسة بحلقة for-loop. أستفيد بشكل مكثف من Object.entries أو Array#map ، ولكن بين الحين والآخر أحتاج إلى تمرير المفاتيح للفهرسة إلى كائن أو (ربما أقل في كثير من الأحيان) فهارس إلى مصفوفة أو tuple. كانت الحلقة for مثالاً مفتعلًا بالتأكيد ، لكنها تثير النقطة التي تشير إلى أن أيًا من الخيارين ( undefined أو لا) له سلبيات.

لكنه يثير نقطة أن أي من الخيارين ( undefined أو لا) له جوانب سلبية.

نعم ، وسيكون من الجيد أن يختار مستخدم TypeScript الجانب السلبي الذي يفضله: wink:

أوه بكل صدق ، نادرًا ما أقوم بالفهرسة بحلقة for-loop. أستفيد بشكل مكثف من Object.entries أو Array#map ، ولكن بين الحين والآخر أحتاج إلى تمرير المفاتيح للفهرسة إلى كائن أو (ربما أقل في كثير من الأحيان) فهارس إلى مصفوفة أو tuple.

في الحالات التي تحتاج فيها إلى ذلك ، يمكنك استخدام Array#entries :

for (const [index, foo] of array.entries()) {
    bar(index, foo)
}

أنا شخصياً لا أستخدم Array#forEach بكل هذا القدر ، ولا أستخدم أبدًا Array#map للتكرار (أستخدمه طوال الوقت لرسم الخرائط). أنا أفضل أن أبقي الكود الخاص بي ثابتًا ، وأنا أقدر القدرة على كسر حلقة for ... of.

لست متأكدًا من أن مجموعات tuple مختلفة حقًا. يجب أن تستوعب أنواع الراحة (على سبيل المثال [str, ...num[]] ) ، ومن الناحية الفنية ، فإن aa[Infinity] هو Javascript صالح تمامًا ، لذلك يمكنني أن أرى أنه من المعقد إنشاء حالة خاصة لهم.

جعلني منشورك أيضًا أفكر في الشكل الذي سيبدو عليه إذا حصلنا على بعض الدعم للتعامل مع عوائد الفهرس على أنها غير محددة. كما في المثال الخاص بك ، إذا كتب aa[num] بالفعل string | number | undefined ، فهل سأضطر إلى كتابة for (let i = 0; i < 2; i++) { foo(aa[i]!); } على الرغم من أنني أعرف أن الفهرس سيبقى في الحدود؟ بالنسبة لي ، عندما تحدد عمليات التحقق من القيمة الفارغة شيئًا ما كتبته ، أود أن أكون قادرًا على إصلاحه إما عن طريق كتابة الأشياء بشكل أفضل في المقام الأول ، أو استخدام حراس وقت التشغيل - إذا اضطررت إلى اللجوء إلى تأكيد غير فارغ ، فأنا عادةً أعتبرها فشلًا من جانبي. لا أرى طريقة للتغلب عليها في هذه الحالة ، رغم ذلك ، وهذا يزعجني.

إذا كان tuple[number] يكتب دائمًا على T | undefined ، فكيف تريد التعامل مع الحالة التي تعرف أن الفهرس مقيد بها بشكل صحيح؟

لست متأكدًا من وجود هذا المبلغ المكرر من for(let i..) على المجموعات ..
إذا كانت المجموعة تحتوي على نوع واحد ، فسيقوم المستخدم باستخدام مصفوفة ،

من واقع خبرتي ، تعتبر tuples رائعة للأنواع المختلطة ، وإذا كانت هذه هي الحالة العامة ، فأنت تكتب التحقق على أي حال

var str = '';
var num = 100
var aa = [str, num] as const
for (let i = 0; i < aa.length; i++) {
 aa[i] // needs some type check anyway to determine if 'string' or 'number'
}

أيضًا بافتراض حدوث هذا التغيير المقترح ما الذي يمنع الناس من القيام بذلك؟

// this sucks
for (let i = 0; i < 2; i++) { 
   foo(aa[i]!); // ! required
}

// this would work
for (let i = 0 as 0 | 1; i < 2; i++) { 
   foo(aa[i]); //  ! not required 
}

يمكنك استخدام keyof typeof aa بدلاً من 0 | 1 عند إصلاح هذه المشكلة

بالتأكيد لن يكون هناك أي نوع من الأمان عند إجراء العمليات الحسابية بـ i ،
ولكنه يسمح حقًا للأشخاص باختيار هذا النوع من الأمان بدلاً من الصفيف الافتراضي "ليس نوعًا آمنًا ولكن مناسبًا"

أعتقد أن وجود عاملي التشغيل ?? و ?. يعني أنه لم يعد مرهقًا جدًا للقيام بالوصول العشوائي إلى Array s.
ولكن أيضًا ، ربما يكون أكثر شيوعًا

  • تكرار (على سبيل المثال مع .map أو .forEach ، حيث لا توجد حاجة لـ " T | undefined " ، لا يتم تشغيل رد الاتصال أبدًا في مؤشرات خارج الحدود) أو
  • اجتياز الحلقات Array في الحلقات ، والتي يمكن تحديد بعضها بشكل ثابت على أنها آمنة ( for...of .. تقريبًا ، باستثناء _sparse arrays_ ، for...in أعتقد أنها آمنة).

وأيضًا ، يمكن اعتبار المتغير "فهرسًا آمنًا" إذا تم التحقق منه باستخدام <index> in <array> أولاً.


_Clarification_: أتحدث عن السبب الأصلي لـ Array<T> لعدم وجود نوع الفهرس [number]: T | undefined (ولكن بدلاً من ذلك [number]: T ) ، وهو أنه سيكون مرهقًا جدًا للاستخدام.
أعني أنه لم يعد الأمر كذلك ، لذلك يمكن إضافة undefined .

@ vp2177 المشكلة ليست الميزات (نعم ، ?. يعمل) ، المشكلة هي أن المترجم لا يحذرنا من ذلك ويمنعنا من إطلاق النار على أنفسنا.

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

نعم ، ?. يعمل

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

للإضافة إلى ذلك ، إذا كنت تستخدم eslint وترغب في استخدام no-unnecessary-condition ، فسيتم تمييز مثل هذه الوصول على أنها غير ضرورية وإزالتها لأن المترجم يقول أنها غير محددة مطلقًا وهذا سيجعل ?. غير ضروري.

وأضافmartpie آسف أعتقد أنك أساءت فهم تعليقي ، توضيحا.

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

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

bradennapier Flow لا يستخدم T | undefined إما في توقيع الفهرس لـ Array s (مثل TypeScript ، للأسف.)

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

بشكل أساسي ، سيتم تغيير السلوك الافتراضي لتوقيع الفهرس إلى

  • قم بتضمين | undefined عند القراءة من مصفوفة أو كائن
  • لا تقم بتضمين | undefined عند الكتابة (لأننا نريد فقط قيم T في كائننا)

سيكون التعريف على هذا النحو:

type MaybeUndefined = {[key: string]: string};
const t: MaybeUndefined = {};

const x = t['foo'] // Has type string | undefined
t['foo'] = undefined // ERROR! 
t['foo'] = "test" // Ok

بالنسبة للأشخاص الذين لا يريدون تضمين undefined في النوع ، يمكنهم إما تعطيله باستخدام خيار مترجم أو تعطيله فقط لبعض الأنواع باستخدام عامل التشغيل ! :

type AlwaysDefined = {[key: string]!: string};
const t: AlwaysDefined = {};

const x = t['foo'] // Has type string
t['foo'] = undefined // ERROR! 
t['foo'] = "test" // Ok

لعدم كسر الكود الحالي ، قد يكون من الأفضل أيضًا تقديم خيار مترجم جديد يؤدي إلى تضمين undefined (كما هو موضح في النوع MaybeUndefined أعلاه). إذا لم يتم تحديد هذا الخيار ، فستتصرف جميع أنواع توقيعات الفهرس كما لو تم استخدام عامل التشغيل ! ضمن الإعلان.

ظهرت فكرة أخرى: قد يرغب المرء في استخدام نفس النوع في بعض الأماكن في الكود بسلوكين مختلفين (بما في ذلك undefined أو لا). يمكن تعريف تعيين نوع جديد:

type MakeDefined<T> = {[K in keyof T]!: T[K]}

هل سيكون هذا امتدادًا جيدًا لـ TypeScript؟

تعجبني الفكرة ولكني أظن أنه سيتعين علينا الحصول على https://github.com/microsoft/TypeScript/issues/2521 أولاً - والتي ، لا تفهمني بشكل خاطئ ، ما زلت أعتقد أنه يجب علينا ذلك تمامًا ، لكنني ' م لا تحبس أنفاسي.

bradennapier Flow لا يستخدم T | undefined إما في توقيع الفهرس لـ Array s (مثل TypeScript ، للأسف.)

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

@ فهارس كائن o[4] حيث o هو النوع object ؟ لا يسمح لك TypeScript بفعل ذلك .....

لا يمكن استخدام تعبير من النوع '4' لفهرسة النوع '{}'
الخاصية "4" غير موجودة في النوع "{}". ts (7053)

RDGthree لن تكون

مع استثناءrictNullChecks ، ليس لدينا علامات تغير سلوك نظام النوع. تعمل العلامات عادةً على تمكين / تعطيل الإبلاغ عن الأخطاء.

وبعد ذلك بقليل قام RyanCavanaugh بما يلي:

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

لذلك في الأساس - هم مدركون تمامًا لحقيقة طلب العلم ، وحتى الآن لم يكونوا مقتنعين بأن الأمر يستحق العمل بالنسبة لهم من أجل الفائدة المشكوك فيها إلى حد ما التي اقترح أن يحصل عليها الأشخاص من هذا. بصراحة ، لست مندهشًا ، لأن الجميع يواصل المجيء إلى هنا مع الاقتراحات والأمثلة نفسها التي تم تناولها بالفعل. من الناحية المثالية ، يجب عليك فقط استخدام for of أو أساليب للمصفوفات ، و Map بدلاً من الكائنات (أو بشكل أقل فعالية ، قيم الكائن 'T | undefined`).

أنا هنا لأنني أحتفظ بحزمة DT شائعة ويطلب الناس دائمًا إضافة |undefined إلى أشياء مثل خرائط رأس http ، وهو أمر معقول تمامًا ، باستثناء الجزء الذي قد يؤدي إلى كسر كل استخدام موجود تقريبًا ، وحقيقة أنه يجعل الاستخدامات الآمنة الأخرى مثل Object.entries() أسوأ بكثير في الاستخدام. بخلاف التذمر من أن رأيك أفضل من رأي المبدعين في Typescript ، ما هي مساهمتك هنا؟

سايمون ، ربما أخطأت في قراءة تعليقك لكنه يبدو نوعًا ما كحجة لصالح الاقتراح الأصلي. قدرة "الميل الأخير" المفقودة هي معاملة الخصائص على أنها | undefined أحيانًا ، اعتمادًا على السياق ، ولكن ليس في حالات أخرى. لهذا السبب رسمت تشبيهًا بـ # 2521 upthread.

في سيناريو مثالي ، يمكنني التصريح عن مصفوفة أو كائن بهذا المعطى

ts const arr: Array<T>; const n: number; const obj: {[k: K]: V}; const k: K;

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

  • arr[n] كـ T | undefined
  • arr[n] = undefined خطأ
  • لديّ وصول إلى نوع من التكرار على arr يمنحني قيمًا مكتوبة على النحو T ، وليس T | undefined
  • obj[k] كـ V | undefined
  • obj[k] = undefined خطأ
  • Object.entries() على سبيل المثال يجب أن يعطيني مجموعات من [K, V] ولا يمكن عدم تحديد أي شيء

إنها الطبيعة غير المتكافئة للمشكلة التي تحدد هذه المسألة في رأيي.

أود أن أقول إن العبارة التي تقول إنها مشكلة للمطورين الذين يستخدمون حلقات for تفشل في مراعاة المطورين الذين يستخدمون Array Destructuring. إنه معطل في كتابته ولن يتم إصلاحه بسبب هذه المشكلة. يوفر كتابة غير دقيقة. يعد تدمير صفيف IMO الذي يتم تنفيذه مشكلة أكبر بكثير من تلك التي لا تزال تستخدم حلقات for.

const example = (args: string[]) => {
  const [userID, duration, ...reason] = args
  // userID and duration is AUTOMATICALLy inferred to be a string here. 
 // However, if for whatever reason args is an empty array 
// userID is actually `undefined` and NOT a `string`. 

// This is valid but it should not be because userID could be undefined
userID.toUpperCase()
}

لا أريد الابتعاد كثيرًا عن النقطة الأصلية للمشكلة ، لكن يجب أن تعلن حقًا طريقة المثال هذه باستخدام نوع tuple لوسيطة الوظيفة. إذا تم الإعلان عن الوظيفة كـ ([userId, duration, ...reason]: [string, number, ...string[]]) => {} فلن تقلق بشأن هذا في المقام الأول.

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

for (let i = 0; i < array.length; i++) {
  const value = array[i] as string | undefined
}

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

const example = (args: [string | undefined, string | undefined, ...string[] | ...undefined[]]) => {

}

هذا ليس لطيفًا على الإطلاق. ولكن كما قلت أعلاه ، فإن المشكلة الرئيسية لا تحتاج حتى إلى كتابتها على هذا النحو ، لأن TS لا تحذر من هذا على الإطلاق. هذا يؤدي إلى مطوري البرامج الذين ينسون هذا لدفع الكود ، ولم يجد Cool CI أي مشاكل في دمج tsc ونشره. يعطي TS إحساسًا بالثقة في الكود ، كما أن تقديم كتابة غير دقيقة يعطي إحساسًا زائفًا بالثقة. => أخطاء في وقت التشغيل!

هذا ليس "فريق عمل" ، هذا هو كتابة المعلمات الرسمية بشكل صحيح. كانت وجهة نظري أنك إذا كتبت طريقتك بشكل صحيح ، فإن أي شخص كان يطلق عليها بطريقة غير آمنة بالتوقيع القديم ( args: string[] ) سيحصل الآن على أخطاء في وقت الترجمة ، كما ينبغي ، عندما لا يكون مضمونًا تمرير العدد الصحيح من الحجج. إذا كنت ترغب في الاستمرار في السماح للأشخاص بالابتعاد عن الفشل في اجتياز جميع الحجج التي تحتاجها ، والتحقق منها بنفسك في وقت التشغيل ، فمن السهل جدًا كتابة (args: [string?, number?, ...string[]]) . هذا بالفعل يعمل بشكل رائع وليس حقًا وثيق الصلة بهذه المشكلة.

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

لا توجد طريقة للتغلب على وجود أنواع غير دقيقة يتم توفيرها لـ Array Destructuring.

لا أريد الابتعاد كثيرًا عن النقطة الأصلية للمشكلة

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

سيتم حل بعض إحباطاتي من هذه المشكلة من خلال بعض الصيغ البسيطة لتطبيق | undefined على النوع الذي يمكن استنتاجه بطريقة أخرى من الوصول إلى الكائن. على سبيل المثال:

const complexObject: { [key: string]: ComplexType } = { ... };
const maybeKey = 'two';

// From:
const maybeValue = complexObject[maybeKey] as
  | (Some<Complex<Type>> & Pick<WithPlentyOf, 'Utility'> & Types)
  | undefined;

// To:
const maybeValue2 = complexObject[maybeKey] as ?;

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

const maybeValue2 = complexObject[maybeKey] as
  | typeof complexObject[typeof maybeKey]
  | undefined;

أتخيل أن هذا النوع من بناء جملة "تأكيد النوع ربما" ( as ? ) سيسهل أيضًا تنفيذ قاعدة eslint require-safe-index-signature-access التي لا تسبب إرباكًا كبيرًا للمستخدمين الجدد. (على سبيل المثال ، "يمكنك إصلاح خطأ النسالة عن طريق إضافة as ? في النهاية.") يمكن أن تتضمن القاعدة أيضًا أداة إصلاح آلية آمنة نظرًا لأنها قد تتسبب فقط في تعطل التجميع ، وعدم حدوث مشكلات في وقت التشغيل مطلقًا.

function eh<T>(v: T): T | undefined {
    return v;
}

const arr = [0, 1, 2, 3, 4, 5];
const thing1 = arr[1]; // number
const thing2 = eh(arr[1]); // number | undefined

لا أعتقد أن هناك تطبيقًا واحدًا لهذا بخلاف ميزة TS المفاجئة هذه على الرغم من أنها

function eh<T>(v: T): T | undefined {
    return v;
}

const arr = [0, 1, 2, 3, 4, 5];
const thing1 = arr[1]; // number
const thing2 = eh(arr[1]); // number | undefined

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

يمكن أن يكون خيار بناء الجملة الآخر (والذي سيكون أقل تحديدًا لحالة الاستخدام من as ? أعلاه) هو السماح باستخدام الكلمة الرئيسية infer في تأكيدات النوع كعنصر نائب للنوع المستنتج بطريقة أخرى ، على سبيل المثال:

const maybeValue = complexObject[maybeKey] as infer | undefined;

يُسمح حاليًا بإعلانات infer فقط في عبارة extends من النوع الشرطي ( ts(1338) ) ، لذلك إعطاء الكلمة الأساسية infer معنى في سياق as [...] تأكيدات النوع لن تتداخل.

قد يكون هذا معقولاً لفرض استخدام infer | undefined عبر قاعدة eslint بينما يكون عامًا بما يكفي للحالات الأخرى ، على سبيل المثال حيث يمكن تحسين النوع الناتج باستخدام أنواع الأدوات المساعدة:

// Strange example, maybe from an unusual compiler originally written in JS:
if(someRegularExpression.test(maybeKey)) {
  /**
  * If we are inside this code block and this `maybeKey` exists on `partialObject`, 
  * we know its `regexSuccess` must also be defined, though this knowledge 
  * cannot be encoded using TypeScript's type system.
  */
  const maybeValue = partialObject[maybeKey] as (Required<Pick<infer, 'regexSuccess'>> & infer) | undefined;
  // ...
}
// Here we don't know if `regexSuccess` is defined on `maybeValue2`:
const maybevalue2 = partialObject[maybeKey] as infer | undefined;
function eh<T>(v: T): T | undefined {
    return v;
}

const arr = [0, 1, 2, 3, 4, 5];
const thing1 = arr[1]; // number
const thing2 = eh(arr[1]); // number | undefined

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

سلوكنا الحالي هو أن التعبير من النوع Array<string>[number] يتم تفسيره على أنه "النوع الذي يحدث عندما تقوم بفهرسة مصفوفة برقم". يسمح لك بالكتابة

const t: Array<string>[number] = "hello";

كطريقة ملتوية لكتابة const t: string .

إذا كان هذا العلم موجودًا ، فماذا يجب أن يحدث؟

إذا كان Array<string>[number] هو string | undefined ، فأنت تواجه مشكلة تتعلق بالسلامة في عمليات الكتابة:

function write<T extends Array<unknown>>(arr: T, v: T[number]) {
    arr[0] = v;
}
const arr = ["a", "b", "c"];
// Would be OK
write(arr, undefined);

إذا كان Array<string>[number] هو string ، إذن لديك نفس المشكلة كما هو موصوف في القراءات:

function read<T extends Array<unknown>>(arr: T): T[number] {
    return arr[14];
}
const arr = ["a", "b", "c"];
// Would be OK
const k: string = read(arr);

يبدو من التعقيد إضافة بناء جملة النوع لكل من "نوع قراءة الفهرس لـ" و "نوع كتابة الفهرس". أفكار؟

يجب أن أضيف أن تغيير معنى Array<string>[number] يمثل مشكلة حقيقية لأنه يعني أن هذه العلامة تغير تفسير ملفات التصريح. هذا أمر محطم للأعصاب لأن هذا يبدو وكأنه نوع من العلم يمكن لعدد كبير من الأشخاص تمكينه ، ولكنه قد يتسبب في حدوث / عدم حدوث أخطاء في الأشياء المنشورة من DefinitelyTyped ، لذلك ربما يتعين علينا التأكد من أن كل شيء يبني نظيفة في كلا الاتجاهين. من الواضح أن عدد العلامات التي يمكننا إضافتها مع هذا السلوك صغير للغاية (لا يمكننا اختبار 64 تكوينًا مختلفًا لكل حزمة من الأنواع) لذلك أعتقد أنه سيتعين علينا ترك Array<T>[number] كـ T فقط لتجنب هذه المشكلة.

RyanCavanaugh في المثال الخاص بك باستخدام write(arr, undefined) ، سيتم قبول الاستدعاء لـ write ، لكن ألا يتم تجميع المهمة arr[0] = v; ؟

إذا كانت الوظيفة write أعلاه تأتي من مكتبة ، وكانت JS تم تجميعها بدون العلامة ، فتأكد من أنها ستكون غير صحيحة ، ومع ذلك ، فهذه مشكلة موجودة بالفعل لأنه يمكن تجميع المكتبات بأي إشارات تختارها. إذا تم تجميع مكتبة بدون عمليات تحقق فارغة صارمة ، فيمكن لوظيفة فيها إرجاع string ، بينما يجب أن تُرجع string|undefined . لكن يبدو أن معظم المكتبات هذه الأيام تنفذ عمليات فحص لاغية ، وإلا يشتكي الناس. وبالمثل ، بمجرد وجود هذه العلامة الجديدة ، نأمل أن تبدأ المكتبات في تنفيذها ، وفي النهاية ستقوم معظم المكتبات بتعيينها.

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

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

إذا صفيف[الرقم] عبارة عن سلسلة | undefined ، إذن لديك مشكلة تتعلق بالصحة في ما يكتب:

في رأيي ، يجب أن يكون string|undefined Array<string>[number] دائمًا string|undefined . إنها الحقيقة ، إذا قمت بالفهرسة في مصفوفة بأي رقم ، فسأحصل إما على نوع عنصر المصفوفة أو غير محدد. لا يمكنك أن تكون أكثر تحديدًا هناك ما لم تقم بتشفير طول المصفوفة كما تفعل مع tuples. المثال الخاص بك في الكتابة لن يقوم بكتابة check ، لأنه لا يمكنك تعيين string|undefined لفهرس مصفوفة.

يبدو من التعقيد إضافة بناء جملة النوع لكل من "نوع قراءة الفهرس لـ" و "نوع كتابة الفهرس". أفكار؟

يبدو أن هذا هو بالضبط ما يجب أن يكون عليهما لأنهما شيئان مختلفان. لن يتم تعريف أي مصفوفة على الإطلاق ، لذلك سيكون لديك دائمًا إمكانية الحصول على غير معرف. نوع Array<string>[number] سيكون string|undefined . من أجل تحديد ما تريده T في Array<T> ، يمكن استخدام نوع الأداة المساعدة (التسمية ليست رائعة): ArrayItemType<Array<string>> = string . هذا لا يساعد مع أنواع Record ، والتي قد تحتاج إلى شيء مثل RecordValue<Record<string, number>, string> = string .

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

لا أشعر بشدة بالحاجة إلى ذلك في المصفوفات ، نظرًا لأن العديد من اللغات الأخرى (بما في ذلك اللغات "الآمنة" مثل Rust) تترك مسؤولية عمليات التحقق من الحدود للمستخدم ، ولذلك اعتدت أنا والعديد من المطورين فعل ذلك . أيضًا ، تميل البنية إلى جعلها واضحة تمامًا في هذه الحالات ، لأنها _ دائمًا_ تستخدم تدوين القوس مثل foo[i] .

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


يبدو من التعقيد إضافة بناء جملة النوع لكل من "نوع قراءة الفهرس لـ" و "نوع كتابة الفهرس". أفكار؟

يمكن توسيع بناء جملة getter و setter من جافا سكريبت لتشمل تعريفات الكتابة. على سبيل المثال ، الصيغة التالية مفهومة تمامًا بواسطة TypeScript:

const foo = {
  _bar: "",
  get bar(): string {
    return this._bar;
  },
  set bar(value: string) {
    this._bar = value;
  }
}

ومع ذلك ، يقوم TS حاليًا "بدمج" هذه في نوع واحد ، نظرًا لأنه لا يتتبع نوعي "get" و "set" للحقل بشكل منفصل. يعمل هذا الأسلوب مع معظم الحالات الشائعة ، ولكن له أوجه قصور خاصة به ، مثل مطالبة المحسّنين والمحدّدين بمشاركة نفس النوع ، وتعيين نوع getter بشكل غير صحيح إلى حقل يعرّف المُعيِّن فقط.

إذا كان على TS أن يتتبع "get" و "set" بشكل منفصل لجميع الحقول (التي من المحتمل أن يكون لها بعض تكاليف الأداء الحقيقية) ، فسيؤدي ذلك إلى حل هذه المراوغات ، كما يوفر آلية لوصف الميزة الموضحة في هذه المشكلة:

type foo = {
  [key: string]: string
}

سيصبح بشكل أساسي اختصارًا لـ:

type foo = {
  get [key: string](): string | undefined;
  set [key: string](string): string;
}

يجب أن أضيف أن تغيير معنى Array[number] يمثل مشكلة حقيقية لأنه يعني أن هذه العلامة تغير تفسير ملفات التصريح.

إذا كانت ملفات التصريح المُنشأة تستخدم دائمًا صيغة getter + setter الكاملة ، فسيكون هذا _ فقط_ مشكلة للملفات المكتوبة بخط اليد. قد يكون تطبيق القواعد الحالية على بنية الاختزال المتبقية _ في ملفات التعريف فقط_ حلاً هنا. من المؤكد أنه سيحل مشكلات التوافق ، ولكنه سيضيف أيضًا إلى التكلفة العقلية لقراءة ملفات .ts مقابل ملفات .d.ts .


جانبا:

الآن بالطبع ، هذا لا يعالج _جميع_ التحذيرات الدقيقة المتعلقة بالحاصل على / أدوات التحديد:

foo.bar = "hello"

// TS assumes that bar is now a string, which technically isn't guaranteed when
// custom setters and getters are used.
const result: string = foo.bar

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

أشعر حقًا أنه يتعين عليّ فقط نشر كل بضع صفحات من التعليقات وإسقاط رابط إلى # 2521 مرة أخرى.

يبدو من التعقيد إضافة بناء جملة النوع لكل من "نوع قراءة الفهرس لـ" و "نوع كتابة الفهرس". أفكار؟

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

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

أعتقد أن القصد كان التساؤل عما إذا كان يجب أن تكون هناك طريقة للإشارة إلى أنواع القراءة والكتابة ، على سبيل المثال ، هل يجب أن يكون هناك شيء مثل Array<string>[number]= للإشارة إلى نوع الكتابة؟

سأكون سعيدًا بحل كل من # 2521 وهذه المشكلة إذا حدث شيئان:

  • أولاً ، اكتب getters والمحددات بشكل مختلف ، حسب # 2521
  • بعد ذلك ، قم بإنشاء علامة كما تمت مناقشته في هذه المسألة ، بحيث يكون "نوع الكتابة" Array<string>[number] هو string بينما "نوع القراءة" هو string | undefined .

أفترض (عن طريق الخطأ؟) أن الأول سيضع الأساس للأخير ، راجع https://github.com/microsoft/TypeScript/issues/13778#issuecomment -630770947. ليس لدي أي حاجة خاصة إلى بناء جملة صريح لتعريف "نوع الكتابة" بنفسي.

RyanCavanaugh سريع التلوي السؤال: هل سيكون من المفيد أكثر أن يكون اقتراحي المحدد أعلاه كمسألة منفصلة لغرض مناقشة وتتبع نقاط القوة / الضعف / التكاليف بشكل أكثر وضوحًا (خاصة وأن تكاليف الأداء ستكون غير بديهية)؟ أم أن هذا يضيف فقط إلى إصدار ضوضاء؟

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

const t: Array<string>[number] = "hello";

كطريقة ملتوية لكتابة const t: string .

أعتقد أن هذا هو نفس المنطق بالنسبة إلى المجموعات؟

const t : [string][number] = 'hello' // const t: string

لكن tuples يدرك الحدود ويعيد undefined بشكل صحيح لأنواع الأرقام الأكثر تحديدًا:

const t0: [string][0] = 'hello' // ok
const t1: [string][1] = 'hello' // Type '"hello"' is not assignable to type 'undefined'.

// therefore this is already true!
const t2: [string][0|1] // string | undefined 

لماذا ستعمل كتابة number بطريقة مختلفة؟

يبدو أن لديك الآليات الموجودة للقيام بذلك: https://github.com/microsoft/TypeScript/issues/38779

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

في حال كان ذلك مفيدًا لأي شخص ، فإليك مكونًا إضافيًا لـ ESLint يحدد الوصول إلى المصفوفات غير الآمنة وتدمير المصفوفات / الكائنات. لا يشير إلى الاستخدامات الآمنة مثل المجموعات والعناصر (غير المسجلة).

https://github.com/danielnixon/eslint-plugin-total-functions

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

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

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

مجرد فضول ، هل يمكن أن تخبرنا ما هو الجزء غير الجميل ؟ ربما يمكننا حلها إذا كان بإمكانك مشاركة المزيد حول هذا الموضوع.

لا ينبغي أن يكون هذا خيارًا ، يجب أن يكون الخيار الافتراضي .

بمعنى ، إذا كان نظام الكتابة في TypeScript يصف في وقت الترجمة الأنواع التي يمكن أن تتخذها قيم JavaScript في وقت التشغيل:

  • تؤدي فهرسة علبة Array بالطبع إلى إرجاع undefined في JavaScript (الفهرس خارج الحدود أو مصفوفة متفرقة) ، لذلك سيكون توقيع القراءة الصحيح هو T | undefined .
  • "التسبب" في فهرس في Array ليكون undefined ممكن أيضًا عبر الكلمة الرئيسية delete .

كما يمنع TypeScript 4

const a = [1, 2]
delete a[1]

هناك حالة جيدة أيضًا لمنع a[1] = undefined .
يشير هذا إلى أن Array<T>[number] يجب أن يكون مختلفًا بالفعل للقراءة والكتابة.

قد يكون الأمر "معقدًا" ولكنه سيسمح بنمذجة إمكانيات وقت التشغيل في JavaScript بشكل أكثر دقة ؛
ما هو ما تم صنعه من أجل TypeScript ، أليس كذلك؟

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

أنا أتفق مع بقية المنشور بالرغم من ذلك.

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

أنا أتفق مع بقية المنشور بالرغم من ذلك.

ليس إذا كان خيار تكوين.

ليس إذا كان خيار تكوين.

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

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

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

أنا أتفق مع بقية المنشور بالرغم من ذلك.

إلى وجهة نظرك @ thw0rted لقد اقترحت تسوية هنا: https://github.com/microsoft/TypeScript/issues/38779

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

ل "strict" الاتساق شيء من Array<T> يجب أن ينظر إليه باعتباره "a list of type T that is infinitly long with no gaps" في أي وقت وهذا ليس المطلوب الذي يجب أن تستخدم الصفوف ..

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

إذا كنت توافق يرجى التصويت

لا أوافق على أن هذا يحتاج إلى خيار / تكوين جديد تمامًا.

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

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

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

هناك تحديات عملية وتقنية حقيقية تحتاج إلى حل هنا ومن المخيب للآمال أنه لا يمكننا القيام بعمل هادف في هذا الموضوع لأنه مليء بالأشخاص الذين يقترحون أن أي شيء خلف العلم ليس له عواقب حقيقية أو أن أي خطأ يمكن أن أن يكون ts-ignore 'd ليس عبئًا للترقية.

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

شكرا لك على الملخص الخاص بك.

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

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

1. حدد إحصائيًا التركيبات التي يحتمل أن تكون أخطاء.

(المصدر: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals)

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

حسنًا الآن بعد أن وضعت الأمر بهذه الطريقة أنا مقتنع

بالطبع ، أنت أحد المشرفين الأساسيين وأشكرك أنت والفريق والمساهمين في ts. لكن الأمر لم يعد متعلقًا بك فقط. قارن الأصوات المؤيدة: 365 مع معارضة: 6! 6 فقط! إنه يظهر طلبًا كبيرًا على أمان الأنواع.

لكن دعنا نتحدث عن الحلول. هل هناك أي حل قد يتوصل إليه الفريق أو يمكن التفكير فيه؟

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

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

أي أكثر من ذلك. قارن الأصوات المؤيدة: 365 مع معارضة: 6! 6 فقط! إنه يظهر طلبًا كبيرًا على أمان الأنواع.
- @ بيسونوف

هاها.
لا أتفق مع مشاركة RyanCavanaugh بالكامل .. ولكن ... هيا ..
هناك ما الملايين؟ مستخدمو TS هناك؟!؟ 365 تريد هذه الميزة كافية للتعليق والتصويت في هذا الموضوع ...

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

RyanCavanaugh شكرًا جزيلاً لك على منحنا فرصة للعب بها. أقوم بتشغيله على قاعدة شفرة صغيرة جدًا (~ 105 ts (x) files) ولعبت بها قليلاً. لم أجد أي مشكلة مهمة. سطر واحد تم تغييره من:

const refHtml = useRef(useMemo(() => document.getElementsByTagName('html')[0], []))

إلى:

const refHtml = useRef(useMemo(() => document.getElementsByTagName('html')[0] ?? null, []))

سأحاول في مشروع متوسط ​​الأسبوع المقبل.

@ lonewarrior556 إنها

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

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

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

MatthiasKunnen إذا كانت هذه

العلم هو النهج الخاطئ لهذا ، جزئيًا بسبب الاندماج
مشكلة. يجب علينا فقط تسمية TypeScript v5 ... هذا النوع من النهج
تحول عدد التركيبات القابلة للاختبار من 2 ^ N إلى N فقط ...

يوم السبت ، 15 آب (أغسطس) 2020 الساعة 15:56 ، مايكل بيركمان [email protected]
كتب:

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

-
أنت تتلقى هذا لأنك مشترك في هذا الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/microsoft/TypeScript/issues/13778#issuecomment-674408067 ،
أو إلغاء الاشتراك
https://github.com/notifications/unsubscribe-auth/AAADY5AXEY65S5HDGNGIPZDSA2O3FANCNFSM4C6KEKAA
.

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

ماذا عن الأشخاص العشرة التاليين الذين يريدون التعليق هنا فقط اختبروا مسودة PR # 39560 بدلاً من ذلك؟

هذا أمر مزعج للغاية إذا كنت تريد تمكين قاعدة ESLint @typescript-eslint/no-unnecessary-condition لأنها تشكو بعد ذلك من جميع حالات

if (some_array[i] === undefined) {

إنها تعتقد أنها حالة غير ضرورية (لأن تنكسكريبت تقول إنها كذلك!) لكنها ليست كذلك. لا أريد حقًا أن أضطر إلى إضافة // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition جميع أنحاء قاعدة الكود الخاصة بي.

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

if (some_array[?i] === undefined) {

(اقتراح بناء جملة عشوائي ؛ نرحب بالبدائل)

Timmmm يرجى قراءة التعليق فقط فوق احد قمت بها. هناك تصميم يمكنك تجربته لتنفيذ نسخة من هذا الاقتراح - يمكنك إخبارنا هناك إذا كان يحل مشكلتك eslint .

@ the0rted لقد قرأت هذا التعليق. إنه لا ينفذ نسخة من اقتراحي. ربما يجب عليك فقط قراءته مرة أخرى.

عذرًا ، عندما قلت "هذا" الاقتراح ، كنت أعني القدرة في OP ، أي معاملة array_of_T[i] كـ T | undefined . أرى أنك تسأل عن بناء جملة لتمييز عامل فهرس معين بأنه "ربما غير محدد" ، ولكن إذا كنت تستخدم التنفيذ في Ryan's PR ، فلن تحتاج إلى ذلك ، لأن جميع مشغلي الفهرس سيكونون "ربما غير معرّفين" . ألا يلبي هذا حاجتك؟

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

لذلك أردت أن أقدم اقتراحًا بديلاً ، لكن يبدو أن الناس يكرهونه. : - /

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

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

لم أذكرها أعلاه ، ولكن توجد بالفعل بنية للتخطي لمرة واحدة: if ((some_array[i] as MyType | undefined) === undefined) . انها ليست كما مقتضب كما اختزال جديد ولكن نأمل انها ليست بناء عليك أن تستخدم في كثير من الأحيان.

_نشرها في الأصلosyrisrblx في https://github.com/microsoft/TypeScript/issues/40435#issuecomment -690017567_

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

ربما يمكن أن يؤكد !: تعريفه دائمًا؟

interface X {
    [index: string]!: number; // -> number
}

interface Y {
    [index: string]: number; // -> number | undefined
}

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

يمكنك تجربة ذلك في الإصدار التجريبي من TypeScript 4.1 ، والذي سيصدر قريبًا ™ (أو الليلة إذا كنت في حاجة إليه الآن! 🙂)

عذرًا إذا تم عرض هذا من قبل ، ولكن هل يمكن دعم {[index: string]?: number} // -> number | undefined ؟ إنه متوافق مع بناء جملة الخصائص الاختيارية للواجهة - مطلوب افتراضيًا ، وربما غير محدد بـ "؟".

خيار المترجم في 4.1 رائع ، ولكن وجود المزيد من التحكم الدقيق سيكون جيدًا أيضًا.

إذا كنت ترغب في تجربته في الريبو الخاص بك (أخذني قليلاً لاكتشاف ذلك):

  1. تثبيت typecript @ next yarn (add|upgrade) typescript@next
  2. أضف علامة (بالنسبة لي في tsconfig.json) "noUncheckedIndexedAccess": true

في عملية تمكين هذه القاعدة في مشروعي ، صادفت هذا الخطأ المثير للاهتمام:

type MyRecord = { a: number; b: string };

declare const myRecord: MyRecord;

declare const key: 'a' | 'b';
const value = myRecord[key]; // string | number ✅

// ❌ Unexpected error
// Type 'MyRecord[Key] | undefined' is not assignable to type 'MyRecord[Key]'
const fn = <Key extends keyof MyRecord>(key: Key): MyRecord[Key] => myRecord[key];

في هذه الحالة لم أكن أتوقع أن يقوم myRecord[key] بإرجاع النوع MyRecord[Key] | undefined ، لأن key مقيد بـ keyof MyRecord .

تحديث : مشكلة مقدمة https://github.com/microsoft/TypeScript/issues/40666

ربما يكون هذا خطأ / سهو!

في عملية تمكين هذه القاعدة في مشروعي ، صادفت هذا الخطأ المثير للاهتمام:

type MyRecord = { a: number; b: string };

declare const myRecord: MyRecord;

declare const key: 'a' | 'b';
const value = myRecord[key]; // string | number ✅

// ❌ Unexpected error
// Type 'MyRecord[Key] | undefined' is not assignable to type 'MyRecord[Key]'
const fn = <Key extends keyof MyRecord>(key: Key): MyRecord[Key] => myRecord[key];

في هذه الحالة لم أكن أتوقع أن يقوم myRecord[key] بإرجاع النوع MyRecord[Key] | undefined ، لأن key مقيد بـ keyof MyRecord .

سأقول أن هذا خطأ. بشكل أساسي ، إذا كان keyof Type يشتمل فقط على أنواع حرفية سلسلة / رقم (على عكس تضمين string أو number ) ، ثم Type[Key] حيث Key extends keyof Type يجب ألا تتضمن undefined ، على ما أعتقد.

الربط التبادلي لـ # 13195 ، والذي يبحث أيضًا في الاختلافات وأوجه التشابه بين "لا توجد خاصية هنا" و "هناك خاصية هنا ولكنها undefined "

تتبع بعض المشكلات المتعلقة بقيود التصميم هنا

  • # 41612

لمعلوماتك: لقد اكتشفت خطأين في قاعدة بياناتي بفضل هذا العلم ، شكرًا جزيلاً لك!
إنه أمر مزعج بعض الشيء أن التحقق من arr.length > 0 ليس كافيًا لحماية arr[0] ، لكن هذا إزعاج بسيط (يمكنني إعادة كتابة الشيك لجعل tsc سعيدًا) مقارنة بالأمان الإضافي ، IMO.

rubenlg أوافق على arr.length . لمجرد التحقق من العنصر الأول ، أعدت كتابة الكود الخاص بنا مثل

const firstEl = arr[0];
if (firstEl !== undefined) {
  ...
}

ولكن هناك عدد قليل من الأماكن حيث نقوم بعمل if (arr.length > 2) أو أكثر ، وهو أمر محرج بعض الشيء. ومع ذلك ، لا أعتقد أن التحقق من .length سيكون آمنًا تمامًا على أي حال حيث يمكنك فقط تعديله:

const a: number[] = [];

a.length = 1;

if (a.length > 0) {
    const b: number = a[0];
    console.log(b);
}

يطبع undefined .

ومع ذلك ، لا أعتقد أن التحقق من .length سيكون آمنًا تمامًا على أي حال حيث يمكنك فقط تعديله:

const a: number[] = [];

a.length = 1;

if (a.length > 0) {
    const b: number = a[0];
    console.log(b);
}

يطبع undefined .

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

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

const a: number[] = [1]
if (a.length > 0) {
    a.pop();
    console.log(a[0])
}
هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات