يُعَدّ تطبيقنا الذي عملنا عليه في المقال السابق وحدةً متراصةً، لذلك يجب تقسيمه إلى مكوّنات يمكن وصفها وإدارتها قبل تمكننا من جعل تطبيقنا يفعل شيئًا ما، إذ لا تحتوي مكتبة React على قواعد صارمة لتحديد ما يُعَدّ مكوّنًا Component، فالأمر متروك لك، وسنعرض في هذا المقال طريقةً معقولةً لتقسيم تطبيقنا إلى مكونات.
- المتطلبات الأساسية: الإلمام بأساسيات لغات HTML وCSS وجافاسكربت JavaScript ومعرفة استخدام سطر الأوامر أو الطرفية.
- الهدف: إظهار طريقة لتقسيم تطبيق قائمة المهام إلى مكوّنات.
تحديد المكون الأول
قد يبدو تحديد أحد المكوّنات أمرًا صعبًا إلى حين حصولك على بعض الخبرة العملية، ولكن الأمور المهمة هي:
- إذا كان أحد الأشياء جزءًا واضحًا من تطبيقك، فيُحتمَل أن يكون مكوّنًا.
- إذا أُعيد استخدام أحد الأشياء كثيرًا، فيُحتمَل أن يكون مكوّنًا.
تُعَدّ النقطة الثانية ذات قيمة خاصة، إذ يتيح إنشاء مكوّن من عناصر واجهة المستخدِم تعديلَ شيفرتك البرمجية في مكان واحد ورؤية تلك التعديلات في جميع الأماكن التي يُستخدَم فيها هذا المكوّن، إذ ليس عليك تقسيم كل شيء إلى مكوِنات مباشرةً، ولنستخدِم النقطة الثانية لإنشاء مكوّن من أكثر الأجزاء المُعاد استخدامها والأكثر أهميةً في واجهة المستخدِم وهو عنصر قائمة المهام.
إنشاء المكون <Todo />
يجب علينا إنشاء ملف جديد للمكوّن قبل إنشائه، ويجب إنشاء مجلد لهذه المكونات، إذ تنشئ الأوامر التالية المجلد components وملفًا ضمنه يُسمَّى Todo.js، ولكن تأكّد من وجودك في جذر تطبيقك قبل تشغيل هذه الأوامر:
mkdir src/components touch src/components/Todo.js
إنّ ملف Todo.js الجديد فارغ حاليًا، لذا افتحه واكتب فيه السطر الأول التالي:
import React from "react";
سننشئ مكوّنًا يسمّى Todo
، لذلك يمكننا إضافة شيفرتنا إلى الملف Todo.js على النحو التالي، إذ سنعرِّف الدالة Todo()
ونصدّرها على السطر نفسه كما يلي:
export default function Todo() { return ( ); }
كل شيء جيد حتى الآن، لكن يجب أن يعيد المكون شيئًا ما، لذا ارجع إلى الملف src/App.js، وانسخ أول عنصر <li>
من القائمة غير المرتبة، والصقه في الملف Todo.js بحيث يصبح كما يلي:
export default function Todo() { return ( <li className="todo stack-small"> <div className="c-cb"> <input id="todo-0" type="checkbox" defaultChecked={true} /> <label className="todo-label" htmlFor="todo-0"> Eat </label> </div> <div className="btn-group"> <button type="button" className="btn"> Edit <span className="visually-hidden">Eat</span> </button> <button type="button" className="btn btn__danger"> Delete <span className="visually-hidden">Eat</span> </button> </div> </li> ); }
ملاحظة: يجب أن تعيد المكونات شيئًا ما دائمًا، فإذا حاولت لاحقًا تصيير Render مكون لا يعيد شيئًا، فستعرِض React خطأً في متصفحك.
أصبح المكوّن Todo
مكتملًا حاليًا، وبالتالي يمكننا استخدامه، والآن أضف السطر التالي في الملف App.js بالقرب من أعلى الملف لاستيراد المكوّن Todo
:
import Todo from "./components/Todo";
يمكنك مع استيراد هذا المكون وضع استدعاءات المكوّن <Todo />
مكان جميع عناصر <li>
في الملف App.js، ويجب أن يصبح العنصر <ul>
كما يلي:
<ul role="list" className="todo-list stack-large stack-exception" aria-labelledby="list-heading" > <Todo /> <Todo /> <Todo /> </ul>
إذا نظرت إلى متصفحك، فستلاحظ شيئًا مؤسفًا، إذ تُكرِّر قائمتك المهمة الأولى ثلاث مرات كما يلي:
لا نريد الأكل فقط، إذ لدينا مهام أخرى يجب تنفيذها، وسنوضّح فيما يلي كيفية إجراء استدعاءات لمكوّنات مختلفة تصيّر محتوًى فريدًا.
إنشاء مكون <Todo /> فريد
تُعَدّ المكونات مهمةً لأنها تتيح إعادة استخدام أجزاء من واجهة المستخدِم، والإشارة إلى مكان ما ليكون مصدرًا لواجهة المستخدِم تلك، ولكن تكمن المشكلة في أننا لا نريد إعادة استخدام جميع المكونات، وإنما نريد إعادة استخدام معظم الأجزاء، وتعديل أجزاء صغيرة، لذا يجب استخدام الخاصيات Props.
الخاصية name
إذا أردنا تتبّع أسماء المهام التي نريد إكمالها، فيجب علينا التأكد من أنّ كل مكوّن <Todo />
يصيِّر اسمًا فريدًا، لذا امنح كل مكوّن <Todo />
في الملف App.js خاصية الاسم name
، ولنستخدم أسماء المهام التي كانت لدينا سابقًا كما يلي:
<Todo name="Eat" /> <Todo name="Sleep" /> <Todo name="Repeat" />
إذا حدّثتَ متصفحك، فسترى الشيء السابق نفسه بالضبط، إذ أعطينا المكوّن <Todo />
بعض الخاصيات، لكننا لم نستخدِمها بعد، فلنَعُد الآن إلى الملف Todo.js ونصلح كل شيء.
عدّل أولًا تعريف الدالة Todo()
بحيث تأخذ الخاصيات props
على أساس معامِل، كما يمكنك تطبيق التابع console.log()
على الخاصيات props
كما فعلنا سابقًا إذا أردت التحقق من استلام المكوّن للخاصيات استلامًا صحيحًا، ثم يمكنك وضع خاصية الاسم name
مكان تكرارات المهمة Eat
، وتذكّر استخدام الأقواس المعقوصة لحقن قيمة متغير في تعابير JSX، إذ يجب أن تكون الدالة Todo()
بعد ذلك كما يلي:
export default function Todo(props) { return ( <li className="todo stack-small"> <div className="c-cb"> <input id="todo-0" type="checkbox" defaultChecked={true} /> <label className="todo-label" htmlFor="todo-0"> {props.name} </label> </div> <div className="btn-group"> <button type="button" className="btn"> Edit <span className="visually-hidden">{props.name}</span> </button> <button type="button" className="btn btn__danger"> Delete <span className="visually-hidden">{props.name}</span> </button> </div> </li> ); }
يجب أن يعرض متصفحك الآن ثلاث مهام فريدة، ولكن لا تزال جميع هذه المهام مُحدَّدةً افتراضيًا.
الخاصية completed
حُدِّدت المهمة Eat
فقط في القائمة الثابتة الأصلية، إذ نريد إعادة استخدام معظم واجهة المستخدِم التي تشكل المكوِّن <Todo />
مع تعديل شيء واحد من خلال منح كل استدعاء للمكوّن <Todo />
في الملف App.js الخاصية completed
الجديدة، كما يجب أن يكون للخاصية completed
التابعة للمكوّن الأول الذي اسمه Eat
القيمة true
، وللمكونات الأخرى القيمة false
كما يلي:
<Todo name="Eat" completed={true} /> <Todo name="Sleep" completed={false} /> <Todo name="Repeat" completed={false} />
يجب علينا العودة إلى الملف Todo.js لاستخدام هذه الخاصيات، لذا عدّل السمة defaultChecked
للعنصر <input />
بحيث تساوي قيمتها الخاصية completed
، ثم يكون عنصر <input />
الخاص بالمكون Todo
على النحو التالي:
<input id="todo-0" type="checkbox" defaultChecked={props.completed} />
حدّث متصفحك لإظهار تحديد المكوِّن Eat
فقط كما يلي:
إذا عدّلت كل خاصيات completed
الخاصة بالمكوّن <Todo />
، فسيحدِّد متصفحك أو يلغي تحديد مربعات الاختيار المكافئة والمُصيَّرة وفقًا لذلك.
الخاصية id
يعطي المكوّن <Todo />
لكل مهمة السمة id
بالقيمة todo-0
، وهذا خطأ في HTML لأن سمات id
يجب أن تكون فريدةً، إذ تستخدِمها لغات جافاسكربت وCSS وغيرها بوصفها معرّفات فريدةً لأجزاء المستند، وبالتالي يجب أن نعطي المكون الخاصية id
التي تأخذ قيمةً فريدةً لكل مكوّن Todo
.
إذًا لنمنح كل نسخة من المكوِّن <Todo />
معرّفًا باستخدام التنسيق todo-i
، إذ تزيد قيمة i
بمقدار واحد في كل مرة كما يلي:
<Todo name="Eat" completed={true} id="todo-0" /> <Todo name="Sleep" completed={false} id="todo-1" /> <Todo name="Repeat" completed={false} id="todo-2" />
عُد الآن إلى الملف Todo.js واستفد من الخاصية id
، إذ يجب تعديل قيمة السمة id
للعنصر <input />
وقيمة السمة htmlFor
الخاصة بالعنصر label
كما يلي:
<div className="c-cb"> <input id={props.id} type="checkbox" defaultChecked={props.completed} /> <label className="todo-label" htmlFor={props.id}> {props.name} </label> </div>
كل شيء جيد حتى الآن، ولكن تُعَدّ شيفرتنا مكرَّرةً، فالأسطر الثلاثة التي تصيّر المكوّن <Todo />
متطابقة تقريبًا مع اختلاف واحد فقط هو قيمة كل خاصية، كما يمكننا تنظيف شيفرتنا باستخدام إحدى ميزات جافاسكربت الأساسية وهي التكرار Iteration، ولكن يجب أولًا إعادة التفكير في المهام لاستخدام هذه الميزة.
بيانات المهام
تحتوي كل مهمة من مهامنا حاليًا على ثلاثة أجزاء من المعلومات، وهي اسمها، وما إذا كانت مُحدَّدة، ومعرّفها الفريد، كما تُترجَم هذه البيانات إلى كائن Object، وبما أنه لدينا أكثر من مهمة، فسنستخدِم مصفوفةً من الكائنات لتمثيل هذه البيانات، لذا أنشئ ثابتًا const
جديدًا بعد تعليمة الاستيراد الأخيرة في الملف src/index.js وقبل التابع ReactDOM.render()
كما يلي:
const DATA = [ { id: "todo-0", name: "Eat", completed: true }, { id: "todo-1", name: "Sleep", completed: false }, { id: "todo-2", name: "Repeat", completed: false } ];
سنمرِّر بعد ذلك الثابت DATA
إلى المكوّن <App />
بوصفه خاصيةً تُسمَّى tasks
، إذ يجب أن يكون السطر الأخير من الملف src/index.js كما يلي:
ReactDOM.render(<App tasks={DATA} />, document.getElementById("root"));
أصبحت هذه المصفوفة متاحةً الآن للمكون App
بالصورة props.tasks
، كما يمكنك استخدام التابع console.log()
للتحقق من ذلك.
اقتباسملاحظة: ليس لأسماء الثوابت ذات الأحرف الكبيرة
ALL_CAPS
معنًى خاصًا في جافاسكربت، وإنما هي اتفاقية بين المطوِّرين تخبرهم بعدم تغيّر هذه البيانات بعد تعريفها أبدًا.
التصيير مع التكرار
يمكننا تصيير مصفوفة الكائنات من خلال تحويل كل منها إلى المكون <Todo />
، إذ تمنحنا لغة جافاسكربت تابع مصفوفة لتحويل البيانات إلى شيء آخر، وهو Array.prototype.map()
، لذا أنشئ ثابتًا const
جديدًا يسمى taskList
قبل تعليمة return
الخاصة بالدالة App()
، واستخدِم التابع map()
لتحويله، ولنحوّل مجموعة مهامنا إلى شيء بسيط يتمثّل باسم name
كل مهمة كما يلي:
const taskList = props.tasks?.map(task => task.name);
لنحاول وضع الثابت taskList
مكان جميع أبناء العنصر <ul>
كما يلي:
<ul role="list" className="todo-list stack-large stack-exception" aria-labelledby="list-heading" > {taskList} </ul>
يمنحنا ذلك طريقةً لإظهار جميع المكوّنات مرةً أخرى، إذ يصيّر المتصفح حاليًا اسم كل مهمة بوصفه نصًا دون بنية معينة، كما ينقصنا حاليًا بنية HTML مثل عنصر <li>
ومربعات الاختيار والأزرار.
يمكننا إصلاح ذلك من خلال إعادة المكوّن <Todo />
من التابع map()
، وتذكَّر أنّ صيغة JSX تسمح لنا بخلط بنى جافاسكربت مع اللغات التوصيفية Markup، فلنجرب ما يلي بدلًا مما لدينا حاليًا:
const taskList = props.tasks.map(task => <Todo />);
انظر مرةً أخرى إلى تطبيقك، إذ تبدو مهامنا الآن كما كانت سابقًا، لكنها تفتقد إلى أسماء المهام نفسها، وتذكَّر أنّ كل مهمة نطبّق عليها التابع map()
لها الخاصيات id
وname
وchecked
، والتي نريد تمريرها إلى المكوّن <Todo />
، وبالتالي سنحصل على الشيفرة التالية:
const taskList = props.tasks.map(task => ( <Todo id={task.id} name={task.name} completed={task.completed} /> ));
يبدو التطبيق الآن كما كان سابقًا، ولكن أصبحت شيفرتنا أقل تكرارًا.
خاصيات key الفريدة
يجب أن تتعقّب React المهام لتصييرها بصورة صحيحة بعد أن صيّرت هذه المهام من مصفوفة، إذ تستخدِم React التخمين لتتبع الأشياء، ولكن يمكننا مساعدتها عن طريق تمرير الخاصية key
لمكونات <Todo />
، إذ تُعَدّ key
خاصيةً خاصةً تديرها React، ولا يمكنك استخدام الكلمة key
لأيّ غرض آخر، وبما أنّ الخاصية key
يجب أن تكون فريدةً، فسنعيد استخدام خاصية id
الخاصة بكل كائن مهمة على أساس مفتاح له، لذا عدِّل الثابت taskList
كما يلي:
const taskList = props.tasks.map(task => ( <Todo id={task.id} name={task.name} completed={task.completed} key={task.id} /> ) );
يجب عليك دائمًا تمرير مفتاح فريد لأيّ شيء تُصيّره مع التكرار، ولن يتغير شيء واضح في متصفحك، ولكن إذا لم تستخدِم مفاتيح فريدةً، فستعطي React تحذيرات في الطرفية console ويمكن أن يتصرف تطبيقك بطريقة غريبة.
تقسيم أجزاء التطبيق المتبقية إلى مكونات
يمكننا الآن تحويل باقي التطبيق إلى مكونات بعد فرزِنا المكوّن الأكثر أهميةً، وتذكَّر أنّ المكونات هي إما أجزاء واضحة من واجهة المستخدِم، أو أجزاء معاد استخدامها من واجهة المستخدِم، أو كليهما، كما يمكننا إنشاء مكونين آخرين هما:
-
<Form/>
-
<FilterButton/>
بما أننا نعلم بحاجتنا لهذين المكوِنين، فيمكننا تجميع أوامر إنشاء الملفات في أمر واحد في الطرفية، لذا شغّل الأمر التالي في طرفيتك، مع الانتباه إلى أنك في المجلد الجذر لتطبيقك:
touch src/components/Form.js src/components/FilterButton.js
المكون <Form/>
افتح الملف components/Form.js ونفِّذ ما يلي:
-
استورد مكتبة
React
في أعلى الملف كما فعلنا في الملف Todo.js. -
أنشئ المكوِّن
Form()
الجديد باستخدام بنيةTodo()
الأساسية نفسها، ثم صدِّر هذا المكوِّن. -
انسخ وسوم
<form>
وما يوجد بينها من الملف App.js، والصقها ضمن تعليمةreturn
الخاصة بالمكوِّنForm()
. -
صدّر المكوِّن
Form
في نهاية الملف.
يجب أن يكون الملف Form.js الآن كما يلي:
import React from "react"; function Form(props) { return ( <form> <h2 className="label-wrapper"> <label htmlFor="new-todo-input" className="label__lg"> What needs to be done? </label> </h2> <input type="text" id="new-todo-input" className="input input__lg" name="text" autoComplete="off" /> <button type="submit" className="btn btn__primary btn__lg"> Add </button> </form> ); } export default Form;
المكون <FilterButton/>
كرِّر الأمور نفسها التي نفّذتها لإنشاء الملف Form.js على الملف FilterButton.js، ولكن استدعِ المكوِّن FilterButton()
وانسخ جزء HTML للزر الأول الموجود ضمن العنصر <div>
ذو الصنف filters
من الملف App.js في تعليمة return
، إذ يجب أن يكون الملف الآن كما يلي:
import React from "react"; function FilterButton(props) { return ( <button type="button" className="btn toggle-btn" aria-pressed="true"> <span className="visually-hidden">Show </span> <span>all </span> <span className="visually-hidden"> tasks</span> </button> ); } export default FilterButton;
اقتباسملاحظة: لاحظ أننا ارتكبنا الخطأ نفسه هنا الذي ارتكبناه لأول مرة مع المكوِّن
<Todo />
، إذ سيبقى كل زر كما هو، ولكن سنصلح هذا المكوِّن لاحقًا.
استيراد جميع المكونات
أضف بعض تعليمات الاستيراد import
في الجزء العلوي من الملف App.js لاستيراد هذه المكوّنات الجديدة، ثم عدّل تعليمة return
الخاصة بالمكوِّن App()
لتصيير المكونات، إذ يجب أن يكون الملف App.js كما يلي:
import React from "react"; import Form from "./components/Form"; import FilterButton from "./components/FilterButton"; import Todo from "./components/Todo"; function App(props) { const taskList = props.tasks.map(task => ( <Todo id={task.id} name={task.name} completed={task.completed} key={task.id} /> ) ); return ( <div className="todoapp stack-large"> <h1>TodoMatic</h1> <Form /> <div className="filters btn-group stack-exception"> <FilterButton /> <FilterButton /> <FilterButton /> </div> <h2 id="list-heading">3 tasks remaining</h2> <ul role="list" className="todo-list stack-large stack-exception" aria-labelledby="list-heading" > {taskList} </ul> </div> ); } export default App;
نكون بذلك جاهزين تقريبًا للتعامل مع التفاعل في تطبيق React الخاص بنا.
الخلاصة
تعمّقنا في كيفية تقسيم التطبيق إلى مكوّنات وتصييرها بكفاءة، إذ سننتقل الآن إلى إلقاء نظرة على كيفية التعامل مع الأحداث في React وإضافة التفاعل.
ترجمة -وبتصرُّف- للمقال Componentizing our React app.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.