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

مشكلة API الخادم الفاشلة
دعنا نفكر في مثال شرطي قائم على العديد من المشاريع الحقيقية. لنفترض أننا نقوم بتطوير موقع ويب جديد لمنظمة كانت موجودة لبعض الوقت. لديها بالفعل نقاط نهاية REST ، لكنها ليست مصممة تمامًا لما سنقوم بإنشائه. هنا نحتاج إلى الوصول إلى الخادم فقط لمصادقة المستخدم ، والحصول على معلومات عنه وتنزيل قائمة بالإشعارات غير المرئية من هذا المستخدم. نتيجة لذلك ، نحن مهتمون بنقاط النهاية التالية لواجهة برمجة تطبيقات الخادم:
/auth
: يخول المستخدم ويعيد رمز الوصول./profile
: إرجاع معلومات المستخدم الأساسية./notifications
: يسمح لك بالحصول على إخطارات المستخدم غير المقروءة.
تخيل أن تطبيقنا يحتاج دائمًا إلى تلقي جميع هذه البيانات في وحدة واحدة ، أي أنه من الأفضل أن يكون لدينا نقطة واحدة فقط بدلاً من ثلاث نقاط نهاية.
ومع ذلك ، فإننا نواجه مشاكل أكثر بكثير من العديد من نقاط النهاية. على وجه الخصوص ، نحن نتحدث عن حقيقة أن البيانات التي نتلقاها لا تبدو أفضل طريقة.
على سبيل المثال ، تم إنشاء نقطة النهاية
/profile
في العصور القديمة ، ولم تتم كتابتها في جافا سكريبت ، ونتيجة لذلك ، فإن أسماء الخصائص في البيانات التي تم إرجاعها إليها تبدو غير عادية لتطبيق JS:
{ "Profiles": [ { "id": 1234, "Christian_Name": "David", "Surname": "Gilbertson", "Photographs": [ { "Size": "Medium", "URLS": [ "/images/david.png" ] } ], "Last_Login": "2018-01-01" } ] }
بشكل عام - لا يوجد شيء جيد.
صحيح ، إذا نظرت إلى ما تنتج عنه نقطة النهاية
/notifications
، فستبدو البيانات الواردة أعلاه من
/profile
رائعة جدًا:
{ "data": { "msg-1234": { "timestamp": "1529739612", "user": { "Christian_Name": "Alice", "Surname": "Guthbertson", "Enhanced": "True", "Photographs": [ { "Size": "Medium", "URLS": [ "/images/alice.png" ] } ] }, "message_summary": "Hey I like your hair, it re", "message": "Hey I like your hair, it really goes nice with your eyes" }, "msg-5678": { "timestamp": "1529731234", "user": { "Christian_Name": "Bob", "Surname": "Smelthsen", "Photographs": [ { "Size": "Medium", "URLS": [ "/images/smelth.png" ] } ] }, "message_summary": "I'm launching my own cryptocu", "message": "I'm launching my own cryptocurrency soon and many thanks for you to look at and talk about" } } }
هنا قائمة الرسائل كائن وليس صفيف. علاوة على ذلك ، هناك بيانات المستخدم هنا ، والتي تم ترتيبها بشكل غير مريح كما هو الحال في نقطة نهاية
/profile
. وهذه مفاجأة - تحتوي خاصية
timestamp
على عدد الثواني منذ بداية 1970.
إذا اضطررت لرسم رسم تخطيطي لهندسة ذلك النظام غير الملائم الذي تحدثنا عنه للتو ، فسيبدو كما هو موضح في الشكل أدناه. يتم استخدام اللون الأحمر لتلك الأجزاء من هذه الدائرة التي تتوافق مع البيانات سيئة الإعداد لمزيد من العمل.
مخطط النظامنحن ، في هذه الظروف ، قد لا نسعى جاهدين لإصلاح بنية هذا النظام. يمكنك ببساطة تحميل البيانات من واجهات برمجة التطبيقات الثلاثة هذه واستخدام هذه البيانات في التطبيق. على سبيل المثال ، إذا كنت بحاجة إلى عرض اسم المستخدم الكامل على الصفحة ، فسنحتاج إلى دمج خصائص
Christian_Name
و
Surname
.
هنا أود أن أدلي بملاحظة واحدة بشأن الأسماء. تعد فكرة تقسيم اسم الشخص بالكامل إلى اسم شخصي ولقب من سمات الدول الغربية. إذا كنت تطور شيئًا مصممًا للاستخدام الدولي ، فحاول اعتبار الاسم الكامل للشخص كسلسلة غير قابلة للتجزئة ، ولا تضع أي افتراضات حول كيفية تقسيم هذه السلسلة إلى أجزاء أصغر من أجل استخدام ما حدث في الأماكن التي تحتاج إلى الإيجاز أو تريد مناشدة المستخدم بأسلوب غير رسمي.
العودة إلى هياكل البيانات غير الكاملة لدينا. يتم التعبير عن المشكلة الواضحة الأولى التي يمكن رؤيتها هنا في الحاجة إلى دمج البيانات المتباينة في رمز واجهة المستخدم. وهو يتألف من حقيقة أننا قد نحتاج إلى تكرار هذا الإجراء في عدة أماكن. إذا كنت بحاجة إلى القيام بذلك من حين لآخر ، فالمشكلة ليست خطيرة للغاية ، ولكن إذا كنت بحاجة إلى ذلك كثيرًا ، فهي أسوأ بكثير. ونتيجة لذلك ، هناك ظواهر غير مرغوب فيها ناجمة عن عدم تطابق كيفية ترتيب البيانات المستلمة من الخادم وكيفية استخدامها في التطبيق.
المشكلة الثانية هي تعقيد الكود المستخدم لتكوين واجهة المستخدم. أعتقد أن مثل هذا الرمز يجب أن يكون ، أولاً ، بسيطًا قدر الإمكان ، وثانيًا - واضحًا قدر الإمكان. كلما زادت تحويلات البيانات الداخلية التي يتعين عليك إجراؤها على العميل ، كلما زاد تعقيدها ورمزها المعقد هو المكان الذي تختفي فيه الأخطاء عادةً.
المسألة الثالثة تتعلق بأنواع البيانات. من مقتطفات الشفرة أعلاه ، يمكنك أن ترى ، على سبيل المثال ، معرفات الرسائل عبارة عن سلاسل ، ومعرفات المستخدم هي أرقام. من وجهة نظر فنية ، كل شيء على ما يرام ، لكن مثل هذه الأشياء يمكن أن تربك المبرمج. أيضا ، انظر إلى عرض التواريخ! ولكن ماذا عن الفوضى في جزء البيانات التي تتعلق بصورة الملف الشخصي؟ بعد كل شيء ، كل ما نحتاجه هو عنوان URL يؤدي إلى الملف المقابل ، وليس شيئًا سنضطر من خلاله إلى إنشاء عنوان URL هذا بأنفسنا ، خوضًا في غابة هياكل البيانات المتداخلة.
إذا قمنا بمعالجة هذه البيانات ، وتمريرها إلى رمز واجهة المستخدم ، فعند تحليل الوحدات ، لا يمكننا أن نفهم على الفور بالضبط ما نتعامل معه هناك. تحويل بنية البيانات الداخلية ونوعها عند العمل معهم يخلق عبئا إضافيا على المبرمج. ولكن من دون كل هذه الصعوبات ، من الممكن القيام بذلك.
في الواقع ، كخيار ، سيكون من الممكن تنفيذ نظام نوع ثابت لحل هذه المشكلة ، ولكن الكتابة الصارمة غير قادرة ، فقط من خلال وجودها ، على جعل الشفرة السيئة جيدة.
الآن بعد أن رأيت خطورة المشكلة التي نواجهها ، دعنا نتحدث عن طرق حلها.
الحل رقم 1: تغيير واجهة برمجة تطبيقات الخادم
إذا لم يتم إملاء الجهاز غير المناسب لواجهة برمجة التطبيقات الحالية بسبب بعض الأسباب المهمة ، فلا شيء يمنعك من إنشاء إصدار جديد يناسب احتياجات المشروع وتحديد موقع هذا الإصدار الجديد ، على سبيل المثال ، في
/v2
. ربما يمكن أن يسمى هذا النهج أنجح حل للمشاكل المذكورة أعلاه. يتم عرض مخطط مثل هذا النظام في الشكل أدناه ، ويتم تمييز بنية البيانات التي تتوافق تمامًا مع احتياجات العميل باللون الأخضر.
واجهة برمجة تطبيقات الخادم الجديدة التي تنتج بالضبط ما يحتاجه العميل من النظاممن خلال البدء في تطوير مشروع جديد ، تترك واجهة برمجة التطبيقات الخاصة به الكثير مما هو مرغوب فيه ، أنا مهتم دائمًا بإمكانية تنفيذ النهج الموصوف للتو. ومع ذلك ، في بعض الأحيان ، يكون لجهاز واجهة برمجة التطبيقات ، وإن كان غير ملائم ، بعض الأهداف المهمة ، أو ببساطة تغيير واجهة برمجة تطبيقات الخادم غير ممكن. في هذه الحالة ، ألجأ إلى النهج التالي.
الحل رقم 2: نمط BFF
هذا هو نمط BFF (
Backend-For-the-Frontend ) القديم الجيد. باستخدام هذا النمط ، يمكنك التجريد من نقاط نهاية REST العالمية المعقدة وإعطاء الواجهة الأمامية ما تحتاجه بالضبط. هنا تمثيل تخطيطي لمثل هذا الحل.
تطبيق نمط BFFمعنى وجود طبقة BFF هو تلبية احتياجات الواجهة الأمامية. ربما سيستخدم نقاط نهاية REST إضافية أو خدمات GraphQL أو مقابس الويب أو أي شيء آخر. هدفها الرئيسي هو القيام بكل ما هو ممكن من أجل راحة جانب العميل من التطبيق.
بنيتي المفضلة هي NodeJS BFF ، باستخدام مطوري الواجهة الأمامية الذين يمكنهم القيام بما يحتاجون إليه ، وإنشاء واجهات برمجة تطبيقات رائعة لتطبيقات العملاء التي يقومون بتطويرها. من الناحية المثالية ، يوجد الرمز المطابق في نفس المستودع مثل رمز الواجهة الأمامية نفسها ، مما يبسط مشاركة الرمز ، على سبيل المثال ، لفحص البيانات المرسلة ، سواء على العميل أو على الخادم.
بالإضافة إلى ذلك ، هذا يعني أن المهام التي تتطلب تغييرات في جزء العميل من التطبيق وواجهة برمجة تطبيقات الخادم الخاصة به يتم تنفيذها في مستودع واحد. تافه ، كما يقولون ، ولكن لطيف.
ومع ذلك ، قد لا يتم استخدام BFF دائمًا. وهذه الحقيقة تقودنا إلى حل آخر لمشكلة الاستخدام المريح لواجهات برمجة تطبيقات الخادم السيئة.
الحل رقم 3: نمط BIF
يستخدم نمط BIF (الواجهة الخلفية في الواجهة الأمامية) نفس المنطق الذي يمكن تطبيقه باستخدام BFF (الجمع بين واجهات برمجة التطبيقات المتعددة وتنظيف البيانات) ، ولكن هذا المنطق ينتقل إلى جانب العميل. في الواقع ، هذه الفكرة ليست جديدة ، كان يمكن رؤيتها قبل عشرين عامًا ، ولكن مثل هذا النهج يمكن أن يساعد في العمل مع واجهات برمجة تطبيقات الخادم سيئة التنظيم ، لهذا السبب نتحدث عنها. إليك كيف تبدو.
تطبيق نمط BIF▍ ما هو BIF؟
كما يتبين من القسم السابق ، فإن BIF هو نمط ، أي نهج لفهم الكود وتنظيمه. لا يؤدي استخدامه إلى الحاجة إلى إزالة أي منطق من المشروع. إنه يفصل فقط منطق نوع (تعديل هياكل البيانات) عن منطق نوع آخر (تشكيل واجهة المستخدم). وهذا مشابه لفكرة "فصل المسؤوليات" التي يسمعها الجميع.
هنا أود أن أشير إلى أنه على الرغم من أن هذا لا يمكن تسميته كارثة ، فقد كان علي في كثير من الأحيان أن أرى تطبيقات BIF الأمية. لذلك ، يبدو لي أن العديد سيهتمون بسماع قصة حول كيفية تنفيذ هذا النمط بشكل صحيح.
يجب اعتبار رمز BIF رمزًا يمكن أخذه ونقله مرة واحدة إلى خادم Node.js ، وبعد ذلك سيعمل كل شيء بالطريقة نفسها كما كان من قبل. أو حتى نقلها إلى حزمة NPM خاصة ، والتي سيتم استخدامها في العديد من المشاريع الأمامية في إطار شركة واحدة ، وهو ببساطة مدهش.
تذكر أننا ناقشنا أعلاه المشاكل الرئيسية التي تنشأ عند العمل مع واجهة برمجة تطبيقات الخادم الفاشلة. من بينها استدعاء متكرر للغاية لواجهة برمجة التطبيقات وحقيقة أن البيانات التي يتم إرجاعها من قبلهم لا تلبي احتياجات الواجهة الأمامية.
سنقوم بتقسيم الحل لكل من هذه المشاكل إلى كتل منفصلة من التعليمات البرمجية ، سيتم وضع كل منها في ملفها الخاص. ونتيجة لذلك ، ستتكون طبقة BIF لجزء العميل من التطبيق من ملفين. بالإضافة إلى ذلك ، سيتم إرفاق ملف اختبار لهم.
calls الجمع بين مكالمات API
لا يعد إجراء الكثير من المكالمات إلى واجهات برمجة تطبيقات الخادم في رمز العميل مشكلة خطيرة. ومع ذلك ، أود تلخيصها ، لجعل من الممكن تلبية "طلب" واحد (من رمز التطبيق إلى طبقة BIF) ، والحصول على ما هو مطلوب بالضبط في الاستجابة.
بالطبع ، في حالتنا ، لا مفر من إجراء ثلاثة طلبات HTTP إلى الخادم ، ولكن التطبيق لا يحتاج إلى معرفة ذلك.
يتم تمثيل API لطبقة BIF الخاصة بي كوظائف. لذلك ، عندما يحتاج التطبيق إلى بعض البيانات حول المستخدم ، فإنه
getUser()
وظيفة
getUser()
، والتي ستعيد هذه البيانات إليها. إليك ما تبدو عليه هذه الوظيفة:
import parseUserData from './parseUserData'; import fetchJson from './fetchJson'; export const getUser = async () => { const auth = await fetchJson('/auth'); const [ profile, notifications ] = await Promise.all([ fetchJson(`/profile/${auth.userId}`, auth.jwt), fetchJson(`/notifications/${auth.userId}`, auth.jwt), ]); return parseUserData(auth, profile, notifications); };
هنا ، أولاً ، يتم تقديم طلب إلى خدمة المصادقة للحصول على رمز مميز يمكن استخدامه لتفويض المستخدم (لن نتحدث عن آليات المصادقة هنا ، ولكن هدفنا الرئيسي هو BIF).
بعد تلقي الرمز المميز ، يمكنك تنفيذ طلبين في نفس الوقت يستلمان بيانات ملف تعريف المستخدم ومعلومات حول الإشعارات غير المقروءة.
بالمناسبة ، انظر إلى مدى جمال المظهر غير
async/await
Promise.all
عند العمل معه باستخدام
Promise.all
واستخدام المهمة التدميرية.
لذا ، كانت هذه هي الخطوة الأولى ، وهنا استخلصنا من حقيقة أن الوصول إلى الخادم يتضمن ثلاثة طلبات. ومع ذلك ، لم يتم الانتهاء من القضية بعد. على وجه التحديد ، انتبه إلى الاستدعاء
parseUserData()
، والتي ، كما يمكنك أن
parseUserData()
من اسمها ،
parseUserData()
البيانات المستلمة من الخادم. لنتحدث عنها.
cleaning تنظيف البيانات
أريد أن أقدم على الفور توصية واحدة ، والتي أعتقد أنها يمكن أن تؤثر بشكل خطير على مشروع لم يكن في السابق يحتوي على طبقة BIF ، على وجه الخصوص ، مشروع جديد. حاول ألا تفكر في ما تحصل عليه من الخادم لفترة من الوقت. بدلاً من ذلك ، ركز على البيانات التي يحتاجها تطبيقك.
بالإضافة إلى ذلك ، من الأفضل عدم محاولة ، عند تصميم التطبيق ، مراعاة احتياجاته المستقبلية المحتملة ، على سبيل المثال ، المتعلقة بعام 2021. فقط حاول أن تجعل التطبيق يعمل تمامًا كما يجب اليوم. والحقيقة هي أن الحماس المفرط للتخطيط ومحاولات التنبؤ بالمستقبل هو السبب الرئيسي للتعقيد غير المبرر لمشاريع البرمجيات.
لذا ، عد إلى أعمالنا. الآن نحن نعلم كيف تبدو البيانات المستلمة من واجهات برمجة التطبيقات للخادم الثلاثة ، ونعلم كيف يجب أن تتحول بعد التحليل.
يبدو أن هذه إحدى الحالات النادرة التي يكون فيها استخدام TDD منطقيًا حقًا. لذلك ،
parseUserData()
اختبارًا طويلًا كبيرًا
parseUserData()
:
import parseUserData from './parseUserData'; it('should parse the data', () => { const authApiData = { userId: 1234, jwt: 'the jwt', }; const profileApiData = { Profiles: [ { id: 1234, Christian_Name: 'David', Surname: 'Gilbertson', Photographs: [ { Size: 'Medium', URLS: [ '/images/david.png', ], }, ], Last_Login: '2018-01-01' }, ], }; const notificationsApiData = { data: { 'msg-1234': { timestamp: '1529739612', user: { Christian_Name: 'Alice', Surname: 'Guthbertson', Enhanced: 'True', Photographs: [ { Size: 'Medium', URLS: [ '/images/alice.png' ] } ] }, message_summary: 'Hey I like your hair, it re', message: 'Hey I like your hair, it really goes nice with your eyes' }, 'msg-5678': { timestamp: '1529731234', user: { Christian_Name: 'Bob', Surname: 'Smelthsen', }, message_summary: 'I\'m launching my own cryptocu', message: 'I\'m launching my own cryptocurrency soon and many thanks for you to look at and talk about' }, }, }; const parsedData = parseUserData(authApiData, profileApiData, notificationsApiData); expect(parsedData).toEqual({ jwt: 'the jwt', id: '1234', name: 'David Gilbertson', photoUrl: '/images/david.png', notifications: [ { id: 'msg-1234', dateTime: expect.any(Date), name: 'Alice Guthbertson', premiumMember: true, photoUrl: '/images/alice.png', message: 'Hey I like your hair, it really goes nice with your eyes' }, { id: 'msg-5678', dateTime: expect.any(Date), name: 'Bob Smelthsen', premiumMember: false, photoUrl: '/images/placeholder.jpg', message: 'I\'m launching my own cryptocurrency soon and many thanks for you to look at and talk about' }, ], }); });
وإليك رمز الوظيفة نفسها:
const getPhotoFromProfile = profile => { try { return profile.Photographs[0].URLS[0]; } catch (err) { return '/images/placeholder.jpg'; // } }; const getFullNameFromProfile = profile => `${profile.Christian_Name} ${profile.Surname}`; export default function parseUserData(authApiData, profileApiData, notificationsApiData) { const profile = profileApiData.Profiles[0]; const result = { jwt: authApiData.jwt, id: authApiData.userId.toString(), // ID name: getFullNameFromProfile(profile), photoUrl: getPhotoFromProfile(profile), notifications: [], // , }; Object.entries(notificationsApiData.data).forEach(([id, notification]) => { result.notifications.push({ id, dateTime: new Date(Number(notification.timestamp) * 1000), // , , , Unix, name: getFullNameFromProfile(notification.user), photoUrl: getPhotoFromProfile(notification.user), message: notification.message, premiumMember: notification.user.Enhanced === 'True', }) }); return result; }
أود أن أشير إلى أنه عندما يكون من الممكن جمع مائتي سطر من التعليمات البرمجية المسؤولة عن تعديل البيانات المتناثرة قبل ذلك طوال التطبيق ، فإنه يسبب شعورًا رائعًا في مكان واحد. الآن كل هذا في ملف واحد ، تتم كتابة اختبارات الوحدة لهذا الرمز ، ويتم تزويد جميع اللحظات الغامضة بالتعليقات.
قلت في وقت سابق أن BFF هو نهجي المفضل لدمج البيانات ومسحها ، ولكن هناك مجال واحد يتفوق فيه BIF على BFF. على وجه التحديد ، قد تتضمن البيانات المستلمة من الخادم كائنات JavaScript لا تدعم JSON ، مثل كائنات
Date
أو
Map
(ربما تكون هذه إحدى ميزات JavaScript الأقل استخدامًا). على سبيل المثال ، في حالتنا ، يجب علينا تحويل التاريخ الوارد من الخادم (معبرًا عنه بالثواني ، وليس بالمللي ثانية) إلى كائن JS من نوع
Date
.
الملخص
إذا كنت تعتقد أن مشروعك يحتوي على شيء مشترك مع المشروع الذي درسنا فيه مشاكل واجهات برمجة التطبيقات غير الناجحة ، فقم بتحليل الكود الخاص به عن طريق طرح نفسك على الأسئلة التالية حول استخدام البيانات من الخادم على العميل:
- هل يجب عليك الجمع بين الخصائص التي لا يتم استخدامها بشكل منفصل مطلقًا (على سبيل المثال ، الاسم الأول والأخير للمستخدم)؟
- هل يجب أن يعمل كود JS مع أسماء الخصائص التي تم تشكيلها بطريقة غير مقبولة في JS (شيء مثل PascalCase)؟
- ما هي أنواع بيانات المعرفات المختلفة؟ ربما تكون هذه أحيانًا سلاسل ، وأحيانًا أرقام؟
- كيف يتم عرض التواريخ في مشروعك؟ ربما في بعض الأحيان هذه كائنات
Date
JS جاهزة للاستخدام في الواجهة ، وأحيانًا أرقام ، أو حتى سلاسل؟ - هل يتعين عليك غالبًا التحقق من الخصائص لوجودها ، أو التحقق مما إذا كان الكيان صفيفًا قبل البدء في تعداد عناصر هذا الكيان لتشكيل جزء من واجهة المستخدم على أساسه؟ هل يمكن أن يكون هذا الكيان لن يكون مصفوفة ، حتى لو كانت فارغة؟
- هل يجب عليك فرز المصفوفات أو تصفيتها عند تكوين الواجهة ، والتي ، من الناحية المثالية ، يجب فرزها وتصفيتها بشكل صحيح؟
- إذا اتضح أنه عند التحقق من الخصائص لوجودها ، لا توجد خصائص مطلوبة ، هل يجب عليك التبديل إلى استخدام بعض القيم الافتراضية (على سبيل المثال ، استخدم الصورة القياسية عندما لا توجد صورة مستخدم في البيانات المستلمة من الخادم)؟
- هل يتم تسمية العقارات بشكل موحد؟ هل من الممكن أن يكون للكيان نفسه أسماء مختلفة ، والتي قد تكون ناجمة عن الاستخدام المشترك لواجهات برمجة تطبيقات الخادم "القديمة" و "الجديدة" نسبيًا؟
- هل يجب عليك ، جنبًا إلى جنب مع البيانات المفيدة ، نقل بيانات لم يتم استخدامها مطلقًا ، وذلك فقط لأنها تأتي من واجهة برمجة تطبيقات الخادم؟ هل تتداخل هذه البيانات غير المستخدمة مع التصحيح؟
إذا كان بإمكانك الإجابة بشكل إيجابي على سؤال أو سؤالين من هذه القائمة ، فربما لا يجب عليك إصلاح شيء يعمل بالفعل بشكل صحيح.
ومع ذلك ، إذا كنت تقرأ هذه الأسئلة ، فابحث في كل واحد منهم عن مشكلات مشروعك ، إذا كان جهاز الكود الخاص بك معقدًا بدون داعٍ بسبب كل هذا ، إذا كان من الصعب إدراكه واختباره ، إذا كان يحتوي على أخطاء يصعب اكتشافها ، فراجع نمط BIF.
في النهاية ، أود أن أقول أنه عند إدخال طبقة BIF في التطبيقات الحالية ، تصبح الأمور أسهل نظرًا لأنه يمكن القيام بذلك على مراحل ، في خطوات صغيرة. لنفترض أن الإصدار الأول من الوظيفة لإعداد البيانات ،
parseData()
عليها
parseData()
، يمكنه ببساطة ، دون تغييرات ، إرجاع ما يأتي إلى
parseData()
. ثم يمكنك نقل المنطق تدريجيًا من الرمز المسؤول عن إنشاء واجهة المستخدم لهذه الوظيفة.
أعزائي القراء! هل واجهت مشاكل يقترح مؤلف هذه المادة استخدام نمط BIF لها؟