الفرق بين المراجعتين لصفحة: «React/lists and keys»
Kinan-mawed (نقاش | مساهمات) لا ملخص تعديل |
تحديث |
||
(10 مراجعات متوسطة بواسطة 3 مستخدمين غير معروضة) | |||
سطر 1: | سطر 1: | ||
<noinclude>{{DISPLAYTITLE:القوائم والمفاتيح}}</noinclude> | <noinclude>{{DISPLAYTITLE:القوائم والمفاتيح في React}}</noinclude> | ||
فلنتذكّر أولًا كيفيّة تحويل القوائم في JavaScript. | فلنتذكّر أولًا كيفيّة تحويل القوائم في [[JavaScript]]. | ||
في المثال التالي سنستخدم الدالة <code>[ | في المثال التالي سنستخدم الدالة <code>[[JavaScript/Array/map|map()]]</code> لمضاعفة قيم مصفوفة من الأرقام اسمها <code>numbers</code>، وسنُعيِّن المصفوفة الجديدة التي تُعيدها الدالة <code>map()</code> إلى المتغير <code>doubled</code> ثم نعرض محتواه عبر التابع <code>console.log()</code>:<syntaxhighlight lang="javascript"> | ||
const numbers = [1, 2, 3, 4, 5]; | const numbers = [1, 2, 3, 4, 5]; | ||
const doubled = numbers.map((number) => number * 2); | const doubled = numbers.map((number) => number * 2); | ||
console.log(doubled); | console.log(doubled); | ||
</syntaxhighlight>نتيجة تنفيذ هذا المثال هي <code>[2, 4, 6, 8, 10]</code>. | </syntaxhighlight>نتيجة تنفيذ هذا المثال هي <code>[2, 4, 6, 8, 10]</code>. | ||
يكون تحويل المصفوفات في React إلى قوائم من العناصر مماثلًا | يكون تحويل المصفوفات في React إلى قوائم من [[React/rendering elements|العناصر]] مماثلًا تقريبًا لذلك. | ||
== تصيير مكونات متعددة == | == تصيير مكونات متعددة == | ||
سطر 36: | سطر 36: | ||
return ( | return ( | ||
<ul>{listItems}</ul> | <ul>{listItems}</ul> | ||
); | |||
} | |||
const numbers = [1, 2, 3, 4, 5]; | |||
ReactDOM.render( | |||
<NumberList numbers={numbers} />, | |||
document.getElementById('root') | |||
); | |||
</syntaxhighlight>عندما تُنفَّذ هذه الشيفرة ستتلقى تحذيرًا أنّه يجب تزويد مفتاح (key) لعناصر القائمة. والمفتاح هو عبارة عن خاصيّة على شكل سلسلة نصيّة يجب إضافتها عند إنشاء قوائم من العناصر. سنناقش أهميته في الفقرة القادمة. | |||
فلنُعيِّن مفتاح <code>key</code> لعناصر القائمة بداخل التابع <code>numbers.map()</code> وبذلك نُصلِح مشكلة المفتاح المفقود:<syntaxhighlight lang="javascript"> | |||
function NumberList(props) { | |||
const numbers = props.numbers; | |||
const listItems = numbers.map((number) => | |||
<li key={number.toString()}> | |||
{number} | |||
</li> | |||
); | |||
return ( | |||
<ul>{listItems}</ul> | |||
); | |||
} | |||
const numbers = [1, 2, 3, 4, 5]; | |||
ReactDOM.render( | |||
<NumberList numbers={numbers} />, | |||
document.getElementById('root') | |||
); | |||
</syntaxhighlight>[https://codepen.io/gaearon/pen/jrXYRR?editors=0011 جرب المثال على موقع CodePen]. | |||
== المفاتيح (Keys) == | |||
تُساعِد المفاتيح React على معرفة العناصر التي تغيرت، أو أُضيفت، أو أُزيلت. يجب أن تُعطَى المفاتيح للعناصر بداخل المصفوفة وذلك لإعطاء هذه العناصر هوية مستقرة:<syntaxhighlight lang="javascript"> | |||
const numbers = [1, 2, 3, 4, 5]; | |||
const listItems = numbers.map((number) => | |||
<li key={number.toString()}> | |||
{number} | |||
</li> | |||
); | |||
</syntaxhighlight>أفضل طريقة لانتقاء مفتاح هي استخدام سلسلة نصيّة تُعرِّف بشكل فريد عنصر القائمة عن العناصر الأخرى، نستخدم غالبًا المُعرِّفات (<code>IDs</code>) من بياناتنا كمفاتيح:<syntaxhighlight lang="javascript"> | |||
const todoItems = todos.map((todo) => | |||
<li key={todo.id}> | |||
{todo.text} | |||
</li> | |||
); | |||
</syntaxhighlight>عندما لا تملك مُعرِّفات مستقرة للعناصر، فبإمكانك استخدام فهرس العنصر (<code>index</code>) كمفتاح كملاذ أخير:<syntaxhighlight lang="javascript"> | |||
const todoItems = todos.map((todo, index) => | |||
// افعل ذلك فقط إن لم لكن للعناصر معرّفات مستقرة | |||
<li key={index}> | |||
{todo.text} | |||
</li> | |||
); | |||
</syntaxhighlight>لا نُفضِّل استخدام فهارس العناصر إن كان ترتيبها عُرضةً للتغيير، فقد يُؤثِّر هذا بشكل سلبي على الأداء وقد يسبب مشاكل مع حالة المُكوِّن. اطّلع على [https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318 هذا المقال للحصول على شرح مُفصّل للتأثيرات السلبية لاستخدام الفهرس كمفتاح]. إن اخترت عدم تعيين مفتاح لعناصر القائمة فستستخدم React الفهارس كمفاتيح بشكل افتراضي. | |||
إن كنت ترغب بمعرفة المزيد ستجد هنا [https://reactjs.org/docs/reconciliation.html#recursing-on-children شرحا مفصلا حول أهمية المفاتيح]. | |||
== استخراج المكونات ذات المفاتيح == | |||
يكون للمفاتيح معنى فقط في سياق المصفوفة الموجودة ضمنها. فمثلًا إن استخرجت المُكوِّن <code>ListItem</code> فيجب أن تحتفظ بالمفتاح ضمن العناصر <code><ListItem /></code> الموجودة في المصفوفة بدلًا من وضعه في العنصر <code>[[HTML/li|<nowiki><li></nowiki>]]</code> الموجود في المُكوِّن <code>ListItem</code>. | |||
=== مثال: الاستخدام الخاطئ للمفتاح === | |||
<syntaxhighlight lang="javascript"> | |||
function ListItem(props) { | |||
const value = props.value; | |||
return ( | |||
// خطأ! فليس هنالك حاجة لتحديد المفتاح هنا | |||
<li key={value.toString()}> | |||
{value} | |||
</li> | |||
); | |||
} | |||
function NumberList(props) { | |||
const numbers = props.numbers; | |||
const listItems = numbers.map((number) => | |||
// خطأ! يجب تحديد المفتاح هنا | |||
<ListItem value={number} /> | |||
); | |||
return ( | |||
<ul> | |||
{listItems} | |||
</ul> | |||
); | ); | ||
} | } | ||
سطر 45: | سطر 126: | ||
); | ); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== مثال: الاستخدام الصحيح للمفتاح === | |||
<syntaxhighlight lang="javascript"> | |||
function ListItem(props) { | |||
// صحيح! فليس هنالك حاجة لتحديد المفتاح هنا | |||
return <li>{props.value}</li>; | |||
} | |||
function NumberList(props) { | |||
const numbers = props.numbers; | |||
const listItems = numbers.map((number) => | |||
// Correct! Key should be specified inside the array. | |||
// صحيح! يجب تحديد المفتاح بداخل المصفوفة. | |||
<ListItem key={number.toString()} | |||
value={number} /> | |||
); | |||
return ( | |||
<ul> | |||
{listItems} | |||
</ul> | |||
); | |||
} | |||
const numbers = [1, 2, 3, 4, 5]; | |||
ReactDOM.render( | |||
<NumberList numbers={numbers} />, | |||
document.getElementById('root') | |||
); | |||
</syntaxhighlight>[https://codepen.io/gaearon/pen/ZXeOGM?editors=0010 جرب المثال على موقع CodePen]. | |||
وكقاعدة عامة تحتاج العناصر المُستدعاة من قبل التابع <code>map()</code> إلى مفاتيح. | |||
== ينبغي أن تكون المفاتيح فريدة فقط ضمن العناصر الأشقاء == | |||
يجب أن تكون العناصر المستخدمة ضمن المصفوفات فريدة من بين العناصر الأشقاء لها، ولكن ليس بالضرورة أن تكون فريدة بشكل عام، فيمكننا استخدام نفس المفاتيح عند إنتاج مصفوفتين مختلفتين:<syntaxhighlight lang="javascript"> | |||
function Blog(props) { | |||
const sidebar = ( | |||
<ul> | |||
{props.posts.map((post) => | |||
<li key={post.id}> | |||
{post.title} | |||
</li> | |||
)} | |||
</ul> | |||
); | |||
const content = props.posts.map((post) => | |||
<div key={post.id}> | |||
<h3>{post.title}</h3> | |||
<p>{post.content}</p> | |||
</div> | |||
); | |||
return ( | |||
<div> | |||
{sidebar} | |||
<hr /> | |||
{content} | |||
</div> | |||
); | |||
} | |||
const posts = [ | |||
{id: 1, title: 'أهلًا بالعالم', content: 'مرحبًا بك لتعلم React'}, | |||
{id: 2, title: 'التثبيت', content: 'بإمكانك تثبيت React عن طريق npm'} | |||
]; | |||
ReactDOM.render( | |||
<Blog posts={posts} />, | |||
document.getElementById('root') | |||
); | |||
</syntaxhighlight>[https://codepen.io/gaearon/pen/NRZYGN?editors=0010 جرب المثال على موقع CodePen]. | |||
تعمل المفاتيح كتلميح في React، ولكنها لا تُمرَّر إلى المُكوِّنات. إن احتجت نفس القيمة في مُكوِّناتك فمرّرها بشكل صريح كخاصيّة <code>prop</code> مع استخدام اسم آخر:<syntaxhighlight lang="javascript"> | |||
const content = posts.map((post) => | |||
<Post | |||
key={post.id} | |||
id={post.id} | |||
title={post.title} /> | |||
); | |||
</syntaxhighlight>في المثال السّابق يستطيع المُكوِّن <code>Post</code> قراءة <code>props.id</code> ولكن لا يُمكنه قراءة <code>props.key</code>. | |||
== تضمين التابع <code>map()</code> في JSX == | |||
صرّحنا في الأمثلة السّابقة عن المتغير <code>listItems</code> بشكلٍ مُنفصل وضمّناه في JSX:<syntaxhighlight lang="javascript"> | |||
function NumberList(props) { | |||
const numbers = props.numbers; | |||
const listItems = numbers.map((number) => | |||
<ListItem key={number.toString()} | |||
value={number} /> | |||
); | |||
return ( | |||
<ul> | |||
{listItems} | |||
</ul> | |||
); | |||
} | |||
</syntaxhighlight>تسمح JSX [[React/introducing jsx#.D8.AA.D8.B6.D9.85.D9.8A.D9.86 .D8.A7.D9.84.D8.AA.D8.B9.D8.A7.D8.A8.D9.8A.D8.B1 .D9.81.D9.8A JSX|بتضمين أي تعبير]] موجود بين قوسين لذا يمكننا وضع نتيجة التابع <code>map()</code> بشكل سطري (inline):<syntaxhighlight lang="javascript"> | |||
function NumberList(props) { | |||
const numbers = props.numbers; | |||
return ( | |||
<ul> | |||
{numbers.map((number) => | |||
<ListItem key={number.toString()} | |||
value={number} /> | |||
)} | |||
</ul> | |||
); | |||
} | |||
</syntaxhighlight>[https://codepen.io/gaearon/pen/BLvYrB?editors=0010 جرب المثال على موقع CodePen]. | |||
يُؤدّي هذا أحيانًا إلى شيفرةٍ مفهومةٍ بشكلٍ أكبر، ولكن قد يُساء استخدام هذا التنسيق. كما هو الحال في JavaScript لك حريّة القرار إذا ما كان استخراج المتغيرات لسهولة القراءة يستحق العناء. إن كان جسم التابع <code>map()</code> متداخلًا كثيرًا فمن الأفضل [[React/components and props#.D8.A7.D8.B3.D8.AA.D8.AE.D8.B1.D8.A7.D8.AC .D8.A7.D9.84.D9.85.D9.83.D9.88.D9.86.D8.A7.D8.AA|استخراج المُكوِّن]]. | |||
== انظر أيضًا == | |||
* [[React/hello world|مثال أهلًا بالعالم في React]] | |||
* [[React/introducing jsx|مقدمة إلى JSX]] | |||
* [[React/rendering elements|تصيير العناصر]] | |||
* [[React/components and props|المكونات والخاصيات]] | |||
* [[React/state and lifecycle|حالة ودورة حياة المكونات]] | |||
* [[React/handling events|معالجة الأحداث في React]] | |||
* [[React/conditional rendering|التصيير الشرطي]] | |||
* [[React/forms|الحقول]] | |||
* [[React/lifting state up|رفع الحالات المشتركة للمستوى الأعلى]] | |||
* [[React/composition vs inheritance|الفرق بين التركيب والوراثة في React]] | |||
* [[React/thinking in react|أسلوب التفكير في React]] | |||
== مصادر == | |||
* [https://reactjs.org/docs/lists-and-keys.html صفحة القوائم والمفاتيح في توثيق React الرسمي]. | |||
[[تصنيف:React]] | |||
[[تصنيف:React Main Concepts]] |
المراجعة الحالية بتاريخ 16:15، 2 نوفمبر 2020
فلنتذكّر أولًا كيفيّة تحويل القوائم في JavaScript.
في المثال التالي سنستخدم الدالة map()
لمضاعفة قيم مصفوفة من الأرقام اسمها numbers
، وسنُعيِّن المصفوفة الجديدة التي تُعيدها الدالة map()
إلى المتغير doubled
ثم نعرض محتواه عبر التابع console.log()
:
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);
نتيجة تنفيذ هذا المثال هي [2, 4, 6, 8, 10]
.
يكون تحويل المصفوفات في React إلى قوائم من العناصر مماثلًا تقريبًا لذلك.
تصيير مكونات متعددة
بإمكانك بناء مجموعات من العناصر وتضمينها في JSX باستخدام الأقواس {}
.
في المثال التالي سنستخدم الدالة map()
للمرور على جميع عناصر مصفوفة الأرقام numbers
بحيث نُعيد عنصر <li>
لكل عنصر من هذه المصفوفة. وأخيرا نُعيِّن مصفوفة العناصر الناتجة إلى المتغير listItems
:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);
نضع الآن كامل المصفوفة listItems
بداخل عنصر <ul>
، ونُصيِّره إلى DOM:
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
تعرض هذه الشيفرة قائمة منقطة مؤلفة من الأرقام بين 1 و 5.
مكون يحتوي على قائمة بسيطة
نُصيِّر عادةً القوائم بداخل المُكوِّنات، لذلك بإمكاننا إعادة كتابة المثال السابق باستخدام مُكوِّن يقبل مصفوفة من الأرقام تُدعى numbers
ويعرض عناصرها على شكل قائمة غير مرتبة:
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
عندما تُنفَّذ هذه الشيفرة ستتلقى تحذيرًا أنّه يجب تزويد مفتاح (key) لعناصر القائمة. والمفتاح هو عبارة عن خاصيّة على شكل سلسلة نصيّة يجب إضافتها عند إنشاء قوائم من العناصر. سنناقش أهميته في الفقرة القادمة.
فلنُعيِّن مفتاح key
لعناصر القائمة بداخل التابع numbers.map()
وبذلك نُصلِح مشكلة المفتاح المفقود:
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
المفاتيح (Keys)
تُساعِد المفاتيح React على معرفة العناصر التي تغيرت، أو أُضيفت، أو أُزيلت. يجب أن تُعطَى المفاتيح للعناصر بداخل المصفوفة وذلك لإعطاء هذه العناصر هوية مستقرة:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
أفضل طريقة لانتقاء مفتاح هي استخدام سلسلة نصيّة تُعرِّف بشكل فريد عنصر القائمة عن العناصر الأخرى، نستخدم غالبًا المُعرِّفات (IDs
) من بياناتنا كمفاتيح:
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
عندما لا تملك مُعرِّفات مستقرة للعناصر، فبإمكانك استخدام فهرس العنصر (index
) كمفتاح كملاذ أخير:
const todoItems = todos.map((todo, index) =>
// افعل ذلك فقط إن لم لكن للعناصر معرّفات مستقرة
<li key={index}>
{todo.text}
</li>
);
لا نُفضِّل استخدام فهارس العناصر إن كان ترتيبها عُرضةً للتغيير، فقد يُؤثِّر هذا بشكل سلبي على الأداء وقد يسبب مشاكل مع حالة المُكوِّن. اطّلع على هذا المقال للحصول على شرح مُفصّل للتأثيرات السلبية لاستخدام الفهرس كمفتاح. إن اخترت عدم تعيين مفتاح لعناصر القائمة فستستخدم React الفهارس كمفاتيح بشكل افتراضي.
إن كنت ترغب بمعرفة المزيد ستجد هنا شرحا مفصلا حول أهمية المفاتيح.
استخراج المكونات ذات المفاتيح
يكون للمفاتيح معنى فقط في سياق المصفوفة الموجودة ضمنها. فمثلًا إن استخرجت المُكوِّن ListItem
فيجب أن تحتفظ بالمفتاح ضمن العناصر <ListItem />
الموجودة في المصفوفة بدلًا من وضعه في العنصر <li>
الموجود في المُكوِّن ListItem
.
مثال: الاستخدام الخاطئ للمفتاح
function ListItem(props) {
const value = props.value;
return (
// خطأ! فليس هنالك حاجة لتحديد المفتاح هنا
<li key={value.toString()}>
{value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// خطأ! يجب تحديد المفتاح هنا
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
مثال: الاستخدام الصحيح للمفتاح
function ListItem(props) {
// صحيح! فليس هنالك حاجة لتحديد المفتاح هنا
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
// صحيح! يجب تحديد المفتاح بداخل المصفوفة.
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
وكقاعدة عامة تحتاج العناصر المُستدعاة من قبل التابع map()
إلى مفاتيح.
ينبغي أن تكون المفاتيح فريدة فقط ضمن العناصر الأشقاء
يجب أن تكون العناصر المستخدمة ضمن المصفوفات فريدة من بين العناصر الأشقاء لها، ولكن ليس بالضرورة أن تكون فريدة بشكل عام، فيمكننا استخدام نفس المفاتيح عند إنتاج مصفوفتين مختلفتين:
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map((post) =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
);
const content = props.posts.map((post) =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
{sidebar}
<hr />
{content}
</div>
);
}
const posts = [
{id: 1, title: 'أهلًا بالعالم', content: 'مرحبًا بك لتعلم React'},
{id: 2, title: 'التثبيت', content: 'بإمكانك تثبيت React عن طريق npm'}
];
ReactDOM.render(
<Blog posts={posts} />,
document.getElementById('root')
);
جرب المثال على موقع CodePen.
تعمل المفاتيح كتلميح في React، ولكنها لا تُمرَّر إلى المُكوِّنات. إن احتجت نفس القيمة في مُكوِّناتك فمرّرها بشكل صريح كخاصيّة prop
مع استخدام اسم آخر:
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);
في المثال السّابق يستطيع المُكوِّن Post
قراءة props.id
ولكن لا يُمكنه قراءة props.key
.
تضمين التابع map()
في JSX
صرّحنا في الأمثلة السّابقة عن المتغير listItems
بشكلٍ مُنفصل وضمّناه في JSX:
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
تسمح JSX بتضمين أي تعبير موجود بين قوسين لذا يمكننا وضع نتيجة التابع map()
بشكل سطري (inline):
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
يُؤدّي هذا أحيانًا إلى شيفرةٍ مفهومةٍ بشكلٍ أكبر، ولكن قد يُساء استخدام هذا التنسيق. كما هو الحال في JavaScript لك حريّة القرار إذا ما كان استخراج المتغيرات لسهولة القراءة يستحق العناء. إن كان جسم التابع map()
متداخلًا كثيرًا فمن الأفضل استخراج المُكوِّن.
انظر أيضًا
- مثال أهلًا بالعالم في React
- مقدمة إلى JSX
- تصيير العناصر
- المكونات والخاصيات
- حالة ودورة حياة المكونات
- معالجة الأحداث في React
- التصيير الشرطي
- الحقول
- رفع الحالات المشتركة للمستوى الأعلى
- الفرق بين التركيب والوراثة في React
- أسلوب التفكير في React