logo
داکیومنت ریکت - قسمت چهارم
ریکت

ریکت

24 مرداد 1402

سلام دوستان امیدوارم خوب باشید و همیشه درحال یادگیری🧑‍💻. توی این قسمت از داکیومنت ریکت قرار هست این سه موضوع رو بررسی کنیم:

  • Prop ها و نحوه عملکردشون
  • رندر کردن لیست ها
  • چطوری می‌تونیم کامپوننت هامون رو Pure نگهداریم؟

نحوه عملکرد Prop ها

راه ارتباطی بین کامپوننت های ریکت، Prop ها هستن. هر کامپوننت والد میتونه به فرزندان خودش از طریق Prop ها اطلاعات ارسال کنه.

اطلاعاتی که به تگ های JSX ارسال می‌کنید، مثل src، className و... هم Prop های آشنایی هستند که ازشون استفاده می‌کنیم.

function Avatar() {
  return (
    <img
      className="avatar"
      src="https://i.imgur.com/1bX5QH6.jpg"
      alt="Lin Lanying"
      width={100}
      height={100}
    />
  );
}

export default function Profile() {
  return (
    <Avatar />
  );
}

این Prop هایی که ما به تگ img یا هر تگ دیگه ایی پاس میدیم از پیش تعریف شده هستند.

ارسال Prop به کامپوننت

در مثالی که در بالا داشته، فرض کنید قرار هست به کامپوننت Avatar که هیچ Prop ایی نداره، Prop ارسال کنیم، خوب این مراحل رو باید انجام بدیم:

1. ارسال Prop به فرزند

خوب اول میایم به کامپوننت Avatar ، دو تا Prop از نوع آبجکت (Person) و عدد (size) ارسال می‌کنیم.

export default function Profile() {
  return (
    <Avatar
      person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
      size={100}
    />
  );
}

2. خواندن Prop داخل کامپوننت فرزند

حالا داخل کامپوننت فرزند ما به دو روش می‌تونیم، دیتا های ارسالی رو بخونیم.

روش به اول بصورت destructuring هست، یعنی برحسب اسم Prop ارسالی بیایم به وسیله {{ و }} بصورت مستقیم به مقادیر ارسالی دسترسی داشته باشیم.

function Avatar({ person, size }) {
  // person and size are available here
}

در روش دوم ما لازم هست ورودی کامپوننت رو دریافت کنیم، ساختار ورودی هم بصورت object هست که مقادیر Prop های ارسالی به کامپوننت داخل این object هست و می‌تونیم بهش دسترسی داشته باشیم.

function Avatar(props) {
  let person = props.person;
  let size = props.size;
  // ...
}

یک نکته، در حالتی که ما بصورت destructuring اومدیم Prop ها رو دریافت کردیم، می‌تونیم مقدار پیشفرض هم بهش پاس بدیم تا اگر Prop ارسال نشده بود یا undefined بود اون مقدار استفاده شود.

function Avatar({ person, size = 100 }) {
  // ...
}


ارسال JSX به عنوان فرزند

گاهی وقت ها لازم هست، کامپوننت تو در تو داشته باشیم، زمانی که کامپوننت ها تو در تو می‌شوند، والد از طریق Prop ایی به نام children اون JSX فرزند رو میتونه ببینه.

بطور مثال کامپوننت Card در اینجا از طریق children کامپوننت Avatar رو دریافت می‌کنه و به عنوان فرزند تگ div قرار میده و رندر می‌کنه.

import Avatar from './Avatar.js';

function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

export default function Profile() {
  return (
    <Card>
      <Avatar
        size={100}
        person={{ 
          name: 'Katsuko Saruhashi',
          imageId: 'YfeOqp2'
        }}
      />
    </Card>
  );
}

چند تا نکته درباره Prop ها:

  1. اولین نکته اینکه Prop ها immutable (تغییرناپذیر) هستن. به این معنا که اگه یک کامپوننت بخواد Prop ها رو تغییر بده باید از کامپوننتی که Prop رو فرستاده درخواست کنه تا دیتا جدید رو بده، خودش نمیتونه این کار رو بکنه. وقتی Prop جدید داده میشه اون فضایی که Prop های قبلی گرفته بودن آزاد میشه و مقادیر جدید دوباره یک فضای جدید میگیرن.

  2. نکته بعدی اینکه از Prop برای تعامل نمیشه استفاده کرد و باید از state ها استفاده کنیم.

  3. در آخر از همه، Prop ها read-only هستن و توی هر رندر دیتا جدید برای Prop ها میاد.



رندر کردن شرطی

توی کامپوننت ها بعضی وقت ها لازم هست، برحسب بعضی شروط بعضی کامپوننت ها نمایش یا مخفی شوند.

روش خیلی رایج توی رندر های شرطی استفاده از عملگر های ternary هست ( : ?). فرمتش اینطوریه، condition ? exprIfTrue : exprIfFalse اگه شرط برقرار باشه عبارت بعد علامت سوال رو نشون میده در غیر اینصورت عبارت بعد دو نقطه.

return (
  <li className="item">
    {isPacked ? name + ' ✔' : name}
  </li>
);

اگه توی شرطمون else وجود نداشت از && هم میتونیم استفاده کنیم که اگه عبارت فقط true بود بیاد کامپوننت رو رندر کنه.

return (
  <li className="item">
    {name} {isPacked && '✔'}
  </li>
);

یک نکته مهم درباره رندر کردن شرطی

وقتی که دارین از && استفاده می‌کنید سمت چپ && ترجیحا نباید عدد باشه. مثلا اگه مقدار messageCount صفر بشه عملا شرط ما false میشه. برای اینکه این مشکل حل بشه به این شکل میشه عمل کرد:

messageCount > 0 && <p>New messages</p>


رندر لیست ها

بعضی وقت ها لازم هست برحسب دیتا ایی که داریم‌‌، یکسری کامپوننت های مشابه رو رندر کنیم. ما از متد هایی که جاوا اسکریپت در اختیارمون گذاشته استفاده می‌کنیم تا این کامپوننت ها رو نمایش بدیم.

فرض کنید دیتا ما بصورت زیر هست:

const people = [
  'Creola Katherine Johnson: mathematician',
  'Mario José Molina-Pasquel Henríquez: chemist',
  'Mohammad Abdus Salam: physicist',
  'Percy Lavon Julian: chemist',
  'Subrahmanyan Chandrasekhar: astrophysicist'
];

ابتدا لازم هست ما با کمک متد Map بیایم برحسب دیتا ایی که داریم آرایه ایی از المان های JSX رو برگردونیم و داخل یک متغیر ذخیره کنیم.

const listItems = people.map(person => <li>{person}</li>);

و در نهایت از اون استفاده کنیم:

return <ul>{listItems}</ul>;

خروجی بصورت زیر هست:



اما هنوز یک مشکلی هست، داخل کنسول ارور Warning: Each child in a list should have a unique “key” prop. رو داریم.

مشکل از اینجا هستش که ما هر موقع از Map داریم استفاده می‌کنیم، به المان ها باید key منحصر به فرد بدیم. حالا مفهوم key چی هست اصلا؟

فرض کنید فایل ها داخل سیستم شما اسم نداشته باشن. بجاش بر حسب ترتیب بهشون اشاره کنید، فایل اول، فایل دوم و... حالا فرض کنید یک فایل پاک بشه، الان گیج میشید. فایل دوم میشه فایل اول، فایل سوم میشه فایل دوم و... دقیقا key هم برای ما مشابه همین اسم فایل هامون هست. کمک میکنه برای شناسایی المان های رندر شده. حتی اگر ترتیب المان هامون عوض بشه با این key میتونیم شناسایی کنیم.

حالا چند تا نکته درباره key:

  1. از index ایی که داخل Map بهمون میده به عنوان key استفاده نکنید. چون با حذف کردن یا اضافه کردن یک آیتم تغییر می‌کنه.
  2. در زمان رندر کردن key تولید نکنید. چون با هر رندر مجدد مقدار key ها همش تغییر میکنه. به طور مثال این اشتباه هست:
key={Math.random()}

در نهایت شکل درست رندر کردن بصورت زیر هست:



چطوری می‌تونیم کامپوننت هامون رو Pure نگهداریم؟

بیاین یکم پایه ایی تر بحث کنیم، Pure Function چه وظیفه ایی داره؟

  1. سرش توی کار خودش باشه. یعنی هیچ آبجکت یا متغیری که وجود داشته قبل فراخوانی، تغییرش نده.
  2. ورودی های یکسان باید خروجی های یکسان داشته باشند.

مثلا فرض کنید در معادله y = 2x، اگه x = 2 باشه همیشه y = 4 میشه. اگه x = 3 باشه همیشه y = 6. امکان نداره اگه x = 3 مثلا بعضی وقت ها 9 بده.

اگه این رو به کد جاوا اسکریپت تبدیل کنیم، مشابه این میشه:

function double(number) {
  return 2 * number;
}

توی مثال بالا double یک pure function هست. اگر بهش 3 پاس بدیم، همیشه 6 برمیگردونه.


ریکت حول این مفهوم طراحی شده. ریکت فرض می‌کنه که شما تمام کامپوننت هایی که می‌نویسید pure هستند، به این معنا که با ورودی های یکسان به کامپوننت، JSX مشابه دریافت می‌کنیم.

function Recipe({ drinkers }) {
  return (
    <ol>    
      <li>Boil {drinkers} cups of water.</li>
      <li>Add {drinkers} spoons of tea and {0.5 * drinkers} spoons of spice.</li>
      <li>Add {0.5 * drinkers} cups of milk to boil and sugar to taste.</li>
    </ol>
  );
}

export default function App() {
  return (
    <section>
      <h1>Spiced Chai Recipe</h1>
      <h2>For two</h2>
      <Recipe drinkers={2} />
      <h2>For a gathering</h2>
      <Recipe drinkers={4} />
    </section>
  );
}

داخل مثال بالا اگر drinkers={2} رو به کامپوننت Recipe پاس بدیم، همیشه JSX ایی با محتوای 2 cups of water برمیگردونه.

اگر drinkers={4} رو به کامپوننت Recipe پاس بدیم، همیشه JSX ایی با محتوای 4 cups of water برمیگردونه.

اگر ما نسبت به کامپوننت ها دیدگاه مشابه دستور پخت داشته باشیم: اگر طبق دستور پخت پیش بریم و چیز جدیدی بهش اضافه نکنیم، همون غذا قبل رو همیشه می‌گیریم. توی این فرض اون غذا همون JSX ما هستش.


کامپوننت impure:

همینطور که گفتیم، کامپوننت pure همیشه باید JSX یکسان برگردونه و هیچ متغیر یا آبجکت ایی رو که قبل از رندر شدن وجود داشته رو تغییر نده--همین کار باعث impure شدن کامپوننت میشه. به این مثال دقت کنید:

let guest = 0;

function Cup() {
  // Bad: changing a preexisting variable!
  guest = guest + 1;
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup />
      <Cup />
      <Cup />
    </>
  );
}

این کامپوننت متغیر guest رو خارج از کامپوننت تعریف کردن و داخل کامپوننت مقدار اون رو داره تغییر میده. این یعنی با چندین بار صدا زدن Cup ما داریم JSX متفاوتی می‌گیریم، و این یعنی کامپوننت ما pure نیست.

برای اینکه مشکل حل شود می‌تونیم از Prop استفاده کنیم:

function Cup({ guest }) {
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup guest={1} />
      <Cup guest={2} />
      <Cup guest={3} />
    </>
  );
}

نکته آخر اینکه ریکت توی حالت توسعه “Strict Mode” رو داره که هر کامپوننت رو دو بار رندر می‌کنه، که این کمک می‌کنه بتونیم این مشکلات رو متوجه بشیم.


خوب بچه ها امیدوارم این قسمت براتون مفید بوده باشه. خوشحال میشم اگه انتقادی یا پیشنهادی دارید برام بنویسید تا محتوای بهتری تولید بشه ❤️

منابع:

این پست برات مفید بود؟

0

heart

0

like

0

happy

0

sad