استدلال الكتابة باستخدام TypeScript باستخدام بناء الجملة والكلمة الأساسية المستنتجة

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



اكتب أساسيات الاستدلال


أولاً ، ألق نظرة على أبسط مثال لاستدلال النوع.

let variable;

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

let variable = 'Hello!';

هنا أعلنا متغيرًا وكتبنا على الفور قيمة فيه. يمكن لـ TypeScript الآن تخمين أن هذا المتغير من النوع string، لذلك لدينا الآن متغير مكتوب مقبول تمامًا.

ينطبق نهج مماثل على الوظائف:

function getRandomInteger(max: number) {
  return Math.floor(Math.random() * max);
}

في هذا الكود ، لا نشير إلى أن الدالة getRandomIntegerترجع رقمًا. لكن مترجم TypeScript يعرف ذلك جيدًا.

اكتب الاستدلال في الأدوية العامة


ترتبط المفاهيم المذكورة أعلاه بالأنواع العالمية (الأدوية العامة). إذا كنت تريد أن تعرف المزيد عن الوراثة، نلقي نظرة على هذا و هذه المواد.

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

function getProperty<ObjectType, KeyType extends keyof ObjectType>(
  object: ObjectType, key: KeyType
) {
  return object[key];
}

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

const dog = {
  name: 'Fluffy'
};
getProperty(dog, 'name');

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

استخدام الكلمة الأساسية


واحدة من ميزات TypeScript الأكثر تقدمًا التي تتبادر إلى الذهن عند الحديث عن استدلال النوع هي الكلمة الرئيسية infer.

تأمل في مثال. قم بإنشاء الوظيفة التالية:

function call<ReturnType>(
  functionToCall: (...args: any[]) => ReturnType, ...args: any[]
): ReturnType {
  return functionToCall(...args);
}

اتصل بمساعدة هذه الوظيفة بوظيفة أخرى واكتب ما تعود إليه في ثابت:

const randomNumber = call(getRandomInteger, 100);

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

const randomNumber = call(getRandomInteger, '100'); //   

نظرًا لأن TypeScript يدعم معلمات الانتشار والراحة في وظائف الترتيب الأعلى ، يمكننا حل هذه المشكلة كما يلي:

function call<ArgumentsType extends any[], ReturnType>(
  functionToCall: (...args: ArgumentsType) => ReturnType, ...args: ArgumentsType
): ReturnType {
  return functionToCall(...args);
}

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

الآن دعنا نحاول مرة أخرى لإجراء مكالمة دالة غير صحيحة:

const randomNumber = call(getRandomInteger, '100');

ينتج عن هذا رسالة خطأ:

Argument of type ‘”100″‘ is not assignable to parameter of type ‘number’.

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

type Option = [string, boolean];
const option: Option = ['lowercase', true];

ميزات الكلمات الرئيسية تستنتج


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

type FunctionReturnType<FunctionType extends (...args: any) => ?> = ?;

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

type FunctionReturnType<ReturnType, FunctionType extends (...args: any) => ReturnType> = ReturnType;
FunctionReturnType<number, typeof getRandomInteger>;

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

type FunctionReturnType<FunctionType extends (args: any) => any> = FunctionType extends (...args: any) => infer ReturnType ? ReturnType : any;

إليك ما يحدث في هذا الرمز:

  • تقول FunctionTypeالتوسع هنا (args: any) => any.
  • نشير إلى أن FunctionReturnTypeهذا النوع مشروط.
  • نتحقق مما إذا كان يتوسع FunctionType (...args: any) => infer ReturnType.

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

FunctionReturnType<typeof getRandomInteger>; // number

ما سبق هو مهمة شائعة بحيث يحتوي TypeScript على أداة مساعدة مدمجة ReturnType ، والتي تم تصميمها لحل هذه المشكلة.

بناء على شكل ثابت


وثمة مسألة أخرى تتعلق نوع الاستدلال هو الفرق بين الكلمات الرئيسية constو letالتي يتم استخدامها عند إعلان الثوابت والمتغيرات.

let fruit = 'Banana';
const carrot = 'Carrot';

متغير fruit- له نوع string. هذا يعني أنه يمكن تخزين أي قيمة سلسلة فيه.

والثابت carrotهو سلسلة حرفية. يمكن اعتباره كمثال على نوع فرعي string. تم وصف الوصف الحرفي لسلسلة الأحرف في PR هذا : "النوع الحرفي للسلسلة هو نوع قيمته المتوقعة هي سلسلة بمحتوى نصي يعادل نفس محتويات السلسلة الحرفية."

يمكن تغيير هذا السلوك. يقدم TypeScript 3.4 ميزة جديدة مثيرة للاهتمام تسمى التأكيدات المستمرة التي توفر استخدام بنية as const. إليك كيف يبدو استخدامه:

let fruit = 'Banana' as const;

الآن fruitهي سلسلة حرفية. كما أن التصميم as constمناسب أيضًا عندما يحتاج بعض الكيانات إلى التغيير. ضع في الاعتبار الكائن التالي:

const user = {
  name: 'John',
  role: 'admin'
};

في JavaScript ، constتعني الكلمة الأساسية أنه لا يمكنك استبدال ما تم تخزينه في ثابت user. ولكن ، من ناحية أخرى ، يمكنك تغيير البنية الداخلية لكائن مسجل في هذا الثابت.

يخزن الكائن الآن الأنواع التالية:

const user: {
  name: string,
  role: string
};

لكي ينظر النظام إلى هذا الكائن على أنه غير قابل للتغيير ، يمكنك استخدام التصميم as const:

const user = {
  name: 'John',
  role: 'admin'
} as const;

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

const user: {
  readonly name: 'John',
  readonly role: 'admin'
};

وعند العمل مع المصفوفات ، تفتح أمامنا إمكانيات أكثر قوة:

const list = ['one', 'two', 3, 4];

نوع هذا الصفيف هو (string | number)[]. باستخدام هذا الصفيف ، as constيمكنك تحويله إلى مجموعة:

const list = ['one', 'two', 3, 4] as const;

الآن يبدو نوع هذا الصفيف كما يلي:

readonly ['one', 'two', 3, 4]

كل هذا ينطبق على الهياكل الأكثر تعقيدًا. تأمل في المثال الذي قدمه أندرس هاليسبرغ في خطابه في TSConf 2019 :

const colors = [
  { color: 'red', code: { rgb: [255, 0, 0], hex: '#FF0000' } },
  { color: 'green', code: { rgb: [0, 255, 0], hex: '#00FF00' } },
  { color: 'blue', code: { rgb: [0, 0, 255], hex: '#0000FF' } },
] as const;

صفيفنا colorsمحمي الآن من التغييرات ، وعناصره محمية أيضًا من التغييرات:

const colors: readonly [
    {
        readonly color: 'red';
        readonly code: {
            readonly rgb: readonly [255, 0, 0];
            readonly hex: '#FF0000';
        };
    },
    /// ...
]

ملخص


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

القراء الأعزاء! هل تستخدم الكلمات الأساسية inferوالبناء as constفي TypeScript؟


All Articles