عندما تبدأ مهنتك في البرمجة ، قد يبدو البحث في التعليمات البرمجية المصدر للمكتبات والأطر المفتوحة أمرًا مخيفًا إلى حد ما. في هذه المقالة ، يشارك كارل منجازي تجربته في كيفية التغلب على خوفه وبدأ في استخدام شفرة المصدر لاكتساب المعرفة وتطوير المهارات. كما أنه يستخدم Redux لإظهار كيف "يوزع" المكتبة.هل تتذكر عندما غمر نفسك في شفرة مكتبة أو إطار عمل تستخدمه غالبًا؟ في حياتي ، جاءت هذه اللحظة في وظيفتي الأولى كمطور متقدم قبل ثلاثة أعوام.
لقد أعدنا فقط كتابة إطار عمل قديم تم استخدامه لإنشاء دورات تدريبية تفاعلية. في بداية عمل إعادة الكتابة ، نظرنا في بعض الحلول الجاهزة ، بما في ذلك Mithril و Inferno و Angular و React و Aurelia و Vue و Polymer. منذ أن كنت لا أزال شابًا بادوان (الذي كان قد تحول لتوه من الصحافة إلى تطوير الويب) ، كنت خائفًا للغاية من تعقيد كل إطار وعدم وجود فهم لكيفية عملهم.
بدأ التفاهم يأتي عندما بدأت في استكشاف إطار Mithril بعناية. منذ ذلك الحين ، تم تعزيز معرفتي بـ JavaScript - والبرمجة بشكل عام - بفضل الساعات التي أمضيتها في الحفر داخل المكتبات الداخلية ، والتي كنت أستخدمها يوميًا في العمل وفي مشاريعي الخاصة. سأخبرك في هذه المقالة كيف يمكنك استخدام مكتبتك المفضلة كبرنامج تعليمي
بدأت في قراءة الكود المصدري باستخدام وظيفة الحروف الفائقة من Mithrilإيجابيات تحليل شفرة المصدر
واحدة من المزايا الرئيسية لتحليل التعليمات البرمجية المصدر هو أنه يمكنك تعلم الكثير. عندما بدأت في تحليل كود Mithril ، كانت لدي فكرة سيئة للغاية عما كان عليه DOM الظاهري. عندما انتهيت ، كنت أعرف بالفعل أن DOM الظاهري هو تقنية تتضمن إنشاء شجرة كائنات تصف واجهة المستخدم. يمكن بعد ذلك تحويل هذه الشجرة إلى عناصر DOM باستخدام واجهة برمجة تطبيقات DOM مثل document.createElement. للتحديث ، يتم إنشاء شجرة جديدة تصف الحالة المستقبلية للواجهة ثم تقارن بالإصدار السابق من هذه الشجرة.
قرأت عن هذا في العديد من المقالات والكتيبات ، ولكن الأكثر إفادة هو مراقبة كل هذا أثناء العمل على طلبنا. تعلمت أيضًا أن أطرح الأسئلة الصحيحة عند مقارنة الأطر. بدلاً من مقارنة التصنيفات ، على سبيل المثال ، يمكنك طرح السؤال "كيف تؤثر الطريقة التي يعمل بها هذا الإطار مع التغييرات على أداء وراحة المستخدم النهائي؟"
ميزة إضافية أخرى هي تطوير فهم بنية تطبيقية جيدة. على الرغم من أن معظم المشاريع مفتوحة المصدر تكون بشكل عام متشابهة إلى حد ما في هيكلها إلى مستودعاتها ، إلا أنها لا تزال لديها اختلافات. هيكل Mithril مسطح للغاية وإذا كنت على دراية جيدة بواجهة برمجة التطبيقات ، فيمكنك تقديم افتراضات واقعية للغاية حول الكود الموجود في مجلدات التقديم والموجه وطلب. هيكل React ، من ناحية أخرى ، يعكس هيكله الجديد. فصل المطورون الوحدة النمطية المسؤولة عن تحديث واجهة المستخدم (تفاعل المصلح) عن الوحدة المسؤولة عن تقديم عناصر DOM (react-dom).
إحدى ميزات هذا الفصل للمطورين هي أنه يمكنهم كتابة
عارضين خاصين بهم باستخدام خطافات في أداة التوفيق التفاعلية. يحتوي Parcel ، وهو منشئ الوحدة الذي درسته مؤخرًا ، على مجلد حزم ، تمامًا مثل React. الوحدة النمطية للمفاتيح تسمى parcel-bundler ، وهي تحتوي على كود مسؤول عن إنشاء التجميعات ، وتشغيل خادم تحديث الوحدة النمطية (خادم الوحدة النمطية الساخنة) ، وأداة سطر الأوامر.
يؤدي تحليل الشفرة المصدرية قريباً إلى قراءة مواصفات JavaScript.ميزة أخرى ، والتي كانت مفاجأة كبيرة بالنسبة لي ، هي أنه يسهل عليك قراءة مواصفات JavaScript الرسمية. في المرة الأولى التي التفت إليها عندما كنت أحاول معرفة الفرق بين رمي الخطأ ورمي خطأ جديد (المفسد -
لا شيء ). لقد طرحت هذا السؤال لأن Mithril استخدم رمي خطأ في تنفيذ وظيفة m وتساءلت لماذا كان أفضل من رمي خطأ جديد. ثم علمت أيضًا أن المشغلين && و ||
ليس بالضرورة إرجاع القيم المنطقية ، لقد وجدت
القواعد التي بواسطتها عامل المقارنة غير الصارمة == "يحل" القيم
والسبب في إرجاع Object.prototype.toString.call ({}) "[كائن كائن]".
كيفية تحليل شفرة المصدر
هناك العديد من الطرق لتحليل شفرة المصدر. يبدو أن أسهل طريقة بالنسبة لي هي: تحديد طريقة من مكتبتك ووصف ما يحدث عند الاتصال بها. لا يستحق وصف كل خطوة ، فأنت تحتاج فقط إلى محاولة فهم مبادئها العامة وهيكلها.
في الآونة الأخيرة ، قمت بتحليل ReactDOM.render بهذه الطريقة وتعلمت الكثير عن React Fiber وبعض الصعوبات في تنفيذه. لحسن الحظ ، React تحظى بشعبية كبيرة ووجود عدد كبير من المقالات حول نفس الموضوع من المطورين الآخرين عجلت هذه العملية.
هذا التعمق في الكود قد عرّفني أيضًا على مفهوم
الجدولة التعاونية وطريقة
window.requestIdleCallback والمثال المباشر
لقائمة مرتبطة (React يعالج التحديثات عن طريق إرسالها إلى قائمة الانتظار ، وهي قائمة ذات صلة بالتحديثات المرتبطة). في هذه العملية ، سيكون من الجيد إنشاء تطبيق بسيط باستخدام المكتبة. هذا يجعل تصحيح الأخطاء أسهل نظرًا لأنك لست مضطرًا للتعامل مع تتبع مكدس المكتبات الأخرى.
إذا لم أقوم بمراجعة مفصلة ، فسوف أقوم بفتح مجلد node_modules في المشروع الذي أعمل عليه أو ألق نظرة على GitHub. أقوم بذلك دائمًا عندما واجهت خللًا أو ميزة شيقة. عند قراءة الكود على جيثب ، تأكد من أن هذا هو أحدث إصدار. يمكن رؤية رمز أحدث إصدار من خلال النقر على الزر لتغيير الفروع واختيار "العلامات". التغييرات في المكتبات وأطر العمل جارية ، لذا من غير المحتمل أن ترغب في تحليل شيء قد لا يكون في الإصدار التالي.
هناك نسخة أكثر سطحية من شفرة مصدر التعلم هي ما أسميه "نظرة سريعة". بطريقة ما قمت بتثبيت Express.js ، فتحت مجلد node_modules وذهبت إلى التبعيات. إذا لم تعطيني README شرحًا مرضيًا ، فاقرأ المصدر. هذا قادني إلى اكتشافات مثيرة للاهتمام:
- يستخدم Express وحدتين لدمج الكائنات ، وتشغيل هذه الوحدات يختلف تمامًا. تضيف واصفات الدمج فقط الخصائص الموجودة في الكائن المصدر ، وتضيف أيضًا خصائص غير قابلة للتعداد ، في حين أن utils-merge تنتقل عن الخصائص التي تم تعدادها للكائن وسلسلة النموذج الأولي بأكمله. يستخدم واصفات الدمج Object.getOwnPropertyNames () و Object.getOwnPropertyDescriptor () ، ويستخدم utils-merge لـ..in؛
- توفر وحدة setprototypeof خيارًا عبر النظام الأساسي لتحديد النموذج الأولي للكائن الذي تم إنشاؤه (تم إنشاء مثيل له) ؛
- Escape-html هي وحدة هروب سلسلة من 78 سطرًا ، وبعد ذلك يمكن إدراج المحتوى في HTML ؛
على الرغم من أن هذه الاكتشافات ليست مفيدة على الفور ، إلا أن الفهم العام لتبعيات مكتبتك أو إطارك مفيد للغاية.
أدوات تصحيح الأخطاء للمتصفح هي أفضل أصدقاء لك عند تصحيح الأخطاء في الواجهة الأمامية. من بين أشياء أخرى ، يسمحون لك بإيقاف البرنامج في أي وقت والتحقق في الوقت نفسه من حالته أو تخطي الوظيفة أو الدخول إليها أو الخروج منها. في الرمز المصغر ، هذا غير ممكن - لهذا السبب قمت بفك الشفرة ووضعها في الملف المقابل في مجلد node_modules.
استخدم المصحح كتطبيق مفيد. قم بعمل افتراض ، ثم اختبره.دراسة حالة: ربط وظيفة في Redux
React-Redux هي مكتبة لإدارة حالة تطبيقات React. عندما أعمل مع مكتبات شائعة مثل هذه ، أبدأ بالبحث عن مقالات حول استخدامها. في إعداد هذا المثال ، راجعت هذا
المقال . هذه نقطة إضافية في تعلم الكود المصدري - إنها تقودك إلى مقالات إعلامية مثل هذه التي تعمل على تحسين تفكيرك وفهمك.
Connect عبارة عن وظيفة رد فعل رد فعل تربط مكون رد الفعل ومخزن رد تطبيق ما. كيف؟ وفقًا
للوثائق ، تقوم بما يلي:
"... تقوم بإرجاع فئة مكون جديدة ذات صلة ، وهي عبارة عن غلاف للمكون الذي تم تمريره إليها."
بعد قراءة هذا ، أطرح الأسئلة التالية:
- هل أعرف الأنماط أو المفاهيم التي تُرجع فيها الدالات معلمات الإدخال مغلفة بوظائف إضافية؟
- إذا كان الأمر كذلك ، كيف يمكنني استخدام هذا بناءً على الوصف من الوثائق؟
عادة ما تكون الخطوة التالية هي إنشاء تطبيق بدائي باستخدام وظيفة الاتصال. ومع ذلك ، في هذه الحالة ، استخدمت تطبيقًا جديدًا على React ، والذي عملنا عليه ، لأنني أردت أن أفهم الاتصال في سياق تطبيق من المحتمل أن يصل إلى الإنتاج قريبًا.
يبدو المكون الذي ركزت عليه يشبه هذا:
class MarketContainer extends Component {
هذا هو مكون الحاوية الذي يعمل بمثابة غلاف للمكونات الأربعة ذات الصلة الأصغر. أحد أول الأشياء التي تجدها في
الملف الذي يربط الصادرات هو التعليق "connect هو الواجهة لـ connectAdvanced". في هذه المرحلة ، يمكننا تعلم شيء ما: لدينا الفرصة لمراقبة نمط "الواجهة" أثناء العمل. في نهاية الملف ، نرى اتصال تصدير مكالمة إلى وظيفة createConnect. المعلمات الخاصة به هي مجموعة من القيم الافتراضية التي يتم إتلافها كما يلي:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {})
ولدينا لحظة تثقيفية واحدة: تصدير الوظيفة المدعوة وتدمير وسيطات الوظيفة افتراضيًا. إعادة الهيكلة مفيدة لنا لأن الرمز يمكن كتابته على النحو التالي:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory })
نتيجةً لذلك ، سنحصل على خطأ - TypeError Uncaught: لا يمكن إتلاف خاصية "connectHOC" الخاصة بـ "غير معرّفة" أو "خالية". قد يحدث هذا لأن الوظيفة لا تحتوي على قيم وسيطة افتراضية.
ملاحظة: لفهم أفضل لإعادة هيكلة الحجج ، يمكنك قراءة مقال ديفيد والش . قد تبدو بعض النقاط تافهة ، بناءً على معرفتك للغة - ثم يمكنك التركيز على تلك النقاط التي لا تعرفها.وظيفة createConnect نفسها لا تفعل شيئًا. تقوم فقط بإرجاع وظيفة الاتصال التي استخدمتها هنا:
export default connect(null, mapDispatchToProps)(MarketContainer)
يستغرق الأمر أربع وسيطات اختيارية ويمر الثلاثة منها من خلال وظيفة
المطابقة ، مما يساعد على تحديد سلوكهم استنادًا إلى الوسائط التي يتم تمريرها ، وكذلك نوعها. اتضح أنه نظرًا لأن الوسيطة الثانية التي تم تمريرها للمطابقة هي إحدى الوظائف الثلاث التي تم استيرادها إلى الاتصال ، فأنا بحاجة إلى اختيار أين أذهب بعد ذلك.
هناك أيضًا شيء يمكن تعلمه من
وظيفة البروكسي المستخدمة في التفاف الوسيطة الأولى في الاتصال ، إذا كانت هذه الوسيطات هي وظائف ؛ من الأداة المساعدة
isPlainObject المستخدمة للتحقق من الكائنات العادية أو من وحدة
التحذير ، والتي توضح كيف يمكنك إنشاء مصحح أخطاء يكسر جميع الأخطاء. بعد وظيفة المطابقة ، ننتقل إلى connectHOC ، وهي الوظيفة التي تأخذ مكون التفاعل الخاص بنا وتربطها مع الإعادة. هناك استدعاء دالة آخر يُرجع
wrapWithConnect - وهي وظيفة تقوم فعلياً بمعالجة ربط المكون إلى المستودع.
بالنظر إلى تطبيق connectHOC ، يمكنني تخمين سبب إخفاء تفاصيل تطبيق الاتصال. هذا هو أساس رد فعل رد الفعل ويحتوي على منطق لا ينبغي الوصول إليه من خلال الاتصال. حتى لو تناولنا هذا الأمر ، ثم فيما بعد ، إذا كنا بحاجة إلى الحفر بشكل أعمق ، فسنحصل بالفعل على المادة المصدر مع شرح مفصل للرمز.
لخص
تعلم شفرة المصدر معقدة للغاية في البداية. ولكن ، مثل كل شيء آخر ، يصبح الأمر أسهل بمرور الوقت. مهمته ليست فهم كل شيء ، ولكن لإبراز شيء مفيد لنفسه - فهم مشترك ومعرفة جديدة. من المهم جدًا توخي الحذر أثناء العملية برمتها والتعمق في التفاصيل.
على سبيل المثال ، لقد وجدت أن وظيفة isPlainObject مهمة لأنها تستخدم هذا إذا كانت (typeof obj! == 'object' || obj === null) خاطئة للتأكد من أن الوسيطة التي تم تمريرها هي كائن بسيط. عندما قرأت هذا الرمز لأول مرة ، فكرت ، لم لا تستخدم فقط Object.prototype.toString.call (opts)! == '[كائن كائن]' ، مما يقلل من الكود ويفصل الكائنات عن أنواعها الفرعية مثل التاريخ. ولكن بالفعل في السطر التالي ، من الواضح أنه حتى إذا فجأة (فجأة!) ، فإن مطورًا يستخدم connect يعرض كائن Date ، على سبيل المثال ، التحقق من Object.getPrototypeOf (obj) === null يمكنه معالجة هذا.
نقطة أخرى غير متوقعة في isPlainObject في هذا المكان:
while (Object.getPrototypeOf(baseProto) !== null) { baseProto = Object.getPrototypeOf(baseProto) }
أدى العثور على إجابة على Google إلى
هذا الموضوع على StackOverflow ، وإلى
هذا التعليق على GitHub's Redux ، والذي يشرح كيف يتعامل هذا الرمز مع المواقف ، على سبيل المثال ، يتم نقل كائن من iFrame.
-
قررت أولا لترجمة المقال. وسأكون ممتنا للتوضيح والمشورة والتوصيات