غالباً ستريد عرض عدة مكونات متشابهة من مجموعة بيانات. يمكنك أن تستخدم دوال المصفوفات الخاصة بلغة JavaScript لتعديل مصفوفة من البيانات. في هذه الصفحة، سوف تستخدم الدالتين الآتيتين: filter() وmap() مع مكتبة React لتصفّي وتحول مصفوفتك المكونة من مجموعة بيانات إلى مصفوفة من المكونات.

You will learn

  • كيفية تصيير مكونات من مصوفة باستخدام دالة JavaScript map().
  • كيفية تصيير مكونات محددة فقط باستخدام دالة JavaScript filter().
  • متى ولماذا تستخدم مفاتيح React (key prop)

تصيير البيانات من مصفوفات.

افترض بأنَّ لديك قائمة بالمحتوى.

<ul>
<li>كريولا كاثرين جونسون: عالمة رياضيات</li>
<li>ماريو خوسيه مولينا-باسكيل هنريكيز: كيميائي</li>
<li>محمد عبد السلام: فيزيائي</li>
<li>بيرسي لافون جوليان: كيميائي</li>
<li>سوبراهمانيان تشاندراسيخار: عالم فيزياء الفلك</li>
</ul>

الاختلاف الوحيد بين تلك القوائم هو محتوياتها، أو بالأحرى بياناتها. غالباً ستحتاج إظهار نماذج متعددة من المكون نفسه باستخدام بيانات مختلفة عندما تبني الواجهات: ابتداءً من قوائم من التعليقات إلى معارض صور الملفات الشخصية. في هذه الحالات، يمكنك تخزين تلك البيانات في كائنات ومصفوفات JavaScript واستخدام دالات مثل map() and filter() لتصيير قوائم من البيانات من تلك المصفوفات والكائنات.

هذا مثال صغير حول كيفية توليد قائمة عناصر من مصفوفة:

  1. انقل البيانات إلى مصفوفة:
const people = [
'كريولا كاثرين جونسون: عالمة رياضيات',
'ماريو خوسيه مولينا-باسكيل هنريكيز: كيميائي',
'محمد عبد السلام: فيزيائي',
'بيرسي لافون جوليان: كيميائي',
'سوبراهمانيان تشاندراسيخار: عالم فيزياء الفلك'
];
  1. استخدم الدالة Map على أفراد القائمة people وحولها إلى مصفوفة من عناصر JSX، listItems:
const listItems = people.map(person => <li>{person}</li>);
  1. قم بإرجاع القائمة listItems من مكونك مغلفة بوسم <ul>:
return <ul>{listItems}</ul>;

هذه هي النتيجة:

const people = [
  'كريولا كاثرين جونسون: عالمة رياضيات',
  'ماريو خوسيه مولينا-باسكيل هنريكيز: كيميائي',
  'محمد عبد السلام: فيزيائي',
  'بيرسي لافون جوليان: كيميائي',
  'سوبراهمانيان تشاندراسيخار: عالم فيزياء الفلك'
];

export default function List() {
  const listItems = people.map(person =>
    <li>{person}</li>
  );
  return <ul>{listItems}</ul>;
}

لاحظ أن sandbox في الأعلى يظهر خطأً في console:

Console
Warning: Each child in a list should have a unique “key” prop.

سوف تتعلم كيف تصلح هذا الخطأ لاحقًا في هذه الصفحة. قبل أن نصل إلى ذلك، دعنا نضيف بعض الهيكلية لبياناتك.

تصفية مصفوفات من العناصر

هذه البيانات يمكن أن تكون مهيكلةً أكثر من ذلك.

const people = [{
id: 0,
name: 'كريولا كاثرين جونسون',
profession: 'عالمة رياضيات',
}, {
id: 1,
name: 'ماريو خوسيه مولينا-باسكيل هنريكيز',
profession: 'كيميائي',
}, {
id: 2,
name: 'محمد عبد السلام',
profession: 'فيزيائي',
}, {
id: 3,
name: 'بيرسي لافون جوليان',
profession: 'كيميائي',
}, {
id: 4,
name: 'سوبراهمانيان تشاندراسيخار',
profession: 'عالم فيزياء الفلك',
}];

لنقل أنك تريد طريقة لإظهار الناس الذين مهنتهم هي 'كيميائي' فقط. تستطيع استخدام دالة JavaScript filter() لتقوم بإرجاع هؤلاء الناس فقط. هذه دالة تستقبل مصفوفة من العناصر، تمررهم عبر دالة اختبار (وهي دالة تقوم بإرجاع true أو false)، وتقوم بإرجاع مصفوفة جديدة من هؤلاء العناصر فقط الذين اجتازوا الاختبار (أي قامت بإرجاع true)

أنت تريد العناصر التي مهنتها profession هي كيميائي فقط. الدالة الاختبارية لهذا الأمر تبدو كالآتي: (person) => person.profession === 'chemist'. هنا نجد كيفية وضعهم معاً:

  1. أنشئ مصفوفة جديدة تحوي الناس التي مهنتها كيميائي، عن طريق استدعاء الدالة filter() على المصفوفة people وتصفيتهم حسب مهنتهم person.profession === 'كيميائي':
const chemists = people.filter(person =>
person.profession === 'كيميائي'
);
  1. الآن قم بعمل map على المصفوفة chemists:
const listItems = chemists.map(person =>
<li>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}:</b>
{' ' + person.profession + ' '}
مشهور بـ {person.accomplishment}
</p>
</li>
);
  1. أخيراً، قم بإرجاع return القائمة listItems من مكونك:
return <ul>{listItems}</ul>;
import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const chemists = people.filter(person =>
    person.profession === 'chemist'
  );
  const listItems = chemists.map(person =>
    <li>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        مشهور بـ {person.accomplishment}
      </p>
    </li>
  );
  return <ul>{listItems}</ul>;
}

Pitfall

الدوال السهمية Arrow function تعيد قيمة التعبير الموجود على اليمين بعد =>، لذلك لا حاجة لعبارة return:

const listItems = chemists.map(person =>
<li>...</li> // إرجاع ضمني
);

على أيّ حال، عليك كتابة return بشكل صريح عندما يتبع => خاصتك بقوس منحني {

const listItems = chemists.map(person => { // Curly brace
return <li>...</li>;
});

الدوال السهمية التي تتضمن => { تمتلك “block body”. تسمح لك بكتابة أكثر من سطرٍ واحدٍ من الكود، لكن عليك كتابة العبارة return بنفسك. إذا نسيتها، لا شيء يرجع من الدالة!

أبقي العناصر مرتبة مع key

لاحظ أنّ كل sandboxes في الأعلى تظهر خطأً في ال console:

Console
Warning: Each child in a list should have a unique “key” prop.

أنت تحتاج إعطاء كل عنصر في المصفوفة key — وهو عبارة عن نص أو معرف فريد يميز العنصر عن العناصر الأخرى في تلك المصفوفة:

<li key={person.id}>...</li>

Note

استدعاء عناصر JSX مباشرة داخل الدالة map() يحتاج دائماً مفاتيح!

تُخبر المفاتيح (Keys) React أي عنصر في المصفوفة يتوافق مع كل مكوّن، بحيث يمكنها تطابقها لاحقًا. يصبح ذلك مهمًا إذا كانت عناصر المصفوفة قابلة للتحريك (مثل الترتيب)، أو الإدراج، أو الحذف. تُساعد المفتاح المُختار بشكل جيد React على استنتاج ماذا حدث بالضبط، وإجراء التحديثات الصحيحة على شجرة DOM.

بدلا من توليد مفاتيح بسرعة وإغفالها، يجب عليك تضمينهم في بياناتك:

export const people = [{
  id: 0, // يُستخدم في JSX كمفتاح (key)
  name: 'كريولا كاثرين جونسون',
  profession: 'عالمة رياضيات',
  accomplishment: 'حسابات الرحلات الفضائية',
  imageId: 'MK3eW3A'
}, {
  id: 1, // يُستخدم في JSX كمفتاح (key)
  name: 'ماريو خوسيه مولينا-باسكيل هنريكيز',
  profession: 'كيميائي',
  accomplishment: 'اكتشاف ثقب الأوزون في القطب الشمالي',
  imageId: 'mynHUSa'
}, {
  id: 2, // يُستخدم في JSX كمفتاح (key)
  name: 'محمد عبد السلام',
  profession: 'فيزيائي',
  accomplishment: 'نظرية المغناطيسية',
  imageId: 'bE7W1ji'
}, {
  id: 3, // يُستخدم في JSX كمفتاح (key)
  name: 'بيرسي لافون جوليان',
  profession: 'كيميائي',
  accomplishment: 'تطوير أدوية الكورتيزون، الستيرويدات وحبوب منع الحمل',
  imageId: 'IOjWm71'
}, {
  id: 4, // يُستخدم في JSX كمفتاح (key)
  name: 'سوبراهمانيان تشاندراسيخار',
  profession: 'عالم فيزياء الفلك',
  accomplishment: 'حسابات كتلة نجمة الأبيض القزم',
  imageId: 'lrWQx8l'
}];

Deep Dive

عرض عدة عناصر DOM لكل قائمة من العناصر

ماذا تفعل عندما يحتاج كل عنصر إلى التصيير إلي عدة عناصر DOM وليس واحداً منها فقط?

الصيغة القصيرة ل <>...</> Fragment لا تسمح لك بتمرير مفتاح، لذلك تحتاج إمّا أن تجمعهم داخل عنصر <div> مفرد، أو استخدام الصيغة الطويلة قليلاً و <Fragment> الأكثر صراحة:

import { Fragment } from 'react';

// ...

const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);

تختفي Fragments من DOM, لذلك سوف تنتج قائمة منبسطة (لاتحوي قوائم داخلية) مكونة من <h1>, <p>, <h1>, <p>, إلخ.

من أين تحصل على key الخاص بك

المصادر المخلتفة للبيانات تقدم مصادر مختلفة للمفاتيح:

  • البيانات من قواعد البيانات: إذا كانت بياناتك قادمة من قاعدة بيانات، يمكنك استخدام المفاتيح أو المعرفات الفريدة التي تقدمها قواعد البيانات.
  • البيانات التي تم توليدها محلياً: إذا كانت بياناتك مولدة ومستمرة محلياً (مثل: الملاحظات في تطبيق تدوين الملاحظات)، استخدم عداد متزايد، crypto.randomUUID() أو حزمة مثل uuid عندما تنشأ العناصر.

قواعد المفاتيح

  • المفاتيح يجب أن تكون فريدة بين الأشقاء. على أي حال،ورغم ذلك، لا مشكلة في استخدام المفاتيح نفسها لعناصر JSX في مصفوفات مختلفة.
  • يجب ألا تتغير المفاتيح أو يتم إحباط أهدافها! لا تقم بتوليدهم أثناء التصيير.

لماذا تحتاج React مفاتيح?

تخيل بأنّ الملفات على حاسوبك لا تمتلك أسماء. بدلاً من ذلك أنت ترجع إليهم عن طريق ترتيبهم — الملف الأول، الملف الثاني، إلخ. يمكنك اعتياد ذلك، لكن بمجرد حذفك لملف، يمكن أن يصبح الأمر غير مقبول. الملف الثاني يصبح الملف الاول، الملف الثالث يصبح الملف الثاني، وهكذا.

أسماء الملفات في مجلد ومفاتيح JSX في مصفوفة تخدم نفس الهدف. إنها تمكننا من تحديد عنصر بشكل فريد بين أشقائه. الاختيار الجيد للمفتاح يقدم معلومات أكثر من الموضع خلال المصفوفة. وحتى لو تغير الموضع بسبب إعادة الترتيب، ال key يمكن React من تحديد العنصر أثناء وجوده.

Pitfall

يمكن أن يكون استخدام فهرس العنصر (index) في مصفوفة كمفتاح له مغرياً . في الحقيقة، ذلك ما ستستخدمه React إذا لم تحدد key على الإطلاق. لكن الترتيب المستخدم في تصيير العناصر سوف يتغير بمرور الوقت إذا تم إدخال عنصر، أو حذفه أو إذا تغير ترتيب المصفوفة. استخدام الفهرس كمفتاح يؤدي إلى مشاكل خفية ومرفوضة في الكود.

بالمثل لا تقلل من أهمية المفاتيخ عند توليدها، كاستخدام key={Math.random()}. هذا سيجعل المفاتيح غير متطابقة أبداً بين التصييرات، مؤديةً بذلك كل مكوناتك وDOM إلى أن يعاد إنشاؤها في كل مرة. ليس هذا بطيئاً فقط، بل سيفقد أي بيانات مدخلة من المستخدم داخل عناصر القائمة أيضاً، استخدم ID مستقراً معتمداً على البيانات.

لاحظ أن مكوناتك لا تتلقى key كخاصية (prop). إنها تستخدم فقط كتلميح ل React نفسه. إذا احتاج مكونك ID، عليك تمريره كخاصية منفصلة: <Profile key={id} userId={id} />.

Recap

تعلمت في هذه الصفحة:

  • كيف تحول البيانات إلى مكونات أو إلى هياكل مثل المصفوفات والكائنات.
  • كيف تولد مجموعات من المكونات المتشابهة باستخدام دالة JavaScript map().
  • كيف تنشئ مصفوفات من عناصر مصفّاة باستخدام دالة JavaScript filter().
  • لماذا وكيف تضبط ال key لكل مكون في مجموعة بطريقة تستطيع فيها React أن تتعقب كل واحد منهم حتى لو تغيرت بياناتهم ومواضعهم.

Challenge 1 of 4:
فصل قائمة في قائمتين

هذا المثال يظهر قائمة بجميع الناس.

أجري تغيرات عليها إظهار قائمتين منفصلتين واحدة بعد اأخرى: Chemists وEveryone Else. مثل ما سبق, يمكنك تحديد فيما إذا كان الشخص عالم كيمياء عن طريق التحقق من صحة person.profession === 'chemist'.

import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const listItems = people.map(person =>
    <li key={person.id}>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        known for {person.accomplishment}
      </p>
    </li>
  );
  return (
    <article>
      <h1>Scientists</h1>
      <ul>{listItems}</ul>
    </article>
  );
}