الكتابة الثابتة الديناميكية في TypeScript
نشرت: 2022-03-10JavaScript هي لغة برمجة ديناميكية بطبيعتها. نحن كمطورين يمكننا التعبير عن الكثير بجهد ضئيل ، واللغة ووقت تشغيلها يحددان ما نعتزم القيام به. هذا ما يجعل JavaScript شائعًا جدًا للمبتدئين ، مما يجعل المطورين ذوي الخبرة منتجين! ومع ذلك ، هناك تحذير: يجب أن نكون يقظين! الأخطاء المطبعية وسلوك البرنامج الصحيح: الكثير من ذلك يحدث في رؤوسنا!
الق نظرة على المثال التالي.
app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { res.status(20).send({ message: "Got you, user " + req.params.userId }); } })
لدينا https://expressjs.com/-style الخادم الذي يسمح لنا بتحديد المسار (أو المسار) ، وتنفيذ رد الاتصال إذا تم طلب عنوان URL.
تأخذ رد الاتصال وسيطين:
- كائن
request
.
هنا نحصل على معلومات عن طريقة HTTP المستخدمة (على سبيل المثال GET و POST و PUT و DELETE) ، والمعلمات الإضافية التي تأتي. في هذا المثال ، يجب تعيين معرفuserID
userID
، حسناً ، على معرف المستخدم! - كائن
response
أوreply
.
هنا نريد إعداد استجابة مناسبة من الخادم للعميل. نريد إرسال أكواد الحالة الصحيحة (status
الطريقة) وإرسال إخراج JSON عبر السلك.
ما نراه في هذا المثال مبسط بشكل كبير ، لكنه يعطي فكرة جيدة عما نحن بصدد القيام به. المثال أعلاه مليء بالأخطاء! الق نظرة:
app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { /* Error 1 */ res.status(20).send({ /* Error 2 */ message: "Welcome, user " + req.params.userId /* Error 3 */ }); } })
أوه ، واو! ثلاثة أسطر من كود التنفيذ وثلاثة أخطاء؟ ماذا حدث؟
- الخطأ الأول دقيق. بينما نخبر تطبيقنا أننا نريد الاستماع إلى طلبات GET (ومن ثم
app.get
) ، فإننا لا نفعل شيئًا إلا إذا كانت طريقة الطلب هي POST . في هذه المرحلة بالذات في طلبنا ، لا يمكن أن تكونreq.method
POST . لذلك لن نرسل أبدًا أي رد ، مما قد يؤدي إلى انقضاء مهلة غير متوقعة. - عظيم أننا نرسل صراحة رمز الحالة!
20
ليس رمز حالة صالحًا. قد لا يفهم العملاء ما يحدث هنا. - هذا هو الرد الذي نريد إرساله مرة أخرى. نصل إلى الحجج التي تم تحليلها ولكن هناك خطأ مطبعي. إنه معرف
userID
وليسuserId
المستخدم. سيتم الترحيب بجميع مستخدمينا بعبارة "مرحبًا ، مستخدم غير محدد!". شيء بالتأكيد رأيته في البرية!
وأشياء من هذا القبيل تحدث! خاصة في JavaScript. نحن نكتسب تعبيرًا - لم يكن علينا أن نقلق بشأن الأنواع مرة واحدة - ولكن علينا أن نولي اهتمامًا وثيقًا لما نقوم به.
هذا هو المكان الذي تحصل فيه JavaScript على الكثير من ردود الفعل العكسية من المبرمجين غير المعتادين على لغات البرمجة الديناميكية. عادة ما يكون لديهم مترجمين يوجهونهم إلى المشاكل المحتملة ويصطادون الأخطاء مقدمًا. قد يبدون متعجرفين عندما يستهجنون مقدار العمل الإضافي الذي يتعين عليك القيام به في رأسك للتأكد من أن كل شيء يعمل بشكل صحيح. قد يخبرونك حتى أن JavaScript ليس لها أنواع. وهذا غير صحيح.
قال Anders Hejlsberg ، المهندس الرئيسي لـ TypeScript ، في كلمته الرئيسية MS Build 2017 " لا يعني ذلك أن JavaScript ليس به نظام كتابة. لا توجد طريقة لإضفاء الطابع الرسمي عليه ".
وهذا هو الغرض الرئيسي من TypeScript. يريد TypeScript أن يفهم شفرة JavaScript أفضل منك. وحيث لا تستطيع TypeScript معرفة ما تعنيه ، يمكنك المساعدة من خلال توفير معلومات كتابة إضافية.
الكتابة الأساسية
وهذا ما سنفعله الآن. لنأخذ طريقة get
من خادم Express-style الخاص بنا ونضيف معلومات كتابة كافية حتى نتمكن من استبعاد أكبر عدد ممكن من فئات الأخطاء.
نبدأ ببعض المعلومات الأساسية. لدينا كائن app
يشير إلى دالة get
. تأخذ وظيفة get
path
، وهو عبارة عن سلسلة ، واستدعاء.
const app = { get, /* post, put, delete, ... to come! */ }; function get(path: string, callback: CallbackFn) { // to be implemented --> not important right now }
في حين أن string
هي نوع أساسي ، ما يسمى بالنوع البدائي ، فإن CallbackFn
هو نوع مركب يتعين علينا تحديده بشكل صريح.
CallbackFn
هو نوع دالة يأخذ وسيطتين:
-
req
، وهو من النوعServerRequest
-
reply
وهو من النوعServerReply
CallbackFn
ترجع void
.
type CallbackFn = (req: ServerRequest, reply: ServerReply) => void;
ServerRequest
هو كائن معقد جدًا في معظم أطر العمل. نقوم بعمل نسخة مبسطة لأغراض العرض. نقوم بتمرير سلسلة method
، لـ "GET"
، "POST"
، "PUT"
، "DELETE"
، إلخ. كما أن لديها سجل params
. السجلات هي كائنات تربط مجموعة من المفاتيح بمجموعة من الخصائص. في الوقت الحالي ، نريد السماح بتعيين كل مفتاح string
إلى خاصية string
. نحن نعيد بناء هذا لاحقًا.
type ServerRequest = { method: string; params: Record<string, string>; };
بالنسبة لـ ServerReply
، نضع بعض الوظائف ، مع العلم أن كائن ServerReply
الحقيقي يحتوي على أكثر من ذلك بكثير. تأخذ وظيفة send
وسيطة اختيارية مع البيانات التي نريد إرسالها. ولدينا إمكانية تعيين رمز الحالة مع وظيفة status
.
type ServerReply = { send: (obj?: any) => void; status: (statusCode: number) => ServerReply; };
هذا شيء بالفعل ، ويمكننا استبعاد خطأين:
app.get("/api/users/:userID", function(req, res) { if(req.method === 2) { // ^^^^^^^^^^^^^^^^^ Error, type number is not assignable to string res.status("200").send() // ^^^^^ Error, type string is not assignable to number } })
لكن لا يزال بإمكاننا إرسال رموز حالة خاطئة (أي رقم ممكن) وليس لدينا أي دليل حول طرق HTTP الممكنة (أي سلسلة ممكنة). دعونا نحسن أنواعنا.
مجموعات أصغر
يمكنك رؤية الأنواع الأولية كمجموعة من جميع القيم الممكنة لتلك الفئة المعينة. على سبيل المثال ، تتضمن string
جميع السلاسل الممكنة التي يمكن التعبير عنها في JavaScript ، ويتضمن number
جميع الأرقام الممكنة بدقة تعويم مزدوجة. تتضمن boolean
جميع القيم المنطقية الممكنة ، سواء كانت true
أو false
.
يسمح لك TypeScript بتحسين هذه المجموعات إلى مجموعات فرعية أصغر. على سبيل المثال ، يمكننا إنشاء Method
نوع يتضمن جميع السلاسل الممكنة التي يمكننا تلقيها لطرق HTTP:
type Methods= "GET" | "POST" | "PUT" | "DELETE"; type ServerRequest = { method: Methods; params: Record<string, string>; };
Method
هي مجموعة أصغر من مجموعة string
الأكبر. Method
هي نوع اتحاد للأنواع الحرفية. النوع الحرفي هو أصغر وحدة في مجموعة معينة. سلسلة حرفية. رقم حرفي. لا يوجد غموض. انها مجرد "GET"
. يمكنك وضعهم في اتحاد مع أنواع حرفية أخرى ، مما يؤدي إلى إنشاء مجموعة فرعية من الأنواع الأكبر التي لديك. يمكنك أيضًا عمل مجموعة فرعية بأنواع حرفية لكل من string
number
، أو أنواع كائنات مركبة مختلفة. هناك الكثير من الاحتمالات لدمج الأنواع الحرفية ووضعها في النقابات.
هذا له تأثير فوري على رد الاتصال بخادمنا. فجأة ، يمكننا التفريق بين هذه الطرق الأربع (أو أكثر إذا لزم الأمر) ، ويمكننا استنفاد جميع الإمكانيات في الكود. سوف يرشدنا TypeScript:
app.get("/api/users/:userID", function (req, res) { // at this point, TypeScript knows that req.method // can take one of four possible values switch (req.method) { case "GET": break; case "POST": break; case "DELETE": break; case "PUT": break; default: // here, req.method is never req.method; } });
مع كل بيان case
تقوم به ، يمكن أن يوفر لك TypeScript معلومات حول الخيارات المتاحة. جربه بنفسك. إذا استنفدت جميع الخيارات ، فسوف يخبرك TypeScript في الفرع default
الخاص بك أن هذا لا يمكن أن يحدث never
. هذا هو حرفياً النوع never
، مما يعني أنك ربما وصلت إلى حالة خطأ تحتاج إلى معالجتها.
هذه فئة واحدة من الأخطاء أقل. نحن نعرف الآن بالضبط طرق HTTP الممكنة المتاحة.
يمكننا أن نفعل الشيء نفسه بالنسبة لرموز حالة HTTP ، من خلال تحديد مجموعة فرعية من الأرقام الصالحة التي يمكن أن statusCode
:
type StatusCode = 100 | 101 | 102 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 449 | 450 | 451 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 598 | 599; type ServerReply = { send: (obj?: any) => void; status: (statusCode: StatusCode) => ServerReply; };
اكتب StatusCode
مرة أخرى نوع اتحاد. وبذلك ، نستبعد فئة أخرى من الأخطاء. فجأة ، فشل رمز مثل هذا:
app.get("/api/user/:userID", (req, res) => { if(req.method === "POS") { // ^^^^^^^^^^^^^^^^^^^ 'Methods' and '"POS"' have no overlap. res.status(20) // ^^ '20' is not assignable to parameter of type 'StatusCode' } })
ويصبح برنامجنا أكثر أمانًا! لكننا نستطيع فعل المزيد!أدخل Generics
عندما نحدد مسارًا باستخدام app.get
، فإننا نعلم ضمنيًا أن طريقة HTTP الوحيدة الممكنة هي "GET"
. ولكن مع تعريفات النوع لدينا ، لا يزال يتعين علينا التحقق من جميع الأجزاء الممكنة من الاتحاد.
نوع CallbackFn
صحيح ، حيث يمكننا تحديد وظائف رد الاتصال لجميع طرق HTTP الممكنة ، ولكن إذا استدعينا صراحة app.get
، فسيكون من الجيد حفظ بعض الخطوات الإضافية الضرورية فقط للامتثال للكتابة.
يمكن أن تساعد الأدوية الجنيسة من TypeScript! تعد Generics إحدى الميزات الرئيسية في TypeScript والتي تتيح لك الحصول على السلوك الأكثر ديناميكية من الأنواع الثابتة. في TypeScript في 50 درسًا ، نقضي الفصول الثلاثة الأخيرة في البحث في جميع تعقيدات الأدوية الجنيسة ووظائفها الفريدة.
ما تحتاج إلى معرفته الآن هو أننا نريد تعريف ServerRequest
بطريقة يمكننا من خلالها تحديد جزء من Methods
بدلاً من المجموعة بأكملها. لذلك ، نستخدم البنية العامة حيث يمكننا تحديد المعلمات كما نفعل مع الوظائف:
type ServerRequest<Met extends Methods> = { method: Met; params: Record<string, string>; };
هذا هو ما يحدث:
- يصبح
ServerRequest
نوعًا عامًا ، كما هو مشار إليه بواسطة أقواس الزاوية - نحدد معلمة عامة تسمى
Met
، وهي مجموعة فرعية منMethods
النوع - نستخدم هذه المعلمة العامة كمتغير عام لتحديد الطريقة.
أشجعك أيضًا على مراجعة مقالتي حول تسمية المعلمات العامة.
مع هذا التغيير ، يمكننا تحديد ServerRequest
مختلفة دون تكرار الأشياء:
type OnlyGET = ServerRequest<"GET">; type OnlyPOST = ServerRequest<"POST">; type POSTorPUT = ServerRquest<"POST" | "PUT">;
نظرًا لأننا قمنا بتغيير واجهة ServerRequest
، يتعين علينا إجراء تغييرات على جميع الأنواع الأخرى التي تستخدم ServerRequest
، مثل CallbackFn
ووظيفة get
:
type CallbackFn<Met extends Methods> = ( req: ServerRequest<Met>, reply: ServerReply ) => void; function get(path: string, callback: CallbackFn<"GET">) { // to be implemented }
باستخدام دالة get
، نقوم بتمرير وسيطة فعلية إلى النوع العام. نحن نعلم أن هذه لن تكون مجرد مجموعة فرعية من Methods
، فنحن نعرف بالضبط المجموعة الفرعية التي نتعامل معها.
الآن ، عندما نستخدم app.get
، لدينا فقط القيمة الممكنة req.method
:
app.get("/api/users/:userID", function (req, res) { req.method; // can only be get });
هذا يضمن أننا لا نفترض أن طرق HTTP مثل "POST"
أو ما شابهها متاحة عندما نقوم بإنشاء app.get
callback. نحن نعلم بالضبط ما نتعامل معه في هذه المرحلة ، لذلك دعونا نعكس ذلك في أنواعنا.
لقد فعلنا الكثير بالفعل للتأكد من أن طريقة request.method
مكتوبة بشكل معقول وتمثل الحالة الفعلية للأمور. إحدى المزايا الرائعة التي نحصل عليها من خلال تعيين نوع اتحاد Methods
هي أنه يمكننا إنشاء وظيفة رد اتصال للأغراض العامة خارج app.get
.
const handler: CallbackFn<"PUT" | "POST"> = function(res, req) { res.method // can be "POST" or "PUT" }; const handlerForAllMethods: CallbackFn<Methods> = function(res, req) { res.method // can be all methods }; app.get("/api", handler); // ^^^^^^^ Nope, we don't handle "GET" app.get("/api", handlerForAllMethods); // This works
كتابة Params
ما لم نتطرق إليه بعد هو كتابة كائن params
. حتى الآن ، حصلنا على سجل يسمح بالوصول إلى كل مفتاح string
. مهمتنا الآن أن نجعل ذلك أكثر تحديدًا قليلاً!
نقوم بذلك عن طريق إضافة متغير عام آخر. واحد للطرق ، واحد للمفاتيح المحتملة في Record
:
type ServerRequest<Met extends Methods, Par extends string = string> = { method: Met; params: Record<Par, string>; };
يمكن أن يكون متغير النوع العام Par
مجموعة فرعية من string
النوع ، والقيمة الافتراضية هي كل سلسلة. مع ذلك ، يمكننا إخبار ServerRequest
بالمفاتيح التي نتوقعها:
// request.method = "GET" // request.params = { // userID: string // } type WithUserID = ServerRequest<"GET", "userID">
دعنا نضيف الوسيط الجديد إلى وظيفة get
ونوع CallbackFn
، حتى نتمكن من تعيين المعلمات المطلوبة:
function get<Par extends string = string>( path: string, callback: CallbackFn<"GET", Par> ) { // to be implemented } type CallbackFn<Met extends Methods, Par extends string> = ( req: ServerRequest<Met, Par>, reply: ServerReply ) => void;
إذا لم نقم بتعيين Par
بشكل صريح ، فإن النوع يعمل كما اعتدنا عليه ، حيث يتم تعيين Par
افتراضيًا على string
. إذا قمنا بتعيينه ، فسنحصل فجأة على تعريف مناسب لكائن req.params
!
app.get<"userID">("/api/users/:userID", function (req, res) { req.params.userID; // Works!! req.params.anythingElse; // doesn't work!! });
هذا عظيم! ومع ذلك ، هناك شيء واحد صغير يمكن تحسينه. لا يزال بإمكاننا تمرير كل سلسلة إلى وسيطة path
app.get
. ألن يكون من الأفضل أن نعكس Par
هناك أيضًا؟
نحن نقدر! مع إصدار الإصدار 4.1 ، يكون TypeScript قادرًا على إنشاء أنواع حرفية للقالب . من الناحية النحوية ، فهي تعمل تمامًا مثل القيم الحرفية لقالب السلسلة ، ولكن على مستوى النوع. حيث تمكنا من تقسيم string
المجموعة إلى مجموعات فرعية مع أنواع حرفية للسلسلة (كما فعلنا مع الطرق) ، تسمح لنا الأنواع الحرفية للقالب بتضمين مجموعة كاملة من السلاسل.
لنقم بإنشاء نوع يسمى IncludesRouteParams
، حيث نريد التأكد من تضمين Par
بشكل صحيح في طريقة Express-style لإضافة نقطتين أمام اسم المعلمة:
type IncludesRouteParams<Par extends string> = | `${string}/:${Par}` | `${string}/:${Par}/${string}`;
النوع العام IncludesRouteParams
يأخذ وسيطة واحدة ، وهي مجموعة فرعية من string
. يقوم بإنشاء نوع اتحاد من اثنين من النماذج الحرفية:
- يبدأ الحرف الأول للقالب بأي
string
، ثم يشتمل على حرف/
متبوعًا بحرف:
، متبوعًا باسم المعلمة. هذا يضمن أننا نلتقط جميع الحالات التي تكون فيها المعلمة في نهاية سلسلة المسار. - يبدأ القالب الحرفي الثاني بأي
string
، متبوعة بنفس نمط/
،:
واسم المعلمة. ثم لدينا حرف/
آخر ، متبوعًا بأي سلسلة. يتأكد هذا الفرع من نوع الاتحاد من اكتشاف جميع الحالات التي تكون فيها المعلمة في مكان ما داخل المسار.
هذه هي الطريقة التي يتصرف بها IncludesRouteParams
مع اسم المعلمة userID
مع حالات الاختبار المختلفة:
const a: IncludeRouteParams<"userID"> = "/api/user/:userID" // const a: IncludeRouteParams<"userID"> = "/api/user/:userID/orders" // const a: IncludeRouteParams<"userID"> = "/api/user/:userId" // const a: IncludeRouteParams<"userID"> = "/api/user" // const a: IncludeRouteParams<"userID"> = "/api/user/:userIDAndmore" //
لنقم بتضمين نوع الأداة المساعدة الجديد الخاص بنا في إعلان دالة get
.
function get<Par extends string = string>( path: IncludesRouteParams<Par>, callback: CallbackFn<"GET", Par> ) { // to be implemented } app.get<"userID">( "/api/users/:userID", function (req, res) { req.params.userID; // YEAH! } );
رائعة! نحصل على آلية أمان أخرى لضمان عدم تفويت إضافة المعلمات إلى المسار الفعلي! كم هي قوية.
الارتباطات العامة
لكن خمن ماذا ، ما زلت غير سعيد بذلك. هناك بعض المشكلات المتعلقة بهذا النهج والتي تصبح واضحة في اللحظة التي تصبح فيها طرقك أكثر تعقيدًا.
- المشكلة الأولى التي أواجهها هي أننا بحاجة إلى تحديد معلماتنا بشكل صريح في معلمة النوع العام. يجب علينا ربط
Par
بـ"userID"
، على الرغم من أننا سنحدده على أي حال في وسيطة مسار الوظيفة. هذه ليست JavaScript-y! - يتعامل هذا الأسلوب مع معلمة مسار واحدة فقط. لحظة إضافة اتحاد ، على سبيل المثال
"userID" | "orderId"
"userID" | "orderId"
التحقق الآمن من الفشل يكون مقتنعًا بإتاحة واحدة فقط من هذه الوسيطات. هذه هي الطريقة التي تعمل بها المجموعات. يمكن أن يكون أحدهما أو الآخر.
يجب أن تكون هناك طريقة أفضل. وهناك. خلاف ذلك ، سينتهي هذا المقال بملاحظة مريرة للغاية.
دعونا نعكس الترتيب! دعنا لا نحاول تعريف معلمات المسار في متغير نوع عام ، ولكن بدلاً من ذلك نستخرج المتغيرات من path
الذي نمرره كأول وسيطة لـ app.get
.
للوصول إلى القيمة الفعلية ، يتعين علينا معرفة كيفية عمل الربط العام في TypeScript. لنأخذ وظيفة identity
هذه على سبيل المثال:
function identity<T>(inp: T) : T { return inp }
قد تكون أكثر وظيفة عامة مملة تراها على الإطلاق ، لكنها توضح نقطة واحدة بشكل مثالي. identity
تأخذ حجة واحدة ، وتعيد نفس المدخلات مرة أخرى. النوع هو النوع العام T
، ويعيد نفس النوع أيضًا.
يمكننا الآن ربط T
string
، على سبيل المثال:
const z = identity<string>("yes"); // z is of type string
يتأكد هذا الربط العام بشكل صريح من أننا نقوم بتمرير strings
فقط إلى identity
، وبما أننا نربط صراحةً ، فإن نوع الإرجاع هو أيضًا string
. إذا نسينا الارتباط ، يحدث شيء مثير للاهتمام:
const y = identity("yes") // y is of type "yes"
في هذه الحالة ، يستنتج TypeScript النوع من الوسيطة التي تمررها ، ويربط T
بالنوع الحرفي للسلسلة "yes"
. هذه طريقة رائعة لتحويل وسيطة دالة إلى نوع حرفي ، والذي نستخدمه بعد ذلك في الأنواع العامة الأخرى.
لنفعل ذلك من خلال تكييف app.get
.
function get<Path extends string = string>( path: Path, callback: CallbackFn<"GET", ParseRouteParams<Path>> ) { // to be implemented }
نقوم بإزالة النوع Par
العام وإضافة Path
. يمكن أن يكون Path
مجموعة فرعية من أي string
. قمنا بتعيين path
إلى هذا النوع العام Path
، مما يعني أنه في اللحظة التي نمرر فيها معلمة get
، نلتقط النوع الحرفي لسلسلتها. نقوم بتمرير Path
إلى نوع عام جديد ParseRouteParams
لم نقم بإنشائه بعد.
دعنا نعمل على ParseRouteParams
. هنا ، نغير ترتيب الأحداث مرة أخرى. بدلاً من تمرير معلمات المسار المطلوبة إلى عام للتأكد من أن المسار على ما يرام ، نقوم بتمرير مسار المسار واستخراج معلمات المسار المحتملة. لذلك ، نحتاج إلى إنشاء نوع شرطي.
الأنواع الشرطية والأنواع الحرفية للقالب التكراري
الأنواع الشرطية مشابهة نحويًا للعامل الثلاثي في JavaScript. تقوم بالتحقق من وجود شرط ، وإذا تم استيفاء الشرط ، تقوم بإرجاع الفرع أ ، وإلا تقوم بإرجاع الفرع ب. على سبيل المثال:
type ParseRouteParams<Rte> = Rte extends `${string}/:${infer P}` ? P : never;
هنا ، نتحقق مما إذا كانت Rte
هي مجموعة فرعية من كل مسار ينتهي بالمعامل في نهاية النمط السريع (مع "/:"
سابقًا). إذا كان الأمر كذلك ، فإننا نستنتج هذه السلسلة. مما يعني أننا نلتقط محتوياته في متغير جديد. إذا تم استيفاء الشرط ، نعيد السلسلة المستخرجة حديثًا ، وإلا فإننا لا نرجع أبدًا ، كما في: "لا توجد معلمات مسار" ،
إذا جربناها ، فسنحصل على شيء من هذا القبيل:
type Params = ParseRouteParams<"/api/user/:userID"> // Params is "userID" type NoParams = ParseRouteParams<"/api/user"> // NoParams is never --> no params!
رائع ، هذا بالفعل أفضل بكثير مما فعلناه سابقًا. الآن ، نريد التعرف على جميع المعلمات الأخرى الممكنة. لذلك علينا إضافة شرط آخر:
type ParseRouteParams<Rte> = Rte extends `${string}/:${infer P}/${infer Rest}` ? P | ParseRouteParams<`/${Rest}`> : Rte extends `${string}/:${infer P}` ? P : never;
يعمل النوع الشرطي الآن على النحو التالي:
- في الحالة الأولى ، نتحقق مما إذا كان هناك معلمة مسار في مكان ما بين المسار. إذا كان الأمر كذلك ، فإننا نستخرج كلاً من معلمة المسار وكل شيء آخر يأتي بعد ذلك. نعيد معلمة المسار التي تم العثور عليها حديثًا
P
في اتحاد حيث نسمي نفس النوع العام بشكل متكرر معRest
. على سبيل المثال ، إذا مررنا المسار"/api/users/:userID/orders/:orderID"
إلىParseRouteParams
، فإننا نستنتج"userID"
فيP
، و"orders/:orderID"
فيRest
. نسمي نفس النوع معRest
- هذا هو المكان الذي يأتي فيه الشرط الثاني. هنا نتحقق مما إذا كان هناك نوع في النهاية. هذا هو الحال بالنسبة ل
"orders/:orderID"
. نستخرج"orderID"
هذا النوع الحرفي. - إذا لم يتبق مزيد من معلمات المسار ، فلن نعود أبدًا.
يُظهر Dan Vanderkam نوعًا مشابهًا وأكثر تفصيلاً لـ ParseRouteParams
، لكن النوع الذي تراه أعلاه يجب أن يعمل أيضًا. إذا ParseRouteParams
المعدلة حديثًا ، فسنحصل على شيء مثل هذا:
// Params is "userID" type Params = ParseRouteParams<"/api/user/:userID"> // MoreParams is "userID" | "orderID" type MoreParams = ParseRouteParams<"/api/user/:userID/orders/:orderId">
دعنا نطبق هذا النوع الجديد ونرى كيف يبدو استخدامنا النهائي لـ app.get
.
app.get("/api/users/:userID/orders/:orderID", function (req, res) { req.params.userID; // YES!! req.params.orderID; // Also YES!!! });
رائع. هذا يشبه كود JavaScript الذي كان لدينا في البداية!
أنواع ثابتة للسلوك الديناميكي
الأنواع التي أنشأناها للتو لوظيفة واحدة app.get
تأكد من أننا استبعدنا الكثير من الأخطاء المحتملة:
- يمكننا فقط تمرير أكواد الحالة الرقمية المناسبة إلى
res.status()
-
req.method
هو واحد من أربع سلاسل محتملة ، وعندما نستخدمapp.get
، نعلم أنه فقط"GET"
- يمكننا تحليل معلمات المسار والتأكد من عدم وجود أي أخطاء إملائية داخل رد الاتصال الخاص بنا
إذا نظرنا إلى المثال من بداية هذه المقالة ، فسنحصل على رسائل الخطأ التالية:
app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { // ^^^^^^^^^^^^^^^^^^^^^ // This condition will always return 'false' // since the types '"GET"' and '"POST"' have no overlap. res.status(20).send({ // ^^ // Argument of type '20' is not assignable to // parameter of type 'StatusCode' message: "Welcome, user " + req.params.userId // ^^^^^^ // Property 'userId' does not exist on type // '{ userID: string; }'. Did you mean 'userID'? }); } })
وكل ذلك قبل أن نقوم بالفعل بتشغيل الكود الخاص بنا! تعد الخوادم ذات النمط السريع مثالًا رائعًا على الطبيعة الديناميكية لجافا سكريبت. اعتمادًا على الطريقة التي تستدعيها ، السلسلة التي تمررها للمتوسط الأول ، يتغير الكثير من السلوك داخل رد الاتصال. خذ مثالًا آخر وستبدو كل أنواعك مختلفة تمامًا.
ولكن مع بعض الأنواع المحددة جيدًا ، يمكننا اكتشاف هذا السلوك الديناميكي أثناء تحرير الكود الخاص بنا. في وقت الترجمة مع الأنواع الثابتة ، وليس في وقت التشغيل عندما تزدهر الأشياء!
وهذه هي قوة TypeScript. نظام من النوع الثابت يحاول إضفاء الطابع الرسمي على كل سلوك JavaScript الديناميكي الذي نعرفه جميعًا جيدًا. إذا كنت ترغب في تجربة المثال الذي أنشأناه للتو ، توجه إلى ملعب TypeScript وتلاعب به.
في هذه المقالة ، تطرقنا إلى العديد من المفاهيم. إذا كنت ترغب في معرفة المزيد ، فراجع TypeScript في 50 درسًا ، حيث تحصل على مقدمة لطيفة لنظام الكتابة في دروس صغيرة سهلة الفهم. تتوفر إصدارات الكتب الإلكترونية على الفور ، وسيشكل الكتاب المطبوع مرجعًا رائعًا لمكتبة الترميز الخاصة بك.