الهدف هو إظهار أين يعطي TS وهم الأمن ، مما يسمح لك بالحصول على أخطاء أثناء تشغيل البرنامج.لن نتحدث عن الخلل ، في TS يوجد ما يكفي من1500 بق مفتوح و 6000 مغلق ('هو: المشكلة هي: تسمية مفتوحة: خطأ').سيتم النظر في جميع الأمثلة مع:- وضع TS الصارم قيد التشغيل (كتب مقالًا أثناء الفهم)
 - بدون صريح "any": "as any"، "Objects"، "Function"، {[key: string]: unknown}
 - بدون "أي" ضمنيًا: (noImplicitAny): عمليات الاستيراد غير المنسوخة (ملفات JS الخالصة) ، استدلال النوع غير صحيح
 - بدون تخمينات خاطئة حول الأنواع: استجابة من الخادم ، طباعة مكتبات جهات خارجية
 
المحتوى:- المقدمة
 - الأنواع الاسمية ، الأنواع المخصصة - عندما تبدو الأشياء كما هي ، ولكنها مختلفة جدًا
 - تباين النوع ، الأنواع الدقيقة - حول العلاقة بين الأنواع
 - إبطال التحسين - الحديث عن الثقة
 - الاستثناءات - هل يستحق الاعتراف عند العبث؟
 - العمليات غير الآمنة - الثقة ليست دائمًا جيدة
 - حالات المكافأة - التحقق من النوع في مرحلة مراجعة العلاقات العامة
 - استنتاج
 
المقدمة
هل من الصعب كتابة دالة لإضافة رقمين في شبيبة؟ اتخاذ تنفيذ ساذجfunction sum(a, b) {
	return a + b;
}
دعونا نتحقق من تطبيقنا لـ `sum (2، 2) === 4` ، هل يبدو أن كل شيء يعمل؟ ليس في الحقيقة ، عندما نصف وظيفة ، يجب أن نفكر في جميع أنواع قيم المدخلات ، وكذلك ما يمكن أن تعيده الدالة1.1 + 2.7   
NaN + 2     
99999999999999992 + 99999999999999992 
2n + 2      
            
{} + true   
2 + '2'     
Soundness هي قدرة المحلل على إثبات عدم وجود أخطاء أثناء تشغيل البرنامج. إذا تم قبول البرنامج من قبل المحلل ، فمن المؤكد أنه آمن.البرنامج الآمن هو برنامج يمكنه العمل إلى الأبد دون أخطاء. أولئك. البرنامج لن يتلف أو يرمي الأخطاء.برنامج صحيح - برنامج يقوم بما ينبغي ولا يفعل ما لا ينبغي. يعتمد التصحيح على تنفيذ منطق الأعمال.يمكن للأنواع أن تثبت أن البرنامج ككل آمن ، ويختبر أن البرنامج آمن وصحيح فقط ضمن بيانات الاختبار (تغطية 100٪ ، وغياب "المسوخ" من stryker ، واجتياز اختبار قائم على الممتلكات وما إلى ذلك لا يمكن إثبات أي شيء ، والتراخيص يقلل من المخاطر). هناك أساطير تثبت النظرية أنها يمكن أن تثبت صحة البرنامج.من المهم فهم فلسفة TS ، لفهم ما تحاول الأداة حله ، وما هو مهم ، وما لا تحاول حله. تتخطىملاحظة حول SoundnessTS بعض العمليات غير المؤكدة في مرحلة التجميع. تم التفكير بعناية في الأماكن ذات السلوك غير السليم.أهداف التصميمليست الهدف من TS - لإنشاء نظام نوع مع ضمان للأمان ، وبدلاً من ذلك التركيز على التوازن بين السلامة والإنتاجيةمثال الهيكل:المشكلة ليست السلوك الآمن ، القائمة قد لا تكون كاملة ، هذا ما وجدته في المقالات والتقارير ، قضايا بوابة TS.الاقتراح هو قضية TS مفتوحة قبل 3-4 سنوات ، مع مجموعة من التعليقات والتفسيرات المثيرة للاهتمام من قبل المؤلفيننصيحة - IMHO للمؤلف ، ما يعتبره المؤلف الممارسات الجيدةالكتابة الهيكلية مقابل الكتابة الاسمية
الكتابة الهيكلية مقابل الكتابة الاسمية 1. المشكلة
الكتابة الهيكلية - عندما لا تراعي مقارنة الأنواع أسمائها أو مكان الإعلان عنها ، ويتم مقارنة الأنواع وفقًا لـ "البنية".نريد إرسال الحرف `sendEmail` إلى العنوان الصحيح` ValidatedEmail` ، وهناك وظيفة للتحقق من العنوان` validateEmail` الذي يُرجع العنوان الصحيح` ValidatedEmail`. للأسف TS يسمح لك بإرسال أي سلسلة إلى `sendEmail` ، لأن `ValidatedEmail` لـ TS لا يختلف عن` string`type ValidatedEmail = string;
declare function validateEmail(email: string): ValidatedEmail;
declare function sendEmail(mail: ValidatedEmail): void;
sendEmail(validateEmail("asdf@gmail.com"));
// Should be error!
sendEmail("asdf@gmail.com");
الكتابة الهيكلية مقابل الكتابة الاسمية 1. العرض
github.com/microsoft/TypeScript/issues/202أدخل الكلمة الأساسية "الاسمية" حتى يتم تحديد الأنواع بشكل رمزي. الآن يمكننا منع تمرير `سلسلة` فقط حيث يتوقع` ValidatedEmail`nominal type ValidatedEmail = string;
declare function validateEmail(email: string): ValidatedEmail;
declare function sendEmail(mail: ValidatedEmail): void;
sendEmail(validateEmail('asdf@gmail.com'));
// Error!
sendEmail('asdf@gmail.com');
الكتابة الهيكلية مقابل الاسمية 1. تلميح
يمكننا إنشاء نوع `Opaque` ، والذي سيأخذ بعضًا من` T` وإعطائه تفردًا من خلال دمجه مع نوع تم إنشاؤه من `K` الذي تم تمريره. يمكن أن يكون `K` إما رمزًا فريدًا (` رمزًا فريدًا`) أو سلسلة (عندئذٍ سيكون من الضروري التأكد من أن هذه السلاسل فريدة).type Opaque<K extends symbol | string, T> 
	= T & { [X in K]: never };
declare const validatedEmailK: unique symbol;
type ValidatedEmail = Opaque<typeof validatedEmailK, string>;
declare function validateEmail(email: string): ValidatedEmail;
declare function sendEmail(mail: ValidatedEmail): void;
sendEmail(validateEmail('asdf@gmail.com'));
// Argument of type '"asdf@gmail.com"' is not assignable
//  to parameter of type 'Opaque<unique symbol, string>'.
sendEmail('asdf@gmail.com');
الكتابة الهيكلية مقابل الكتابة الاسمية 2. المشكلة
لدينا فئة الدولار واليورو ، كل فئة لديها طريقة إضافة لإضافة الدولار إلى الدولار واليورو إلى اليورو. بالنسبة لـ TS ، هذه الفئات متساوية هيكليًا ويمكننا إضافة الدولار إلى اليورو.export class Dollar {
  value: number;
  constructor(value: number) {
    this.value = value;
  }
  add(dollar: Dollar): Dollar {
    return new Dollar(dollar.value + this.value);
  }
}
class Euro {
  value: number;
  constructor(value: number) {
    this.value = value;
  }
  add(euro: Euro): Euro {
    return new Euro(euro.value + this.value);
  }
}
const dollars100 = new Dollar(100);
const euro100 = new Euro(100);
dollars100.add(dollars100);
euro100.add(euro100);
dollars100.add(euro100);
الكتابة الهيكلية مقابل الكتابة الاسمية 2. العرض
github.com/microsoft/TypeScript/issues/202الجملة هي نفسها ، مع "الاسمي" ، ولكن منذ نظرًا لأن الفصول يمكن أن تصبح اسمًا سحريًا (المزيد عن ذلك لاحقًا) ، يتم النظر في إمكانيات إجراء مثل هذا التحول بطريقة أكثر وضوحًا.الكتابة الهيكلية مقابل الاسمية 1. تلميح
إذا كان للفصل حقل خاص (أصلي بـ `#` أو من TS c `خاص`) ، فعندئذ يصبح الفصل بطريقة سحرية ، يمكن أن يكون الاسم والقيمة أي شيء. يتم استخدام `!` (تأكيد التعيين المحدد) لمنع TS من الشتائم في حقل غير مهيأ (يتم تمكين العلامات الصارمة ، علامات صارمة على خصائص التعريف).class Dollar {
  
  private desc!: never;
  value: number;
  constructor(value: number) {
    this.value = value;
  }
  add(dollar: Dollar) {
    return new Dollar(dollar.value + this.value);
  }
}
class Euro {
  
  private desc!: never;
  value: number;
  constructor(value: number) {
    this.value = value;
  }
  add(euro: Euro) {
    return new Euro(euro.value + this.value);
  }
}
const dollars100 = new Dollar(100);
const euro100 = new Euro(100);
dollars100.add(dollars100);
euro100.add(euro100);
dollars100.add(euro100);
تباين النوع 1. المشكلة
خيار البرمجة ، باختصار ، هو القدرة على تمرير Supertype / Subtype هناك ، حيث يتوقع النوع. على سبيل المثال ، هناك شكل هرمي -> دائرة -> مستطيل ، هل من الممكن نقل أو إرجاع شكل / مستطيل إذا كان من المتوقع وجود دائرة؟البديل في برمجة الهابر ، SO .يمكننا تمرير النوع مع الحقل الذي يقع فيه الرقم إلى دالة تتوقع الحقل كسلسلة أو رقم ، وتحوير الكائن المرسل في الجسم ، وتغيير الحقل إلى سلسلة. أولئك. `{status: number} مثل {status: number | string} مثل {status: string} `هنا خدعة مثل تحويل رقم إلى سلسلة ، مما يتسبب في حدوث خطأ مفاجئ .function changeStatus(arg: { status: number | string }) {
  arg.status = "NotFound";
}
const error: { status: number } = { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
نوع التباين 1. عرض
github.com/Microsoft/TypeScript/issues/10717يُقترح إدخال "داخل / خارج" للحد صراحةً من التغاير / التعارض في الأدوية الجنسية.function changeStatus<
  out T extends {
    status: number | string;
  }
>(arg: T) {
  arg.status = "NotFound";
}
const error: { status: number } = { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
نوع التباين 1. نصيحة
إذا عملنا بهياكل غير قابلة للتغيير ، فلن يكون هناك مثل هذا الخطأ (لقد قمنا بالفعل بتمكين إشارة نوع الوظيفة الصارمة).function changeStatus(arg: Readonly<{ status: number | string }>) {
  
  arg.status = "NotFound";
}
const error: Readonly<{ status: number }> = { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
نوع التباين 1. مكافأة
Readonly قابل للتخصيص علىgithub.com/Microsoft/TypeScript/issues/13347github.com/microsoft/TypeScript/pull/6532#issuecomment-174356151ولكن حتى إذا أنشأنا نوع Readonly ، فلن يمنع TS من الانتقال إلى الوظيفة حيث غير متوقع للقراءة فقط `Readonly <{readonly status: number}> مثل {status: number | string} مثل {status: string} `function changeStatus(arg: { status: number | string }) {
  arg.status = "NotFound";
}
const error: Readonly<{ readonly status: number }> 
  = { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
نوع التباين 2. المشكلة
قد تحتوي الكائنات على حقول إضافية لا تحتوي عليها الأنواع المقابلة لها: `{message: string؛ status: string} مثل {message: string} `. بسبب بعض العمليات قد لا تكون آمنةconst error: { message: string; status: string } = {
  message: "No data",
  status: "NotFound"
};
function updateError(arg: { message: string }) {
  const defaultError = { message: "Not found", status: 404 };
  const newError: { message: string; status: number }
    = { ...defaultError, ...arg };
  
  
  console.log(newError.status.toFixed());
}
updateError(error);
يعتقد TS أنه نتيجة لعملية الدمج ، ستكون حالة {... {message: string، status: number}، ... {message: string}} `رقمًا.في الواقع ، `{... {message:" Not found "، status: 404}، ... {message:" No data "، status:" NotFound "}،}` status - string.نوع التباين 2. العرض
github.com/microsoft/TypeScript/issues/12936تقديمه ل`نوع Exact` أو بناء جملة مماثل القول بأن نوع لا يمكن أن تحتوي حقول إضافية.const error: Exact<{ message: string; }> = {
  message: "No data",
};
function updateError(arg: Exact<{ message: string }>) {
  const defaultError = {  message: "Not found", status: 404, };
  
  const newError = { ...defaultError, ...arg };
  console.log(newError.status.toFixed());
}
updateError(error);
نوع التباين 2. تلميح
دمج الكائنات من خلال سرد الحقول بشكل صريح أو تصفية الحقول غير المعروفة.const error: { message: string; status: string } = {
  message: "No data",
  status: "NotFound"
};
function updateError(arg: { message: string }) {
  const defaultError = { message: "Not found", status: 404 };
  
  const newError = { ...defaultError, message: arg.message };
  console.log(newError.status.toFixed());
}
updateError(error);
إبطال التنقية. مشكلة
بعد أن أثبتنا شيئًا بشأن الحالة الخارجية ، فإن وظائف الاتصال ليست آمنة ، لأن لا توجد ضمانات بأن الوظائف لا تغير هذه الحالة الخارجية:export function logAge(name: string, age: number) {
  
  console.log(`${name} will lose ${age.toFixed()}`);
  person.age = "PLACEHOLDER";
}
const person: { name: string; age: number | string } = {
  name: "Person",
  age: 42
};
if (typeof person.age === "number") {
  logAge(person.name, person.age);
  
  logAge(person.name, person.age);
}
إبطال التنقية. جملة او حكم على
github.com/microsoft/TypeScript/issues/7770#issuecomment-334919251إضافة `pure` معدل للوظائف، هذه الإرادة على الأقل تسمح لك أن تثق هذه الوظائفإبطال التنقية. تلميح
استخدم بنيات البيانات غير القابلة للتغيير ، عندها ستكون استدعاء الوظيفة آمنة مسبقًا لعمليات الفحص السابقة.علاوة
نوع التدفق قوي لدرجة أنه لا يحتوي على جميع المشاكل المذكورة أعلاه ، ولكنه يعمل بحيث لا أوصي باستخدامه.الاستثناءات. مشكلة
لا يساعد TS في العمل مع الاستثناءات بأي شكل من الأشكال ؛ لا يوجد شيء واضح على توقيع الوظيفة.import { JokeError } from "../helpers";
function getJoke(isFunny: boolean): string {
  if (isFunny) {
    throw new JokeError("No funny joke");
  }
  return "Duh";
}
const joke: string = getJoke(true);
console.log(joke);
الاستثناءات. جملة او حكم على
github.com/microsoft/TypeScript/issues/13219يقترح إدخال بناء جملة يسمح بوصف الاستثناءات بوضوح في توقيع الوظيفةimport { JokeError } from '../helpers';
function getJoke(isFunny: boolean): string | throws JokeError {
  }
function getJokeSafe(isFunny: boolean): string {
  try {
    return getJoke(isFunny);
  } catch (error) {
    if (error instanceof JokeError) {
      return "";
    } else {
      
      return error as never;
    }
  }
}
console.log(getJokeSafe(true));
الاستثناءات. علاوة
github.com/microsoft/TypeScript/issues/6283لسبب ما ، في TS ، يتجاهل تعريف نوع Promise نوع الخطأconst promise1: Promise<number> = Promise.resolve(42);
const promise: Promise<never> = Promise.reject(new TypeError());
interface PromiseConstructor {
  new <T>(
    executor: (
      resolve: (value?: T | PromiseLike<T>) => void,
      reject: (reason?: any) => void
    ) => void
  ): Promise<T>;
}
الاستثناءات. تلميح
خذ أيًا من الحاوية مثل Promise ، مع أفضل كتابة فقط. ( إما مثال على التنفيذ )import { Either, exhaustiveCheck, JokeError } from "../helpers";
function getJoke(isFunny: boolean): Either<JokeError, string> {
  if (isFunny) {
    return Either.left(new JokeError("No funny joke"));
  }
  return Either.right("Duh");
}
getJoke(true)
  
  .mapLeft(error => {
    if (error instanceof JokeError) {
      console.log("JokeError");
    } else {
      exhaustiveCheck(error);
    }
  })
  
  .mapRight(joke => console.log(joke));
عمليات غير آمنة. مشكلة
إذا كان لدينا مجموعة ذات حجم ثابت ، فيمكن أن تضمن TS وجود شيء في الفهرس المطلوب. لن يعمل هذا مع المصفوفة وسوف يثق بنا TS
export const constNumbers: readonly [1, 2, 3] 
  = [1, 2, 3] as const;
console.log(constNumbers[100].toFixed());
const dynamicNumbers: number[] = [1, 2, 3];
console.log(dynamicNumbers[100].toFixed());
عمليات غير آمنة. جملة او حكم على
github.com/microsoft/TypeScript/issues/13778يُقترح إضافة `undefined` إلى نوع الإرجاع` T` للوصول إلى الفهرس إلى الصفيف. ولكن في هذه الحالة ، عند الوصول إلى أي فهرس ، سيكون عليك استخدام "؟" أو إجراء فحوص صريحة.
const dynamicNumbers: number[] = [1, 2, 3];
console.log(dynamicNumbers[100].toFixed());
console.log(dynamicNumbers[100]?.toFixed());
if (typeof dynamicNumbers[100] === 'number') {
  console.log(dynamicNumbers[100].toFixed());
}
عمليات غير آمنة. تلميح
من أجل عدم إنتاج كيانات تتجاوز الحاجة ، نأخذ الحاوية المعروفة سابقًا `إما 'ونكتب وظيفة آمنة للعمل مع الفهرس ، والتي ستعود` إما <فارغة ، T> `.import { Either } from "../helpers";
function safeIndex<T>(
  array: T[],
  index: number,
): Either<null, T> {
  if (index in array) {
    return Either.right(array[index]);
  }
  return Either.left(null);
}
const dynamicNumbers: number[] = [1, 2, 3];
safeIndex(dynamicNumbers, 100)
  .mapLeft(() => console.log("Nothing"))
  .mapRight(el => el + 2)
  .mapRight(el => console.log(el.toFixed()));
علاوة وظائف إعادة التحميل
إذا أردنا أن نقول أن الدالة تأخذ بضعة أسطر وتعيد سلسلة أو تأخذ عددًا من الأرقام وترجع رقمًا ، فعند التنفيذ ستكون هذه التوقيعات متجاورة ، ويجب على المبرمج ضمان صحتها ، ولكن على TS.ملاحظة نظرة على البديل من خلال الأنواع العامة والشرطية:function add(a: string, b: string): string;
function add(a: number, b: number): number;
function add(a: string | number,
             b: string | number,
): string | number {
  return `${a} + ${b}`;
}
const sum: number = add(2, 2);
sum.toFixed();
علاوة اكتب حارس
تثق TS في المبرمج بأن `isSuperUser` يحدد بشكل صحيح من هو" SuperUser "وإذا تمت إضافة" Vasya "، فلن تكون هناك مطالبات.PS يستحق التفكير في كيفية تمييز الأنواع الموجودة بالفعل في مرحلة اتحادهم الموسومtype SimpleUser = { name: string };
type SuperUser = { 
  name: string; 
  isAdmin: true; 
  permissions: string[] 
};
type Vasya = { name: string; isAdmin: true; isGod: true };
type User = SimpleUser | SuperUser | Vasya;
function isSuperUser(user: User): user is SuperUser {
  return "isAdmin" in user && user.isAdmin;
}
function doSomethings(user: User) {
  
  if (isSuperUser(user)) {
    console.log(user.permissions.join(","));
  }
}
استنتاجات على النصائح
- أنواع الاسمية : نوع معتم، وحقول خاصة- نوع التباين : أنواع المطابقة، نوع DeepReadonly- الاستثناءات : إما الكائن الدقيق الاحادي الخلية- صقل إبطال : وظائف الصرفة- العمليات غير الآمنة (الوصول إلى رقم قياسي) : إما / ربما الكائنات الدقيقة الاحاديه الخليةغير قابل للتغيير البيانات، وظائف نقية، الكائنات الدقيقة الاحاديه الخلية ... مبروك ، أثبتنا أن FP رائع!الموجودات
- تريد TS تحقيق التوازن بين الصحة والإنتاجية
 - يمكن أن تثبت الاختبارات الأمان والصحة لبيانات الاختبار فقط.
 - يمكن أن تثبت الأنواع الأمان العام للبرنامج.
 - طفرة - سيئة ، حسنا؟
 
كما توصي DZ باللعب مع Flow بعد تصحيح خطأ بسيط:
declare function log(arg: { name: string, surname?: string }): void;
const person: { name: string } =  { name: 'Negasi' };
log(person);
رمز المثال ، حل المشكلة وتحليلها ، روابط مفيدة في المستودع .