نمایش لیستها در React

گاهی پیش می آید که بخواهید چندین کامپوننت مشابه از یک لیست از داده ها را نمایش دهید. برای دستکاری داده های یک آرایه میتوانید از متدهای آرایه در جاوا اسکریپت استفاده کنید. در این صفحه، خواهید دید که چگونه با استفاده از متدهای filter()
و map()
در ریاکت، آرایه داده خود را به یک آرایه کامپوننت تبدیل کنید.
رندر کردن دادهها از آرایهها
فرض کنید یک لیست از محتوا دارید به صورت زیر دارید:
<ul>
<li>Creola Katherine Johnson: mathematician</li>
<li>Mario José Molina-Pasquel Henríquez: chemist</li>
<li>Mohammad Abdus Salam: physicist</li>
<li>Percy Lavon Julian: chemist</li>
<li>Subrahmanyan Chandrasekhar: astrophysicist</li>
</ul>
تنها تفاوت بین موارد این لیست محتویات آنهاست. اغلب پیش می آید که بخواهید چندین نمونه از یک کامپوننت را با دادههای مختلف در حین ساخت رابط کاربری نمایش دهید: از لیستهای نظرات تا گالری تصاویر. در این موقعیتها، میتوانید دادهها را در اشیاء و آرایههای جاوا اسکریپت ذخیره کرده و از متدهایی مانند map()
و filter()
برای رندر لیستی از کامپوننتها استفاده کنید.
در اینجا یک مثال کوتاه از چگونگی تولید لیست عناصری از یک آرایه آورده شده است:
- دادهها را به داخل یک آرایه منتقل کنید:
const people = [
'Creola Katherine Johnson: mathematician',
'Mario José Molina-Pasquel Henríquez: chemist',
'Mohammad Abdus Salam: physicist',
'Percy Lavon Julian: chemist',
'Subrahmanyan Chandrasekhar: astrophysicist'
];
- مپ کردن آیتم های آرایه
people
به یک آرایه جدید از تگ های JSX به آرایهlistItems
:
const listItems = people.map(person => <li>{person}</li>);
- برگرداندن آرایه
listItems
درون تگ<ul>
از داخل کامپوننت:
return <ul>{listItems}</ul>;
و نتیجه به این صورت می شود:
const people = [
'Creola Katherine Johnson: mathematician',
'Mario José Molina-Pasquel Henríquez: chemist',
'Mohammad Abdus Salam: physicist',
'Percy Lavon Julian: chemist',
'Subrahmanyan Chandrasekhar: astrophysicist'
];
export default function List() {
const listItems = people.map(person =>
<li>{person}</li>
);
return <ul>{listItems}</ul>;
}
به پیغام خطای کنسول در Sandbox بالا توجه کنید:
Warning: Each child in a list should have a unique “key” prop.
هشدار: هر فرزند لیست باید دارای پراپ "key" منحصر به فرد باشد.
شما یاد خواهید گرفت که چگونه این خطا را در ادامه این صفحه برطرف کنید. قبل از اینکه به آن بپردازیم، بگذارید مقداری ساختار به دادههای خود اضافه کنیم.
فیلتر کردن آرایه ای از اشیا
داده های ما میتواند حتی بیشتر ساختار بندی شود.
const people = [{
id: 0,
name: 'Creola Katherine Johnson',
profession: 'mathematician',
}, {
id: 1,
name: 'Mario José Molina-Pasquel Henríquez',
profession: 'chemist',
}, {
id: 2,
name: 'Mohammad Abdus Salam',
profession: 'physicist',
}, {
id: 3,
name: 'Percy Lavon Julian',
profession: 'chemist',
}, {
id: 4,
name: 'Subrahmanyan Chandrasekhar',
profession: 'astrophysicist',
}];
بیایید فرض کنیم که میخواهید فقط افرادی را نمایش دهید که شغلشان 'chemist'
است. برای این کار می توانید از متد filter()
جاوا اسکریپت استفاده کنید تا فقط این افراد را برگردانید. این متد یک آرایه از آیتم ها را دریافت کرده، آنها را از طریق تابع "آزمون" (تابعی که true
یا false
برمیگرداند) برررسی می کند و یک آرایه جدید از آیتم هایی که آزمون را پاس کردهاند (مقدار true
برای آزمون برگردانده شده) برمیگرداند.
شما فقط میخواهید مواردی را که profession
آنها 'chemist'
است، دریافت کنید. تابع "آزمون" برای این کار به شکل (person) => person.profession === 'chemist'
است. در اینجا نحوه وصل کردن موارد ذکر شده را آوردهایم:
1 . ایجاد یک آرایه جدید فقط از افراد "شیمیدان"، با فراخوانی filter()
بر روی people
و فیلتر کردن بر اساس person.profession === 'chemist'
:
const chemists = people.filter(person =>
person.profession === 'chemist'
);
2 . حالا اجرای مپینگ روی chemists
:
const listItems = chemists.map(person =>
<li>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}:</b>
{' ' + person.profession + ' '}
known for {person.accomplishment}
</p>
</li>
);
3 . در نهایت، برگرداندن آرایه listItems
از کامپوننت:
return <ul>{listItems}</ul>;
حال می توانید نتیجه را در CodeSandBox مشاهده کنید:
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 + ' '}
known for {person.accomplishment}
</p>
</li>
);
return <ul>{listItems}</ul>;
}
export const people = [{
id: 0,
name: 'Creola Katherine Johnson',
profession: 'mathematician',
accomplishment: 'spaceflight calculations',
imageId: 'MK3eW3A'
}, {
id: 1,
name: 'Mario José Molina-Pasquel Henríquez',
profession: 'chemist',
accomplishment: 'discovery of Arctic ozone hole',
imageId: 'mynHUSa'
}, {
id: 2,
name: 'Mohammad Abdus Salam',
profession: 'physicist',
accomplishment: 'electromagnetism theory',
imageId: 'bE7W1ji'
}, {
id: 3,
name: 'Percy Lavon Julian',
profession: 'chemist',
accomplishment: 'pioneering cortisone drugs, steroids and birth control pills',
imageId: 'IOjWm71'
}, {
id: 4,
name: 'Subrahmanyan Chandrasekhar',
profession: 'astrophysicist',
accomplishment: 'white dwarf star mass calculations',
imageId: 'lrWQx8l'
}];
توابع پیکانی به طور ضمنی عبارتی که بلافاصله پس از علامت =>
آمده است را برمیگردانند، بنابراین نیازی به دستور return
برای این کار ندارید:
const listItems = chemists.map(person =>
<li>...</li> // دستور بازگشت ضمنی!
);
اما اگر بعد از علامت
const listItems = chemists.map(person => { // استفاده از آکولاد و دستور بازگشت
return <li>...</li>;
});
توابع پیکانی ای که شامل => {
هستند، به شما می گویند تابع دارای "بدنه بلوک" است. این قابلیت به شما اجازه نوشتن چندین خط کد برای تابع را می دهد، اما خودتان باید دستور return
را برای برگرداندن داده از تابع به ان اضافه کنید. اگر آن را فراموش کنید، هیچ چیزی برنمیگردد!
حفظ ترتیب آیتم های لیست با ویژگی key
به یاد داشته باشید که تمام SandBoxهای بالا پیغام خطای زیر را در کنسول نمایش میدهند:
Warning: Each child in a list should have a unique “key” prop.
برای رفع خطا باید به هر آیتم آرایه یک ویژگی key
اضافه کنید - یک رشته یا عدد که آن را به طور منحصر به فرد در میان سایر آیتم های آن آرایه مشخص میکند:
<li key={person.id}>...</li>
عناصر JSX برگردانده شده در تابع map()
همیشه به یک کلید نیاز دارند!
کلیدها به ریاکت میگویند که هر کامپوننت مربوط به کدام مورد آرایه است تا بتواند بعداً آنها را با هم مطابقت دهند. این امر زمانی که آیتم های آرایه شما احتمایی جابجایی دارند (مثلاً به دلیل مرتبسازی) یا ممکن است کم و زیاد شوند، اهمیت دارد. ویژگی key
که بهخوبی انتخاب شده به ریاکت کمک میکند تا دقیقاً بفهمد چه اتفاقی افتاده و به روزرسانیهای درست را در درخت DOM انجام دهد.
به جای تولید کلیدهای بی ربط، مقدار کلید باید به هر داده برای همیشه وصل باشد:
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 <ul>{listItems}</ul>;
}
export const people = [{
id: 0, // Used in JSX as a key
name: 'Creola Katherine Johnson',
profession: 'mathematician',
accomplishment: 'spaceflight calculations',
imageId: 'MK3eW3A'
}, {
id: 1, // Used in JSX as a key
name: 'Mario José Molina-Pasquel Henríquez',
profession: 'chemist',
accomplishment: 'discovery of Arctic ozone hole',
imageId: 'mynHUSa'
}, {
id: 2, // Used in JSX as a key
name: 'Mohammad Abdus Salam',
profession: 'physicist',
accomplishment: 'electromagnetism theory',
imageId: 'bE7W1ji'
}, {
id: 3, // Used in JSX as a key
name: 'Percy Lavon Julian',
profession: 'chemist',
accomplishment: 'pioneering cortisone drugs, steroids and birth control pills',
imageId: 'IOjWm71'
}, {
id: 4, // Used in JSX as a key
name: 'Subrahmanyan Chandrasekhar',
profession: 'astrophysicist',
accomplishment: 'white dwarf star mass calculations',
imageId: 'lrWQx8l'
}];
نمایش چند نود DOM برای هر مورد لیست
وقتی برای هر آیتم آرایه نیازمند نمایش چندین نود DOM دارید، چه باید کرد؟
سینتکس کوتاه <>...</>
به شما اجازه نمیدهد که کلید را برای هر آیتم مشخص کنید، بنابراین باید آنها را درون یک تگ <div>
قرار دهید و یا از سینتکس کامل <Fragment>
استفاده کنید.
import { Fragment } from 'react';
// ...
const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);
Fragment ها در DOM قابل مشاهده نیستند، بنابراین خروجی هر آیتم لیست همان تگ های h1
و p
می باشد.
منبع کلید کجاست؟
منابع مختلف داده، منابع مختلفی برای کلیدها ارائه میدهند:
- داده های پایگاه داده: اگر دادهها از پایگاه داده میآید، میتوانید از کلیدها/شناسههای پایگاه داده استفاده کنید که به طور طبیعی منحصر به فرد هستند.
- دادههای محلی: اگر دادههای شما بهطور محلی تولید و ذخیره شده است (مثلاً یادداشتها در یک برنامه یادداشتبرداری)، از یک شمارنده افزایشی،
crypto.randomUUID()
یا کتابخانه ای مانندuuid
هنگام ایجاد داده ها استفاده کنید.
قوانین کلیدها
- کلیدها باید در میان خواهر و برادرهای خود منحصر به فرد باشند. با این حال، استفاده از همان کلیدها برای نودهای JSX در آرایههای مختلف اشکال ندارد.
- کلیدها نباید تغییر کنند چون این کار ماموریت اصلی آنها را زیر سؤال میبرد! آنها را در حین رندر کردن تولید نکنید.
چرا ریاکت نیازمند کلیدهاست؟
تصور کنید که فایلها بر روی دسکتاپ شما نام نداشته باشند. در عوض، شما به آنها براساس ترتیبشان اشاره کنید -- اولین فایل، دومین فایل و غیره. ممکن است به این کار عادت کنید، اما وقتی یک فایل را پاک میکنید، گیج خواهید شد. فایل دوم به فایل اول، فایل سوم به فایل دوم و غیره تبدیل میشود.
نام فایلهای یک پوشه و کلیدهای JSX در یک آرایه هدف مشابهی دارند. آنها به ما اجازه میدهند که یک مورد را بین خواهر و برادرهایش بهطور منحصر به فرد شناسایی کنیم. کلیدی که بهخوبی انتخاب شده اطلاعات بیشتری نسبت به موقعیت درون آرایه ارائه میدهد. حتی اگر موقعیت آیتم تغییر کند، key
به ریاکت این امکان را میدهد که آیتم را در طول عمر خود شناسایی کند.
ممکن است برای استفاده از اندیس آیتمها در آرایه به عنوان کلید، وسوسه شوید. در واقع، این همان کاری است که ریاکت در صورت عدم تعیین key
بهطور پیش فرض انجام میدهد. اما ترتیب رندر کردن موارد با گذشت زمان تغییر خواهد کرد اگر یک آیتمی اضافه، حذف یا آرایه دوباره مرتب شود. معمولاً منجر به بروز باگهای ظریف و گیجکننده میشود.
به همین ترتیب، کلیدهای یکبار مصرف تولید نکنید، بهعنوان مثال با key={Math.random()}
. این باعث میشود که کلیدها هرگز بین رندرها یکسان نباشند و به این ترتیب تمام کامپوننتها و DOM باید با هر رندر دوباره تولید شوند. نه تنها این کار کند است، بلکه ورودی های کاربر در داخل آیتم های لیست را نیز از دست خواهند داد. در عوض، از یک ID پایدار مبتنی بر دادهها استفاده کنید.
به یاد داشته باشید که کامپوننتهای شما key
را به عنوان یک پراپ دریافت نخواهند کرد. این فقط به عنوان یک نشانه توسط خود ریاکت استفاده میشود. اگر کامپوننت شما به یک ID نیاز دارد، باید آن را به عنوان یک پراپ جداگانه پاس دهید: <Profile key={id} userId={id} />
.
جمع بندی
در این صفحه یاد گرفتید:
- چگونه دادهها را از کامپوننتها خارج کرده و در ساختارهای دادهای مانند آرایهها و اشیا قرار دهید.
- چگونه مجموعههایی از کامپوننتهای مشابه را با استفاده از
map()
در جاوا اسکریپت تولید کنید. - چگونه آرایههایی از موارد فیلتر شده با استفاده از
filter()
در جاوا اسکریپت ایجاد کنید. - چرا و چگونه به هر کامپوننت در یک مجموعه
key
اختصاص دهید تا ریاکت بتواند در تغییرات آیتم ها باید کدام المان نمایشی را مجددن رندر کند.
چالش ها
تقسیم یک لیست به دو بخش
این مثال لیستی از افراد را به کاربر نشان میدهد.
کد را به شکلی تغییر دهید که دو لیست جداگانه پشت سر هم به کاربر نمایش دهد برای مثال: شیمیدانها و سایر افراد.مانند قبل، میتوانید با بررسی person.profession === 'chemist'
تعیین کنید که آیا یک فرد شیمیدان است یا نه.
برای مشاهده و حل چالش در CodeSandBox کلیک کنید
لیست های تو در تو
لیستی از دستورالعمل های غذایی به شما داده است. برای هر دستورالعمل نام آن را در تگ h2
بهمراه لیستی از تمامی مواد مورد نیاز برای تهیه آن را به کاربر نمایش دهید.
راهنمایی: باید متد map
را داخل یک تابع پاس شده به متد map
دیگر فراخوانی کنید.
برای مشاهده و حل چالش در CodeSandBox کلیک کنید
یک کامپوننت مجزا برای آیتم لیست بسازید
یک کامپوننت با نام Recipe
از کدهای کامپوننت RecipeList
استخراج کنید که با دریافت شناسه، نام و مواد مورد نیاز، مسئولیت نمایش دستورالعمل را برعهده بگیرد. کلید خارجی را کجا باید استفاده کنید و چرا؟
برای مشاهده و حل چالش در CodeSandBox کلیک کنید
نمایش آیتم های لیست با المان جدا کننده
کدی به شما داده شده که متن شعری را نمایش میدهد که هر خط شامل یک المان p
است. وظیفه شما اضافه کردن تگ hr
بعد از هر خط است تا خط ها از هم جدا شوند. خروجی HTML
برنامه باید به شکل زیر نمایش داده شود:
<article>
<p>I write, erase, rewrite</p>
<hr />
<p>Erase again, and then</p>
<hr />
<p>A poppy blooms.</p>
</article>
شعر هایکو تنها شامل سه خط است ولی راه شما باید بیشتر از آن را نیز بتواند هندل کند. توجه داشته باشید که تگ hr
باید بعد از تگ p
بیاید و نه داخل آن!
راه حل چالش ها
چالش اول
برای درست کردن دو لیست می توانید از متد filter
استفاده کنید:
import { people } from './data.js';
import { getImageUrl } from './utils.js';
export default function List() {
const chemists = people.filter(person =>
person.profession === 'chemist'
);
const everyoneElse = people.filter(person =>
person.profession !== 'chemist'
);
return (
<article>
<h1>Scientists</h1>
<h2>Chemists</h2>
<ul>
{chemists.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>
)}
</ul>
<h2>Everyone Else</h2>
<ul>
{everyoneElse.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>
)}
</ul>
</article>
);
}
چالش دوم
import { recipes } from './data.js';
export default function RecipeList() {
return (
<div>
<h1>Recipes</h1>
{recipes.map(recipe =>
<div key={recipe.id}>
<h2>{recipe.name}</h2>
<ul>
{recipe.ingredients.map(ingredient =>
<li key={ingredient}>
{ingredient}
</li>
)}
</ul>
</div>
)}
</div>
);
}
هر یک از دستورالعمل ها از قبل شامل یک فیلد id
هستند، بنابراین در حلقهی بیرونی از آن بهعنوان key
استفاده شده است.
برای لیست مواد اولیه (ingredients)، هیچ ID
مشخصی برای استفاده بعنوان key وجود ندارد. با این حال، میتوان فرض کرد که یک ماده اولیه در یک دستور غذا دوبار تکرار نمیشود، بنابراین نام آن میتواند بهعنوان key استفاده شود.
بهطور جایگزین، میتوان ساختار دادهها را تغییر داد و برای هر ماده اولیه یک ID اضافه کرد، یا از ایندکس (index) بهعنوان key استفاده نمود—البته با این محدودیت که در این صورت نمیتوان ترتیب مواد اولیه را بهطور ایمن تغییر داد.
چالش سوم
کد داخل تابع map
اول را می توانید درون یک کامپوننت جدید انتقال دهید. و نام متغیرها را از recipe.name
به name
برای تمامی ویژگی های مورد استفاد از شی recipe
تبدیل کنید.
import { recipes } from './data.js';
function Recipe({ id, name, ingredients }) {
return (
<div>
<h2>{name}</h2>
<ul>
{ingredients.map(ingredient =>
<li key={ingredient}>
{ingredient}
</li>
)}
</ul>
</div>
);
}
export default function RecipeList() {
return (
<div>
<h1>Recipes</h1>
{recipes.map(recipe =>
<Recipe {...recipe} key={recipe.id} />
)}
</div>
);
}
کد <Recipe {...recipe} key={recipe.id} />
معدل کوتاه شده کد <Recipe id={recipe.id} name={recipe.name} ingredients={recipe.ingredients} key={recipe.id} />
است. وقتی قرار است تمام پراپرتی های یک شی را یه یک کامپوننت پاس دهیم می توانیم از این روش استفاده کنیم.
توجه داشته باشید که ویژگی دلیل این کار این است که ویژگی key
باید مستقیما درون کانتکستی که آرایه را احاطه کرده استفاده شود.
چالش چهارم
برای این کار می توانید به صورت دستی بوسیله حلقه foreach
تمامی المان های p
و hr
را پشت سر هم برای تمامی خطوط ایجاد کنید.
const poem = {
lines: [
'I write, erase, rewrite',
'Erase again, and then',
'A poppy blooms.'
]
};
export default function Poem() {
let output = [];
// Fill the output array
poem.lines.forEach((line, i) => {
output.push(
<hr key={i + '-separator'} />
);
output.push(
<p key={i + '-text'}>
{line}
</p>
);
});
// Remove the first <hr />
output.shift();
return (
<article>
{output}
</article>
);
}
در این روش استفاده از اندیس خط درون شعر برای استفاده در ویژگی کلید کاربردی نیست چون آرایه خروجی ما شامل لیستی از المان های p
و hr
است و برای هر خط دو المان استفاده شده است. برای همین به انتهای کلید تگ p
عبارت -text
و به انتهای کلید تگ hr
عبارت -separator
را اضافه می کنیم.
راه حل دیگر استفاده از لیستی از Fragment
هاست که هر کدام حاوی المان های p
و hr
است. نکته مهم این است که بدلیل انتصاب کلید نمی توان از دستور کوتاه شده <>...</>
استفاده کرد.
import { Fragment } from 'react';
const poem = {
lines: [
'I write, erase, rewrite',
'Erase again, and then',
'A poppy blooms.'
]
};
export default function Poem() {
return (
<article>
{poem.lines.map((line, i) =>
<Fragment key={i}>
{i > 0 && <hr />}
<p>{line}</p>
</Fragment>
)}
</article>
);
}