التفكير على طريقة React

برأينا (React) هو الطريقة الاساسية لبناء تطبيقات ويب كبيرة وسريعة بإستخدام الجافاسكريبت (Javascript) لقد ساعدنا في التوسع في التطبيقات بشكل جيد بفيسبوك (Facebook) وانستجرام (Instagram).

واحدة من المزايا العظيمة ل (React) هي كيف انه يجعلك تفكر في التطبيقات اثناء بناءها. في هذه الصفحة سنقوم معاَ بعملية التفكير لبناء جدول بيانات منتج قابل للبحث به بإستخدام (React)

ابدأ بنموذج التطبيق

تصور أننا نملك واجهة برمجة تطبيقات (API JSON) جاهزة ونموذج من المصمم. هذا النموذج يشبه الآتي:

Mockup

واجهة برمجة التطبيقات ترسل بعض البيانات كالآتي:

[
  {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
  {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
  {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
];

الخطوة الأولي: قم بتقسيم واجهة المستخدم (UI) لتسلسل هرمي لمكونات الواجهة (Component)

اول شئ يجب عليك فعله هو رسم مستطيلات حول كل مكون (component) ومكون فرعي(subcomponent) بالنموذج واعطاء كل منهم أسم. إذا كنت تعمل مع مصمم تحدث معه فعلي الأرجح هو فعل ذلك! فأسماء طبقات الفوتوشوب (Photoshop layers) من الممكن أن تصلح كأسماء لمكوناتك (React component)!

ولكن كيف تعرف ما يجب أن تحدده كمكون؟ فقط إستخدم نفس الأساليب أثناء أخذ قرار بإنشاء دالة (Function) او (Object) جديدة، كمثال علي أسلوب هو (مبدأ المسئولية الأحادية) single responsibility principle وهي أن المكون (component) بشكل مثالي يجب أن يكون مسئول عن فعل شئ واحد فقط وإذا بدأ في التنامي يجب تقسيمه لمكونات فرعية (subcomponent) أصغر.

حيث أننا غالباَ ما نعرض نموذج البيانات (JSON data model) للمستخدم ستجد أنه إذا كان هذا النموذج مبني بشكل جيد فإنه سيتطابق مع واجهة المستخدم (UI) خاصتك بشكل رائع وبالتالي مع بناء مكوناتك (component structure) وهذا لأن واجهة المستخدم (UI) ونموذج البيانات (data models) يميلان الي التقيد بنفس (الشكل البنائي للمعلومات information architecture) وهو مايعني ان العمل علي تقسيم واجهة المستخدم (UI) لمكونات (components) غالباَ مايكون بسيط فقط قم بتقسيمها الي مكونات (component) تمثل تحديداَ جزء واحد من نموذج البيانات (data model) خاصتك.

Component diagram

ستري هنا أن لدينا خمس مكونات (components) في تطبيقنا البسيط ولقد قمنا بالكتابة بخط عريض كل مكون ومايمثله من بيانات.

  1. FilterableProductTable (باللون البرتقالي): يحتوي المثال بكامله
  2. SearchBar (باللون الأزرق): يستقبل ما يدخله المستخدم (user input)
  3. ProductTable (باللون الأخضر): يعرض و ينقح (filter) مجموعة البيانات (data collection) طبقاَ لما ادخله المستخدم (user input)
  4. ProductCategoryRow (باللون الفيروزي): يعرض عنوان (heading) لكل فئة (category)
  5. ProductRow (باللون الأحمر): يعرض صف لكل منتج (product)

إذا نظرت الي المكون ProductTable ستري أن عنوان الجدول (المحتوي علي الأسم والسعر) ليس مكون (component) منفصل بذاته هي مسألة تفضيل وهناك حجة لكلا الطريقتين. لهذا المثال نحن تركناه كجزء من المكون ProductTableلأنه جزء من تصيير (rendering) مجموعة البيانات (data collection) والتي هي من مسئولية المكون ProductTable، ومع ذلك إذا تنامي عنوان الجدول بشكل معقد (كمثال إضافة إمكانية الفرز (sorting)) سيكون بالتأكيد من المنطقي جعله مكون منفصل ProductTableHeader.

والأن بعد أن حددنا المكونات (components) في نموذج التصميم خاصتنا، لنقم برتيبهم في تسلسل هرمي وهذا سهل، المكونات التي تظهر بداخل مكونات أخري في النموذج يجب أن تكون إبن (child) داخل التسلسل:

  • FilterableProductTable

    • SearchBar
    • ProductTable

      • ProductCategoryRow
      • ProductRow

الخطوة الثانية: بناء نسخة ثابتة (static version) بال (React)

شاهد النتيجة التفكير بطريقة (React): الخطوة الثانية علي CodePen.

والأن ونحن لدينا التسلسل الهرمي للمكونات، حان وقت تنفيذ التطبيق. الطريقة السهلة هي بناء نسخة تستخدم نموذج البيانات (data model) لتصيير (renders) واجهة المستخدم (UI) ولكن بدون إمكانية للتفاعل مع التطبيق، من الأفضل فصل هذه العمليات لأن بناء نسخة ثابتة تحتاج للكثير من الكتابة بدون تفكير وإضافة التفاعلية (interactivity) تحتاج للكثير من التفكير والقليل من الكتابة، سنري لماذا.

لبناء نسخة ثابتة من التطبيق ستحتاج الي بناء مكونات تستخدم مكونات أخري وترسل لها البيانات بإستخدام الخاصيات (props)، وهي طريقة لتمرير البيانات من المكون الأب الي المكون الأبن، إذا كنت علي معرفة بمبدأ الحالة (state) لا تستخدم الحالة (state) ابداَ لبناء نسخة ثابتة، الحالة (state) تستخدم لغرض التفاعلية فقط، وهو أن البيانات تتغير بأستمرار وحيث أن هذه النسخة ثابتة أنت لا تحتاجها.

يمكنك البناء من أعلي لأسفل او من أسفل لأعلي، وذلك أنه يمكنك البدء ببناء المكونات في أعلي التسلسل الهرمي (كمثال أبدأ ب FilterableProductTable) او بمكون في اسفله (ProductRow)، في الأمثلة البسيطة من الأسهل عادة البدء من أعلي لأسفل، وفي المشاريع الأكبر من الأسهل البدء من أسفل الي أعلي مع كتابة إختبارات (tests) وأنت تبني.

بنهاية هذه الخطوة، سيكون لديك مكتبة من المكونات القابلة لإعادة الإستخدام (reusable) التي تقوم بتصيير نموذج البيانات، المكونات ستحتوي فقط علي دالات (render()) حيث أن هذه نسخة ثابتة من تطبيقك، المكون بأعلي التسلسل الهرمي (FilterableProductTable) سيحصل علي نموذج البيانات كخاصية (prop)، إذا قمت بعمل تغيير في نموذج البيانات وقمت بإستدعاء الدالة (ReactDOM.render()) مرة أخري فإن واجهة المستخدم سيتم تحديثها، من السهل رؤية كيف يتم تحديث واجهة المستخدم واين تحدث التغييرات حيث أنه لايوجد شئ معقد يحدث، طريقة تدفق البيانات في اتجاه واحد (one-way data flow) لل (React) وتدعي أيضاَ (one-way binding) تحافظ علي كل شئ وسريع وكوحدة (modular) واحدة.

ببساطة انتقل الي الوثائق React docs إذا كنت بحاجة للمساعدة لتنفيذ هذه الخطوة

نبذه بسيطة: الخاصية (Props) مقابل الحالة (State)

هناك نوعان من نماذج البيانات في (React): الحالة (state) و الخاصية (props)، من المهم فهم الأختلاف بين الأثنين، تصفح الوثائق the official React docs إذا لم تكن متأكد من الأختلاف. تصفح أيضا الأسئلة الأكثر تكراراً: ما الفرق بين الحالة state والخاصيّات props؟

الخطوة الثالثة: تحديد الحد الأدني (ولكن المكتمل) الممثل لحالة (state) واجهة المستخدم

لجعل واجهة المستخدم تفاعلية ستحتاج للقدرة على عمل تغييرات فى نموذج البيانات الخاص بتطبيقك، (React) تجعل هذا سهلاً بإستخدام الحالة (state).

لبناء تطبيقك بشكل صحيح، ستحتاج أولاً للتفكير فى الحد الأدنى من الحالة القابلة للتغيير (mutable state) التي سيحتاجها التطبيق، المفتاح هنا هو لا تكرر نفسك (DRY: Don’t Repeat Yourself)، حدد الحد الأدني قدر الإمكان الممثل للحالة التي يحتاجها تطبيقك ثم قم بحساب كل شئ أخر عند الحاجة، علي سبيل المثال إذا كنت تقوم ببناء تطبيق قائمة (TODO) فقط احتفظ ب (array) للعناصر ولا تحتفظ بمتغير حالة (state variable) منفصل للعدد، بدلاً من ذلك عندما تريد أن تصير (render) عدد العناصر (TODO) ببساطة إحسب طول ال (array) لعناصر (TODO).

فكر في كل أجزاء البيانات في مثالنا, لدينا:

  • القائمة الأصلية للمنتجات
  • كلمة البحث التي أدخلها المستخدم
  • حالة ال (checkbox)
  • قائمة المنتجات المنقحة (filtered)

دعنا نحدد أي منهم تصلح كحالة، ببساطة إسأل ثلاث أسئلة عن كل جزء من البيانات:

  1. هل يتم تمريرها من مكون أب كخاصية (props)؟ إذا كان نعم، فمن المحتمل هي ليست حالة
  2. هل هى ثابتة لاتتغير مع مرور الزمن؟ إذا كان نعم، فمن المحتمل هى ليست حالة.
  3. هل يمكنك حسابها بناء على حالة او خاصية (props) أخرى فى هذا المكون؟ إذا كان نعم، فمن المحتمل هي ليست حالة.

القائمة الأصلية للمنتجات يتم تمريرها كخاصية (props) إذاَ فهى ليست حالة، كلمة البحث وال (checkbox) يتضح أنهم حالة حيث أنهم يتغيرون مع الزمن ولا يمكن حسابهم من أى شئ، وأخيراَ القائمة المنقحة للمنتجات ليست حالة لأنه يمكن حسابها من دمج القائمة الأصلية للمنتجات مع كلمة البحث وحالة ال (checkbox).

وأخيراً الحالة هي:

  • كلمة البحث التى أدخلها المستخدم
  • حالة ال (checkbox)

الخطوة الرابعة: حدد أين يجب أن تكون الحالة

شاهد النتيجةالتفكير بطريقة (React): الخطوة الرابعة على CodePen.

حسناَ لقد حددنا ماهو الحد الأدني للحالة، التالى سنحدد ماهو المكون المسئول عن تحويل (mutates) أو يملك (owns) هذه الحالة.

تذكر: فى (React) تتدفق البيانات في إتجاه واحد (one-way flow) لأسفل التسلسل الهرمى للمكونات، قد لا تكون واضحة فى هذه اللحظة أى مكون يجب أن يملك أية حالة وهذه غالباَ أكثر الأجزاء تحدياَ للفهم على القادمون الجدد لذلك إتبع هذه الخطوات للكشف:

لكل جزء من الحالة فى تطبيقك:

  • حدد كل مكون يقوم بتصيير (render) شئ ما بناء على هذه الحالة.
  • إبحث عن مكون مشترك ليملك هذه الحالة (مكون واحد أعلى فى التسلسل الهرمى من كل المكونات التى تحتاج لهذه الحالة).
  • إما المكون المشترك أو مكون أخر أعلى فى التسلسل الهرمى يجب أن يملك هذه الحالة.
  • إذا لم تجد مكون يصلح لأن يملك هذه الحالة، إنشئ واحداَ جديداَ فقط ليملك هذه الحالة وأضفه فى مكان ما فى التسلسل الهرمى أعلى المكون المشترك.

لنتبع تلك الإستراتيجية فى تطبيقنا:

  • المكون ProductTable يحتاج لتنقيح قائمة المنتجات بناء على الحالة والمكون SearchBar يحتاج لإظهار كلمة البحث و حالة ال (checkbox).
  • المكون المشترك المالك للحالة هو FilterableProductTable.
  • نظرياَ من المنطقى أن تتواجد كلمة البحث وقيمة ال (checkbox) فى المكون FilterableProductTable.

رائع، لقد قررنا أن الحالة ستكون فى المكون FilterableProductTable، أولاَ أضف (instance property) this.state = {filterText: '', inStockOnly: false} لل constructor للمكون FilterableProductTable لتكون الحالة الإبتدائية للتطبيق ثم مرر الحالتين filterText و inStockOnly للمكونين ProductTable و SearchBar كخاصيات (props) وفى النهاية، إستخدم هذه الخاصيات (props) لتنقيح صفوف المنتجات فى المكون ProductTable ووضع القيم لل (form fields) فى المكون SearchBar.

يمكنك الأن رؤية كيف سيتفاعل تطبيقك: ضع قيمة الحالة filterText الى كرة "ball" وقم بعمل تحديث (refresh) للتطبيق، سترى أن جدول المنتجات تم تحديثه بشكل صحيح.

الخطوة الخامسة: أضف التدفق العكسى للبيانات

شاهد النتيجة التفكير بطريقة React على CodePen.

الى الأن، قمنا ببناء التطبيق للتصيير (render) بشكل صحيح بإرسال الخاصيات (props) والحالة (state) لأسفل التسلسل الهرمى، الان حان الوقت لدعم تدفق البيانات فى الإتجاه الأخر: المكونات الخاصة ب (form) فى أدنى التسلسل الهرمى تحتاج لتحديث الحالة للمكون FilterableProductTable.

تجعل (React) تدفق البيانات هذا صريحاَ ليسهل فهم كيف يعمل برنامجك، ولكنك ستحتاج للكتابة أكثر من الطريقة التقليدية لنقل البيانات فى الإتجاهين (two-way data binding).

إذا حاولت الكتابة او الضغط على ال (checkbox) بالإصدار الحالى للتطبيق سترى إن React سيتجاهل ذلك، وذلك مقصود حيث أننا قمنا بوضع قيمة الخاصية (value) لل (input) لتكون دائماَ مساوية للحالة التى تم تمريرها من المكون FilterableProductTable.

لنفكر بما نريد أن يحدث، نريد التأكد أينما قام المستخدم بتغيير ال (form) يتم تحديث الحالة لإظهار ما أدخله المستخدم وحيث أن المكونات يجب أن تغير الحالة الخاصة بها فقط، المكون FilterableProductTable سيمرر الدالة (callback) للمكون SearchBar والتى سيتم إستدعائها أينما وجب تحديث الحالة، يمكننا إستخدام الحدث (onChange event) على ال (inputs) لنعرف ذلك، الدالة (callback) التى تم تمريرها بواسطة المكون FilterableProductTable تقوم بإستدعاء setState() ويتم تحديث التطبيق.

بالرغم من أن ذلك يبدو معقداَ فحقيقة وبإستخدام عدد قليل من أسطر الكود وبشكل صريح نرى كيف تتدفق البيانات خلال التطبيق.

وهذا هو كل شئ

أرجو أن تكون قد وصلتك الفكرة عن كيفية التفكير فى بناء المكونات والتطبيقات بإستخدام (React)، بينما قد تكون الكتابة أكثر مما أنت معتاد عليه تذكر أن الكود من المهم أن يكون مقروء أكثر من كتابته ووحدة (modular) الكود هذه سهلة القراءة لأقصى الحدود، عند البدء فى بناء مكتبة كبيرة من المكونات ستقدر هذا الوضوح و النمطية (modularity) وبإعادة إستخدام هذا الكود سيتقلص عدد الأسطر :)