برنامه نویسیفرانت اند

آموزش ساخت کامپوننت‌های پوششی (Wrapper Components) در React با استفاده از Props

در این آموزش جامع، نحوه ساخت کامپوننت‌های پوششی (Wrapper Components) در React با استفاده از Props را یاد بگیرید. با استفاده از عملگرهای Spread و Rest و Prop داخلی children، کامپوننت‌های منعطف و قابل استفاده مجدد برای رابط کاربری خود بسازید.

مقدمه
در این آموزش، شما با استفاده از کتابخانه جاوااسکریپت React، کامپوننت‌های پوششی (Wrapper Components) را با کمک Props ایجاد خواهید کرد. کامپوننت‌های پوششی کامپوننت‌هایی هستند که دور کامپوننت‌های ناشناخته را می‌گیرند و یک ساختار پیش‌فرض برای نمایش کامپوننت‌های فرزند (Child Components) ارائه می‌دهند. این الگو برای ایجاد عناصر رابط کاربری (UI) که به طور مکرر در یک طراحی استفاده می‌شوند، مانند مودال‌ها (Modals)، صفحات قالب و کاشی‌های اطلاعاتی (Information Tiles) بسیار مفید است.

برای ایجاد کامپوننت‌های پوششی، ابتدا یاد می‌گیرید که از عملگرهای Rest و Spread برای جمع‌آوری Propهای استفاده‌نشده و ارسال آن‌ها به کامپوننت‌های تو در تو (Nested Components) استفاده کنید. سپس کامپوننتی ایجاد می‌کنید که از Prop داخلی children برای پیچیدن کامپوننت‌های تو در تو در JSX استفاده می‌کند، درست مانند تگ‌های HTML. در نهایت، کامپوننت‌ها را به عنوان Props ارسال می‌کنید تا پوشش‌هایی انعطاف‌پذیر بسازید که بتوانند کدهای JSX سفارشی را در قسمت‌های مختلفی از یک کامپوننت جای‌دهی کنند.

در طول این آموزش، شما کامپوننت‌هایی برای نمایش لیستی از داده‌های حیوانات به شکل کارت (Card) خواهید ساخت. شما یاد می‌گیرید که چگونه داده‌ها را تفکیک کرده و کامپوننت‌ها را بازنویسی (Refactor) کنید. در پایان این آموزش، شما یک اپلیکیشن کاری خواهید داشت که از تکنیک‌های پیشرفته Prop برای ایجاد کامپوننت‌های قابل استفاده مجدد استفاده می‌کند؛ کامپوننت‌هایی که با رشد و تغییر اپلیکیشن شما مقیاس‌پذیر و سازگار می‌مانند.

نکته: مرحله اول یک پروژه خالی را راه‌اندازی می‌کند که تمرینات این آموزش روی آن انجام می‌شود. اگر پروژه‌ای از قبل دارید و می‌خواهید مستقیماً به کار با Props بپردازید، می‌توانید از مرحله ۲ شروع کنید.

پیش‌نیازها
شما به یک محیط توسعه که Node.js روی آن اجرا می‌شود نیاز دارید. این آموزش با Node.js نسخه 20 و npm تست شده است. برای نصب آن روی سیستم‌عامل اوبونتو 24.04، می‌توانید از مستندات رسمی پارمین کلود (Parmin Cloud) برای نصب Node.js استفاده کنید.

در این آموزش، با استفاده از Create React App یک اپلیکیشن ایجاد خواهید کرد. همچنین از کامپوننت‌های React استفاده می‌کنید و داشتن درک اولیه‌ای از Props در React و دانش پایه‌ای جاوااسکریپت، HTML و CSS برای شما مفید خواهد بود.


مرحله ۱ — ایجاد یک پروژه خالی

در این مرحله، یک پروژه جدید با استفاده از Create React App ایجاد می‌کنید. سپس پروژه نمونه و فایل‌های مرتبط با آن را که هنگام راه‌اندازی نصب می‌شوند، حذف می‌کنید و یک ساختار فایل ساده برای سازماندهی کامپوننت‌های خود ایجاد می‌کنید.

برای شروع، در خط فرمان خود دستور زیر را برای نصب یک پروژه جدید اجرا کنید (برای نصب پکیج‌ها از میرور پارمین کلود استفاده شده است تا سرعت دانلود بالاتر باشد):

npx create-react-app wrapper-tutorial --registry=https://npm.parmincloud.ir

پس از اتمام نصب، وارد دایرکتوری آن شوید:

cd wrapper-tutorial

در یک تب یا پنجره جدید ترمینال، پروژه را با اسکریپت شروع Create React App اجرا کنید. مرورگر با اعمال تغییرات به‌طور خودکار رفرش می‌شود، بنابراین این اسکریپت را در حال اجرا نگه دارید:

npm start

یک سرور محلی در حال اجرا خواهید داشت. اگر پروژه در پنجره مرورگر باز نشد، آن را با آدرس http://localhost:3000/ باز کنید. اگر این را از یک سرور ریموت (مانند سرورهای پارمین کلود) اجرا می‌کنید، آدرس به صورت http://your_domain:3000 خواهد بود.

شما قرار است مجموعه کاملاً جدیدی از کامپوننت‌های سفارشی را بسازید، بنابراین باید با پاک کردن کدهای پیش‌فرض شروع کنید. فایل src/App.js را باز کنید:

nano src/App.js

فایلی شبیه به این خواهید دید:

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

خط import logo from './logo.svg'; را حذف کنید. سپس تمام موارد موجود در دستور return را جایگزین کنید تا یک مجموعه تگ خالی <></> برگرداند. کد نهایی به شکل زیر خواهد بود:

wrapper-tutorial/src/App.js

import React from 'react';
import './App.css';

function App() {
  return <></>;
}

export default App;

فایل را ذخیره کرده و ببندید. در نهایت، لوگوی پیش‌فرض را حذف کنید:

rm src/logo.svg

اکنون یک ساختار فایل ساده ایجاد کنید. یک دایرکتوری به نام components در مسیر src ایجاد کنید:

mkdir src/components

یک دایرکتوری برای App ایجاد کنید و فایل‌های App را به آن منتقل کنید:

mkdir src/components/App
mv src/App.* src/components/App

سپس، مسیر Import در فایل index.js را به‌روزرسانی کنید:

nano src/index.js

تغییر مشخص شده را اعمال کنید تا دستور Import به فایل صحیح اشاره کند:

wrapper-tutorial/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App/App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
serviceWorker.unregister();

مرحله ۲ — جمع‌آوری Propهای استفاده‌نشده با ...props

در این مرحله، کامپوننتی برای نمایش مجموعه‌ای از داده‌های مربوط به حیوانات ایجاد می‌کنید. برای اتصال کامپوننت والد (Parent) به کامپوننت فرزند (Child)، از عملگرهای Rest و Spread برای ارسال Propهای استفاده‌نشده از والد به فرزند استفاده می‌کنید، بدون اینکه والد نیاز داند نام یا نوع Propها را بداند.

ایجاد کامپوننت AnimalCard
ابتدا، فایلی حاوی داده‌های حیوانات ایجاد کنید:

nano src/components/App/data.js

داده‌های زیر را اضافه کنید:

src/components/App/data.js

export default [
  {
    name: 'Lion',
    scientificName: 'Panthero leo',
    size: 140,
    diet: ['meat']
  },
  {
    name: 'Gorilla',
    scientificName: 'Gorilla beringei',
    size: 205,
    diet: ['plants', 'insects']
  },
  {
    name: 'Zebra',
    scientificName: 'Equus quagga',
    size: 322,
    diet: ['plants'],
  }
]

سپس دایرکتوری کامپوننت AnimalCard را ایجاد کرده و فایل آن را باز کنید:

mkdir src/components/AnimalCard
nano src/components/AnimalCard/AnimalCard.js

کامپوننتی اضافه کنید که name، diet و size را به عنوان Prop دریافت و نمایش دهد:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js

import React from 'react';
import PropTypes from 'prop-types';

export default function AnimalCard({ diet, name, size }) {
  return(
    <div>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <div>{diet.join(', ')}.</div>
    </div>
  )
}

AnimalCard.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

در اینجا شما Propها را در لیست پارامترها جدا کرده (Destructuring) و داده‌ها را در یک div نمایش می‌دهید. فایل را ذخیره کنید.

اکنون باید این کامپوننت و داده‌ها را در App.js با هم ترکیب کنید:

nano src/components/App/App.js

خطوط برجسته شده را به App.js اضافه کنید:

wrapper-tutorial/src/components/App/App.js

import React from 'react';
import './App.css';

import animals from './data';
import AnimalCard from '../AnimalCard/AnimalCard';

function App() {
  return (
    <div className="wrapper">
      {animals.map(animal =>
        <AnimalCard
          diet={animal.diet}
          key={animal.name}
          name={animal.name}
          size={animal.size}
        />
      )}
    </div>
  );
}

export default App;

برای اضافه کردن استایل، فایل App.css را باز کنید:

nano src/components/App/App.css

کدهای پیش‌فرض را حذف کرده و استایل‌های زیر را اضافه کنید:

prop-tutorial/src/components/App/App.css

.wrapper {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    padding: 20px;
}

ایجاد کامپوننت Details
اکنون بیایید کامپوننت دیگری به نام AnimalDetails بسازیم تا داده‌های رژیم غذایی را به ایموجی تبدیل کند. دایرکتوری و فایل را ایجاد کنید:

mkdir src/components/AnimalDetails
nano src/components/AnimalDetails/AnimalDetails.js

کامپوننت کوچکی که رژیم غذایی را به ایموجی نمایش می‌دهد اضافه کنید:

wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.js

import React from 'react';
import PropTypes from 'prop-types';
import './AnimalDetails.css';

function convertFood(food) {
  switch(food) {
    case 'insects':
      return '🐜';
    case 'meat':
      return '🍖';
    case 'plants':
    default:
      return '🌱';
  }
}

export default function AnimalDetails({ diet }) {
  return(
    <div className="details">
      <h4>Details:</h4>
      <div>
        Diet: {diet.map(food => convertFood(food)).join(' ')}
      </div>
    </div>
  )
}

AnimalDetails.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
}

فایل CSS آن را نیز ایجاد کنید:

nano src/components/AnimalDetails/AnimalDetails.css
.details {
    border-top: gray solid 1px;
    margin: 20px 0;
}

اکنون می‌توانید AnimalDetails را به AnimalCard اضافه کنید. فایل AnimalCard.js را باز کنید:

nano src/components/AnimalCard/AnimalCard.js

دستور diet.join را با کامپوننت جدید جایگزین کنید:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js

import React from 'react';
import PropTypes from 'prop-types';
import AnimalDetails from '../AnimalDetails/AnimalDetails';

export default function AnimalCard({ diet, name, size }) {
  return(
    <div>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        diet={diet}
      />
    </div>
  )
}

AnimalCard.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

عبور دادن جزئیات از یک کامپوننت با ...props
کامپوننت‌ها به خوبی با هم کار می‌کنند، اما یک ناکارآمدی کوچک در AnimalCard وجود دارد. شما به صراحت diet را از آرگومان Propها استخراج می‌کنید اما از آن استفاده نمی‌کنید، بلکه آن را به کامپوننت فرزند ارسال می‌کنید. راه بهتر این است که Propهای استفاده‌نشده را در AnimalCard جمع‌آوری کرده و مستقیماً به AnimalDetails ارسال کنید.

برای این کار از عملگر Rest استفاده می‌کنید. فایل AnimalCard.js را باز کنید:

nano src/components/AnimalCard/AnimalCard.js

داخل آن، diet را از آبجکت جدا شده (Destructured) حذف کنید و بقیه Propها را در متغیری به نام props جمع‌آوری کنید. سپس آن‌ها را مستقیماً به AnimalDetails ارسال کنید:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js

import React from 'react';
import PropTypes from 'prop-types';
import AnimalDetails from '../AnimalDetails/AnimalDetails';

export default function AnimalCard({ name, size, ...props }) {
  return(
    <div>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        {...props}
      />
    </div>
  )
}

AnimalCard.propTypes = {
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

توجه کنید که شما می‌توانید PropType مربوط به diet را حذف کنید، زیرا در این کامپوننت از آن استفاده نمی‌شود. فایل را ذخیره کنید.

برای دیدن انعطاف‌پذیری این روش، بیایید scientificName را از طریق AnimalCard به AnimalDetails ارسال کنیم. فایل App.js را باز کنید و scientificName را به عنوان Prop اضافه کنید:

nano src/components/App/App.js

wrapper-tutorial/src/components/App/App.js

import React from 'react';
import './App.css';

import animals from './data';
import AnimalCard from '../AnimalCard/AnimalCard';

function App() {
  return (
    <div className="wrapper">
      {animals.map(animal =>
        <AnimalCard
          diet={animal.diet}
          key={animal.name}
          name={animal.name}
          size={animal.size}
          scientificName={animal.scientificName}
        />
      )}
    </div>
  );
}

export default App;

سپس فایل AnimalDetails را باز کنید تا Prop جدید را مصرف کنید:

nano src/components/AnimalDetails/AnimalDetails.js

wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.js

import React from 'react';
...
export default function AnimalDetails({ diet, scientificName }) {
  return(
    <div className="details">
      <h4>Details:</h4>
      <div>
        Scientific Name: {scientificName}.
      </div>
      <div>
        Diet: {diet.map(food => convertFood(food)).join(' ')}
      </div>
    </div>
  )
}

AnimalDetails.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
  scientificName: PropTypes.string.isRequired,
}

فایل را ذخیره کنید. جزئیات جدید بدون هیچ تغییری در کامپوننت AnimalCard نمایش داده می‌شوند.


مرحله ۳ — ایجاد کامپوننت‌های پوششی با children

در این مرحله، یک کامپوننت پوششی ایجاد می‌کنید که می‌تواند گروهی از کامپوننت‌های ناشناخته را به عنوان Prop دریافت کند. React یک Prop داخلی به نام children ارائه می‌دهد که تمام کامپوننت‌های فرزند را جمع‌آوری می‌کند.

برای شروع، کامپوننت جدیدی به نام Card بسازید. این یک کامپوننت پوششی برای ایجاد یک استایل استاندارد خواهد بود:

mkdir src/components/Card
nano src/components/Card/Card.js

کامپوننتی ایجاد کنید که children و title را به عنوان Prop دریافت کرده و آن‌ها را در یک div بپیچد:

wrapper-tutorial/src/components/Card/Card.js

import React from 'react';
import PropTypes from 'prop-types';
import './Card.css';

export default function Card({ children, title }) {
  return(
    <div className="card">
      <div className="card-details">
        <h2>{title}</h2>
      </div>
      {children}
    </div>
  )
}

Card.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.element), 
    PropTypes.element.isRequired
  ]),
  title: PropTypes.string.isRequired,
}

فایل CSS آن را ایجاد کنید:

nano src/components/Card/Card.css
.card {
    border: black solid 1px;
    margin: 10px;
    padding: 10px;
    width: 200px;
}

.card-details {
    border-bottom: gray solid 1px;
    margin-bottom: 20px;
}

اکنون باید از این کامپوننت استفاده کنید. فایل AnimalCard.js را باز کنید:

nano src/components/AnimalCard/AnimalCard.js

برخلاف سایر Propها، نیازی نیست children را به صراحت پاس ندهید. در عوض، JSX را مانند عناصر فرزند HTML قرار دهید:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js

import React from 'react';
import PropTypes from 'prop-types';
import Card from '../Card/Card';
import AnimalDetails from '../AnimalDetails/AnimalDetails';

export default function AnimalCard({ name, size, ...props }) {
  return(
    <Card title="Animal">
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        {...props}
      />
    </Card>
  )
}

AnimalCard.propTypes = {
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

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


مرحله ۴ — ارسال کامپوننت‌ها به عنوان Props

در این مرحله، کامپوننت Card را طوری تغییر می‌دهید که کامپوننت‌های دیگر را به عنوان Props دریافت کند. این کار به کامپوننت شما انعطاف‌پذیری بیشتری می‌دهد تا بتواند کدهای JSX را در چندین مکان مختلف نمایش دهد.

بیایید کامپوننت Card را طوری تغییر دهیم که یک عنصر React دلخواه به نام details را دریافت کند. فایل Card را باز کنید:

nano src/components/Card/Card.js

یک Prop جدید به نام details اضافه کنید و آن را زیر تگ <h2> قرار دهید:

wrapper-tutorial/src/components/Card/Card.js

import React from 'react';
import PropTypes from 'prop-types';
import './Card.css';

export default function Card({ children, details, title }) {
  return(
    <div className="card">
      <div className="card-details">
        <h2>{title}</h2>
        {details}
      </div>
      {children}
    </div>
  )
}

Card.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.element), 
    PropTypes.element.isRequired
  ]),
  details: PropTypes.element,
  title: PropTypes.string.isRequired,
}

Card.defaultProps = {
  details: null,
}

برای اختیاری کردن این Prop، یک مقدار پیش‌فرض null اضافه کرده‌ایم. فایل را ذخیره کنید.

اکنون بیایید AnimalCard را تغییر دهیم تا جزئیات جدیدی را به Card ارسال کند. فایل AnimalCard را باز کنید:

nano src/components/AnimalCard/AnimalCard.js

از آنجایی که کامپوننت Card از قبل از children استفاده می‌کند، باید کدهای JSX جدید را به عنوان Prop ارسال کنید. عبارت “Mammal” (پستاندار) را به کارت اضافه کنید:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js

import React from 'react';
...

export default function AnimalCard({ name, size, ...props }) {
  return(
    <Card title="Animal" details={<em>Mammal</em>}>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        {...props}
      />
    </Card>
  )
}
...

فایل را ذخیره کنید. عبارت اضافه شده را خواهید دید.

این Prop بسیار قدرتمند است زیرا می‌تواند JSX با هر اندازه‌ای را بپذیرد. همچنین نیازی نیست حتماً JSX باشد؛ اگر نشانه‌گذاری پیچیده‌ای دارید، می‌توانید یک کامپوننت جداگانه ایجاد کرده و آن کامپوننت را به عنوان Prop ارسال کنید. برای مشاهده این موضوع، AnimalDetails را به Prop مربوط به details ارسال کنید:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js

import React from 'react';
...

export default function AnimalCard({ name, size, ...props }) {
  return(
    <Card
      title="Animal"
      details={
        <AnimalDetails
          {...props}
        />
      }
    >
      <h3>{name}</h3>
      <div>{size}kg</div>
    </Card>
  )
}
...

فایل را ذخیره کنید. جزئیات اکنون در بالای کارت ظاهر می‌شوند. شما به یک کامپوننت Card دست دارید که می‌تواند JSX سفارشی را در قسمت‌های مختلف قرار دهد. شما محدود به یک Prop نیستید؛ می‌توانید عناصر را به هر تعداد Prop که بخواهید ارسال کنید.


نتیجه‌گیری
شما انواع مختلفی از کامپوننت‌های پوششی ایجاد کرده‌اید که می‌توانند داده‌ها را به صورت انعطاف‌پذیر نمایش دهند و در عین حال ظاهر و ساختار قابل پیش‌بینی داشته باشند. شما کامپوننت‌هایی ساختید که می‌توانند Propهای ناشناخته را جمع‌آوری کرده و به کامپوننت‌های تو در تو ارسال کنند. همچنین از Prop داخلی children برای ایجاد کامپوننت‌های پوششی استفاده کردید. در نهایت، کامپوننتی ساختید که می‌تواند JSX یا کامپوننت‌های React را به عنوان Prop دریافت کند.

کامپوننت‌های پوششی به شما این امکان را می‌دهند که با شرایط ناشناخته سازگار شوید و در عین حال از استفاده مجدد کد و یکپارچگی آن به حداکثر برسید.

از همراهی شما با پارمین کلود سپاسگزاریم.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا