تفکر به سبک ری‌اکت

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

Start with the mockup

تصور کنید شما همین الان دیتایی از طریق ای پی آی و فایل ماکآپ را از دیزاینر دارید. فایل دیتا٬ اطلاعاتی مثل فایل زیر را شامل می‌شود:

[
{"category": "Fruits", "price": "$1", "stocked": true, "name": "Apple"},
{"category": "Fruits", "price": "$1", "stocked": true, "name": "Dragonfruit"},
{
"category": "Fruits",
"price": "$2",
"stocked": false,
"name": "Passionfruit"
},
{"category": "Vegetables", "price": "$2", "stocked": true, "name": "Spinach"},
{
"category": "Vegetables",
"price": "$4",
"stocked": false,
"name": "Pumpkin"
},
{"category": "Vegetables", "price": "$1", "stocked": true, "name": "Peas"}
]

خروجی فایل شبیه تصویر زیر است:

برای پیاده سازی هر دیزاینی ما این پنج قدم را برمی داریم:

Step 1: Break the UI into a component hierarchy

اول دور هر کامپوننت و زیر کامپوننت در دیزاین خط بکشید و برای آنها اسم بگذارید. اگر با یک طراح همکاری می‌کنید، ممکن است او این کامپوننت‌ها را در ابزار طراحی خود قبلاً نامگذاری کرده باشد. از او بپرسید! بسته به بکگراند شما، می‌توانید به روش‌های مختلف درباره‌ی تقسیم یک طراحی به کامپوننت‌ها فکر کنید.

  • برنامه‌نویسی—از تکنیک‌های مشابه برای تصمیم‌گیری در مورد اینکه آیا باید یک تابع یا شیء جدید ایجاد کنید استفاده کنید یکی از این تکنیک‌ها اصل single responsibility است، به این معنی که یک کامپوننت بهتر است تنها یک کار را انجام دهد. اگر کامپوننت به طور مداوم بزرگتر شود، باید به زیرکامپوننت‌های کوچکتر تجزیه شود. single responsibility principle

  • CSS—بررسی کنید که برای چه عناصری از کلاس‌ها استفاده خواهید کرد. (با این حال، کامپوننت‌ها کمتر جزئیات ریز دارند.)

  • design—در نظر بگیرید که چگونه لایه‌های طراحی را سازماندهی خواهید کرد اگر فایل جیسون شما ساختار مناسبی داشته باشد، اغلب متوجه می شوید که به طور طبیعی با ساختار کامپوننت‌های رابط کاربری شما همخوانی دارد. این به این دلیل است که مدل‌های رابط کاربری و داده اغلب همان معماری اطلاعاتی را دارند - به اصطلاح، همان شکل. رابط کاربری خود را به کامپوننت‌ها تقسیم کنید، به گونه‌ای که هر کامپوننت با یک قسمت از مدل داده شما مطابقت داشته باشد

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

در این صفحه پنج کامپوننت هست:

  1. FilterableProductTable به رنگ خاکستری که شامل تمام اپلیکیشن است
  2. SearchBar به رنگ ابی. برای دریافت ورودی از کاربر
  3. ProductTable به رنگ بنفش٬ شامل فیلترها و لیست داده ی خروجی با توجه به ورودی کاربر
  4. ProductCategoryRow به رنگ سبز نشانده‌دهنده‌ی هدینگ برای هر گروه
  5. ProductRow به رنگ زرد٬ برای نشان دادن هر ردیف از هر محصول

اگر به کامپوننت ۳(جدول محصولات) نگاه کنید، می‌بینید که هدر جدول (شامل برچسب‌های “نام” و “قیمت”) قسمت جداگانه‌ای نیست. این مسئله سلیقه‌ای است و می‌توانید هر دو را انتخاب کنید. در این مثال، این بخش جزء کامپوننت ۳است زیرا داخل لیست کامپوننت ۳ ظاهر می‌شود. با این حال، اگر این هدر به طور پیچیده‌تری رشد کند (مثلاً اگر مرتب‌سازی را اضافه کنید)، می‌توانید آن را به عنوان یک کامپوننت جداگانه با نام(هدر جدول محصولات) جابجا کنید.

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

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

Step 2: Build a static version in React

هم‌اکنون که سلسله‌مراتب کامپوننت‌ها را دارید، زمان پیاده‌سازی برنامهٔ است. روش بسیار ساده ایجاد یک نسخه استاتیکی است که رابط کاربری را از مدل دادهٔ خود رندر می‌کند بدون افزودن هرگونه تعامل… برای شروع! اغلب آسانتر است که ابتدا نسخهٔ استاتیک را بسازید و سپس تعامل را اضافه کنید. ساختن یک نسخهٔ استاتیک نیازمند تایپ کردن زیاد و فکر نکردن است، اما افزودن تعاملات نیازمند فکر زیاد و تایپ کمتری است. برای ساخت نسخه‌ای استاتیک از برنامه خود که مدل داده‌های شما را نمایش دهد، شما باید کامپوننت‌هایی بسازید که از کامپوننت‌های دیگر استفاده کنند props. و با استفاده از پراپ‌ها داده را منتقل کنند. پراپ‌ها یک روش برای انتقال داده از والد به فرزند هستند. (اگر با مفهوم استیت آشنایی دارید، به هیچ وجه از وضعیت برای ساخت این نسخه استاتیک استفاده نکنید. استیت فقط برای تعاملاتی است که داده در طول زمان تغییر می‌کند و در این نسخه استاتیک برنامه، به آن نیاز ندارید)

state لینک به

function ProductCategoryRow({ category }) {
  return (
    <tr>
      <th colSpan="2">{category}</th>
    </tr>
  );
}

function ProductRow({product}) {
  const name = product.stocked ? (
    product.name
  ) : (
    <span style={{color: 'red'}}>{product.name}</span>
  );

  return (
    <tr>
      <td>{name}</td>
      <td>{product.price}</td>
    </tr>
  );
}

function ProductTable({products}) {
  const rows = [];
  let lastCategory = null;

  products.forEach((product) => {
    if (product.category !== lastCategory) {
      rows.push(
        <ProductCategoryRow
          category={product.category}
          key={product.category}
        />
      );
    }
    rows.push(<ProductRow product={product} key={product.name} />);
    lastCategory = product.category;
  });

  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
}

function SearchBar() {
  return (
    <form>
      <input type="text" placeholder="Search..." />
      <label>
        <input type="checkbox" /> Only show products in stock
      </label>
    </form>
  );
}

function FilterableProductTable({products}) {
  return (
    <div>
      <SearchBar />
      <ProductTable products={products} />
    </div>
  );
}

const PRODUCTS = [
  {category: 'Fruits', price: '$1', stocked: true, name: 'Apple'},
  {category: 'Fruits', price: '$1', stocked: true, name: 'Dragonfruit'},
  {category: 'Fruits', price: '$2', stocked: false, name: 'Passionfruit'},
  {category: 'Vegetables', price: '$2', stocked: true, name: 'Spinach'},
  {category: 'Vegetables', price: '$4', stocked: false, name: 'Pumpkin'},
  {category: 'Vegetables', price: '$1', stocked: true, name: 'Peas'},
];

export default function App() {
  return <FilterableProductTable products={PRODUCTS} />;
}

اگر این کد برایتان ترسناک به نظر می‌رسد، ابتدا با راهنمای سریع آشنا شوید! Quick Start

بعد ساختن کامپوننت‌های خود، شما یک کتابخانه از کامپوننت‌های قابل استفاده مجدد خواهید داشت که مدل داده‌های شما را نمایش می‌دهند. به دلیل اینکه این برنامه از نوع استاتیک است، کامپوننت‌ها فقط JSX را بازمی‌گردانند. کامپوننت‌، در بالای سلسله‌مراتب (FilterableProductTable) مدل داده‌های شما را به عنوان یک ویژگی دریافت می‌کند. این با نام “جریان داده یک‌طرفه” شناخته می‌شود زیرا داده‌ها از کامپوننت‌ه بالاتر در سلسله‌مراتب به کامپوننت‌های پایین‌تر در درخت جریان می‌یابند

Pitfall

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

Step 3: Find the minimal but complete representation of UI state

برای ایجاد تعامل در رابط کاربری، باید به کاربران امکان تغییر مدل داده‌های زیرین خود را بدهید. برای این کار از وضعیت (state) استفاده خواهید کرد.

وضعیت را به عنوان مجموعه حداقلی از داده‌های تغییر پذیری تصور کنید که برنامه شما نیاز دارد که به خاطر بسپارد. مهم‌ترین اصل در ساختاردهی وضعیت، حفظ اصل DRY (Don’t Repeat Yourself) است. DRY کمترین حالتی که نیازهای برنامه‌ی شما از state دارد را تعیین کنید و سایر موارد را به صورت درخواستی محاسبه کنید. به عنوان مثال، اگر در حال ساختن یک لیست خرید هستید، می‌توانید موارد را به صورت یک آرایه در state ذخیره کنید. اگر می‌خواهید تعداد موارد در لیست را نشان دهید، تعداد موارد را به عنوان یک مقدار وضعیت جدید درنظر نگیرید - در عوض، طول آرایه‌تان را بخوانید.

حالا به تمام قسمت های دیتا در این اپلیکیشن نگاه کنید:

1-FilterableProductTable 2-SearchBar 3-ProductTable 4-ProductCategoryRow 5-ProductRow

کدام یکی از این‌ها state هستند؟ اول آنهایی که نیستند را مشخص میکنیم:

  • ایا در طول زمان بدون تغییر خواهد ماند؟ اگر بله پس استیت نیست- آیا از یک کامپوننت والد با پراپس پاس داده شده؟ اگر بله پس استیت نیست- ایا میتوانید آن را خودتان به دست بیاورید؟ براساس استیت فعلی یا پراپس کامپوننت اگر بله پس قطعا استیت نیست!-

  • هرچه به جز این حالت ها باشد٬ استیت است
    state

    یکبار دیگر باهم مرور کنیم:

    آیا از یک کامپوننت والد با پراپس پاس داده شده؟ اگر بله پس استیت نیست- ایا میتوانید آن را خودتان به دست بیاورید؟ براساس استیت فعلی یا پراپس کامپوننت اگر بله پس قطعا استیت نیست!-

    1. اگر اصل لیست محصولات به عنوان پراپس پاس داده شده٬ پس استیت یا وضعیت نیست
    2. متن جستجو به نظر استیت است چرا که به مرور زمان تغییر میکند و ما نمیتوانیم آن را محاسبه کنیم
    3. مقدار چک باکس یک استیت است زیرا محاسبه نمیشود و به مرور زمان تغییر میکند
    4. لیست محصولات فیلتر شده استیت نیست٬ زیرا میتوانیم آن را حساب کنیم! تنها به لیست اولیه و مقداری که کاربر جستجو کرده به همراه مقدار تکس باکس نیاز داریم

    یعنی ما فقط متن جستجو شده و مقدار تکست باکس را به عنوان استیت نگه میداریم

    Deep Dive

    Props vs State

    دو نوع مدل دیتا در ری اکت داریم: وضعیت (state) و (props). این دو باهم تفاوت زیادی دارند:

    پراپس شبیه ارگیومنت هایی که به تایع پاس میدهید . آنها به کامپوننت والد اجازه می دهند که دیتا را به فرزند خود پاس دهد و ظاهر آن را دستکاری کند. مثلا: یک form میتواند رنگ را به عنوان پراپس به کامپوننت ‍‍‍Button پاس دهد.

    • استیت مثل حافظه ی یک کامپوننت است به کامپوننت این امکان را میدهد تا تغییرات اطلاعات را ثبت کند و به آنها پاسخ مناسب دهد. مثلا کامپوننت دکمه میتواند تغییراتی مثل رفتن موس روی خود را در استیت ثبت کند

    پراپ‌ها (Props) و استیت‌ها (State) متفاوت هستند، اما با هم کار می‌کنند. یک کامپوننت والد (Parent) اغلب برخی اطلاعات را در استیت نگه می‌دارد (تا بتواند آن‌ها را تغییر دهد) و به عنوان پراپ به کامپوننت‌های فرزند (Child) ارسال می‌کند. اگر هنوز تفاوت بین آن‌ها برای اولین بار کاملاً روشن نشده باشد، مشکلی ندارد. برای کامل شدن درک این موضوع، نیاز به تمرین کمی دارد!

    Step 4: Identify where your state should live

    بعد از شناسایی داده‌های حداقلی مرتبط با برنامه‌ی شما، نیاز دارید تعیین کنید که کدام کامپوننت مسئول تغییر این داده‌هاست، یا به عبارتی، مالک (owns) این داده‌ها است. به یاد داشته باشید: ری‌اکت از جریان داده‌ی یک طرفه استفاده می‌کند و داده‌ها را از کامپوننت والد به کامپوننت‌های فرزند در سلسله‌مراتب ارسال می‌کند. ممکن است اولیه‌اش که کدام کامپوننت باید مالک داده‌هایی باشد که در اختیار دارد، به وضوح نباشد. اگر تازه با این مفهوم آشنا شده‌اید، ممکن است چالش برانگیز باشد، اما با دنبال کردن این

    برای هر قطعه از استیت (state) در برنامه‌ی شما:

    تمام کامپوننت‌هایی که بر اساس آن استیت چیزی را رندر می‌کنند را شناسایی کنید. پدر مشترک نزدیک آن‌ها را پیدا کنید - یعنی یک کامپوننت بالاتر از همه‌ی آن‌ها در سلسله‌مراتب. تصمیم بگیرید که استیت باید در کجا قرار بگیرد: اغلب، می‌توانید استیت را مستقیماً در کامپوننت مشترک آن‌ها قرار دهید. همچنین می‌توانید استیت را در یکی از کامپوننت‌های بالاتر از کامپوننت مشترک آن‌ها قرار دهید. اگر نمی‌توانید کامپوننتی پیدا کنید که استیت را به صورت منطقی مدیریت کند، می‌توانید یک کامپوننت جدید را که تنها برای نگه‌داری استیت ایجاد شده باشد بسازید و آن را در جایی در سلسله‌مراتب بالاتر از کامپوننت مشترک قرار دهید. در مرحله‌ی قبلی، دو قطعه از استیت را در این برنامه پیدا کردید: متن ورودی جستجو و مقدار چک‌باکس. در این مثال، همیشه همراه یکدیگر ظاهر می‌شوند، بنابراین منطقی است که آن‌ها را در یک جایگاه قرار دهید

    حالا بیایید از راهبرد ما مسئله را بررسی کنیم

    شناسایی کامپوننت‌هایی که از استیت استفاده می‌کنند: ***

    ProductTable نیاز دارد که لیست محصولات را بر اساس این استیت (متن جستجو و مقدار چک‌باکس) فیلتر کند. *** SearchBar نیاز دارد که این استیت (متن جستجو و مقدار چک‌باکس) را نمایش دهد. *** یافتن پدر مشترک آن‌ها: اولین کامپوننت والد که هر دو کامپوننت با هم به اشتراک می‌گذارند، FilterableProductTable است. *** تصمیم برای محل قرارگیری استیت: ما مقادیر متن فیلتر و وضعیت چک‌باکس را در FilterableProductTable نگه‌داری خواهیم کرد. *** بنابراین مقادیر استیت در FilterableProductTable قرار خواهند گرفت.

    استیت را درون کامپوننتی که useState() Hook. دارد اضافه کنید. Hookها توابه خاصی هستند که به شما اجازه می‌دهند تا قدرت ری‌اکت را درونشان قلاب کنید. دو متغییر استیت در بالای FilterableProductTableتعریف و مقدار پیش‌فرض آنها را مشخص کنید:

    function FilterableProductTable({ products }) {
    const [filterText, setFilterText] = useState('');
    const [inStockOnly, setInStockOnly] = useState(false);

    سپس filterText و inStockOnly را به ProductTable و SearchBar به عنوان prop ارسال کنید:

    <div>
    <SearchBar filterText={filterText} inStockOnly={inStockOnly} />
    <ProductTable
    products={products}
    filterText={filterText}
    inStockOnly={inStockOnly}
    />
    </div>

    شما می‌توانید شروع به دیدن نحوه‌ی رفتار برنامه‌ی خود کنید. مقدار اولیه filterText را از useState(”) به useState(‘fruit’) در کد محیط آزمایشی زیر ویرایش کنید. خواهید دید که هر دو متن ورودی جستجو و جدول به‌روزرسانی می‌شوند.

    import { useState } from 'react';
    
    function FilterableProductTable({products}) {
      const [filterText, setFilterText] = useState('');
      const [inStockOnly, setInStockOnly] = useState(false);
    
      return (
        <div>
          <SearchBar filterText={filterText} inStockOnly={inStockOnly} />
          <ProductTable
            products={products}
            filterText={filterText}
            inStockOnly={inStockOnly}
          />
        </div>
      );
    }
    
    function ProductCategoryRow({category}) {
      return (
        <tr>
          <th colSpan="2">{category}</th>
        </tr>
      );
    }
    
    function ProductRow({product}) {
      const name = product.stocked ? (
        product.name
      ) : (
        <span style={{color: 'red'}}>{product.name}</span>
      );
    
      return (
        <tr>
          <td>{name}</td>
          <td>{product.price}</td>
        </tr>
      );
    }
    
    function ProductTable({products, filterText, inStockOnly}) {
      const rows = [];
      let lastCategory = null;
    
      products.forEach((product) => {
        if (product.name.toLowerCase().indexOf(filterText.toLowerCase()) === -1) {
          return;
        }
        if (inStockOnly && !product.stocked) {
          return;
        }
        if (product.category !== lastCategory) {
          rows.push(
            <ProductCategoryRow
              category={product.category}
              key={product.category}
            />
          );
        }
        rows.push(<ProductRow product={product} key={product.name} />);
        lastCategory = product.category;
      });
    
      return (
        <table>
          <thead>
            <tr>
              <th>Name</th>
              <th>Price</th>
            </tr>
          </thead>
          <tbody>{rows}</tbody>
        </table>
      );
    }
    
    function SearchBar({filterText, inStockOnly}) {
      return (
        <form>
          <input type="text" value={filterText} placeholder="Search..." />
          <label>
            <input type="checkbox" checked={inStockOnly} /> Only show products in
            stock
          </label>
        </form>
      );
    }
    
    const PRODUCTS = [
      {category: 'Fruits', price: '$1', stocked: true, name: 'Apple'},
      {category: 'Fruits', price: '$1', stocked: true, name: 'Dragonfruit'},
      {category: 'Fruits', price: '$2', stocked: false, name: 'Passionfruit'},
      {category: 'Vegetables', price: '$2', stocked: true, name: 'Spinach'},
      {category: 'Vegetables', price: '$4', stocked: false, name: 'Pumpkin'},
      {category: 'Vegetables', price: '$1', stocked: true, name: 'Peas'},
    ];
    
    export default function App() {
      return <FilterableProductTable products={PRODUCTS} />;
    }
    
    

    توجه کنید که هنوز ویرایش فرم کار نمی‌کند. یک خطا در کنسول محیط آزمایشی بالا وجود دارد که دلیل آن را توضیح می‌دهد.

    Console
    You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field.

    در محیط آزمایشی بالا، ProductTable و SearchBar مقادیر filterText و inStockOnly را به عنوان پراپ (props) می‌خوانند تا جدول، ورودی و چک‌باکس را رندر کنند. به عنوان مثال، اینجا نحوه‌ی پر کردن مقدار ورودی در SearchBar نمایش داده شده است:

    function SearchBar({ filterText, inStockOnly }) {
    return (
    <form>
    <input
    type="text"
    value={filterText}
    placeholder="Search..."/>

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

    Step 5: Add inverse data flow

    در حال حاضر برنامه‌ی شما با پراپ‌ها و استیت‌ها که از بالا به پایین سلسله‌مراتب به‌خوبی رندر می‌شود. اما برای تغییر استیت بر اساس ورودی کاربر، شما نیاز به پشتیبانی از جریان داده به سمت دیگر دارید: کامپوننت‌های فرم (form components) که در عمق سلسله‌مراتب هستند باید استیت را در FilterableProductTable به‌روزرسانی دهنده.

    ری‌اکت این جریان داده را صریحاً اعلام می‌کند، اما نیاز به تایپ کردن بیشتری نسبت به ورودی داده دوطرفه دارد. اگر در مثال بالا سعی کنید در ورودی تایپ کنید یا چک‌باکس را انتخاب کنید، مشاهده خواهید کرد که ری‌اکت ورودی‌های شما را نادیده می‌گیرد. این عمدی است. با نوشتن <input value={filterText} />، شما value پراپ ورودی را به طور دائمی برابر با استیت filterText قرار داده‌اید که از FilterableProductTable به آن منتقل می‌شود. از آنجایی که استیت filterText هرگز تنظیم نمی‌شود، ورودی هرگز تغییر نمی‌کند.

    شما می‌خواهید طوری عمل کنید که هر زمان کاربر ورودی‌های فرم را تغییر دهد، استیت به‌روزرسانی شود تا تغییرات نمایان شود. استیت توسط FilterableProductTable مدیریت می‌شود، بنابراین تنها این کامپوننت می‌تواند توابع setFilterText و setInStockOnly را فراخوانی کند. برای اجازه دادن به SearchBar برای به‌روزرسانی استیت FilterableProductTable، شما باید این توابع را به SearchBar ارسال کنید.

    function FilterableProductTable({ products }) {
    const [filterText, setFilterText] = useState('');
    const [inStockOnly, setInStockOnly] = useState(false);

    return (
    <div>
    <SearchBar
    filterText={filterText}
    inStockOnly={inStockOnly}
    onFilterTextChange={setFilterText}
    onInStockOnlyChange={setInStockOnly} />

    شما روی‌داد onChange را درون SearchBar اضافه خواهید کرد و استیت پدر را از آنجا تنظیم می‌کنید.

    function SearchBar({
    filterText,
    inStockOnly,
    onFilterTextChange,
    onInStockOnlyChange
    }) {
    return (
    <form>
    <input
    type="text"
    value={filterText}
    placeholder="Search..."
    onChange={(e) => onFilterTextChange(e.target.value)}
    />
    <label>
    <input
    type="checkbox"
    checked={inStockOnly}
    onChange={(e) => onInStockOnlyChange(e.target.checked)}

    حالا اپلیکیشن کار میکند !

    import { useState } from 'react';
    
    function FilterableProductTable({products}) {
      const [filterText, setFilterText] = useState('');
      const [inStockOnly, setInStockOnly] = useState(false);
    
      return (
        <div>
          <SearchBar
            filterText={filterText}
            inStockOnly={inStockOnly}
            onFilterTextChange={setFilterText}
            onInStockOnlyChange={setInStockOnly}
          />
          <ProductTable
            products={products}
            filterText={filterText}
            inStockOnly={inStockOnly}
          />
        </div>
      );
    }
    
    function ProductCategoryRow({category}) {
      return (
        <tr>
          <th colSpan="2">{category}</th>
        </tr>
      );
    }
    
    function ProductRow({product}) {
      const name = product.stocked ? (
        product.name
      ) : (
        <span style={{color: 'red'}}>{product.name}</span>
      );
    
      return (
        <tr>
          <td>{name}</td>
          <td>{product.price}</td>
        </tr>
      );
    }
    
    function ProductTable({products, filterText, inStockOnly}) {
      const rows = [];
      let lastCategory = null;
    
      products.forEach((product) => {
        if (product.name.toLowerCase().indexOf(filterText.toLowerCase()) === -1) {
          return;
        }
        if (inStockOnly && !product.stocked) {
          return;
        }
        if (product.category !== lastCategory) {
          rows.push(
            <ProductCategoryRow
              category={product.category}
              key={product.category}
            />
          );
        }
        rows.push(<ProductRow product={product} key={product.name} />);
        lastCategory = product.category;
      });
    
      return (
        <table>
          <thead>
            <tr>
              <th>Name</th>
              <th>Price</th>
            </tr>
          </thead>
          <tbody>{rows}</tbody>
        </table>
      );
    }
    
    function SearchBar({
      filterText,
      inStockOnly,
      onFilterTextChange,
      onInStockOnlyChange,
    }) {
      return (
        <form>
          <input
            type="text"
            value={filterText}
            placeholder="Search..."
            onChange={(e) => onFilterTextChange(e.target.value)}
          />
          <label>
            <input
              type="checkbox"
              checked={inStockOnly}
              onChange={(e) => onInStockOnlyChange(e.target.checked)}
            />{' '}
            Only show products in stock
          </label>
        </form>
      );
    }
    
    const PRODUCTS = [
      {category: 'Fruits', price: '$1', stocked: true, name: 'Apple'},
      {category: 'Fruits', price: '$1', stocked: true, name: 'Dragonfruit'},
      {category: 'Fruits', price: '$2', stocked: false, name: 'Passionfruit'},
      {category: 'Vegetables', price: '$2', stocked: true, name: 'Spinach'},
      {category: 'Vegetables', price: '$4', stocked: false, name: 'Pumpkin'},
      {category: 'Vegetables', price: '$1', stocked: true, name: 'Peas'},
    ];
    
    export default function App() {
      return <FilterableProductTable products={PRODUCTS} />;
    }
    
    

    برای کسب اطلاعات بیشتر در مورد کنترل روی‌داد‌ها و به روز‌رسانی استیت متوانید به قسمت افزودن تعاملات مراجعه کنید.

    Where to go from here

    این مقدمه‌ای بسیار کوتاه بود برای آشنایی با نحوه‌ی فکر کردن در مورد ساخت کامپوننت‌ها و برنامه‌ها با ری‌اکت. شما می‌توانید همین حالا یک پروژه‌ی ری‌اکت راه‌اندازی کنید یا به عمق بیشتری درباره‌ی تمام دستورالعمل‌ها (سینتکس) استفاده‌شده در این آموزش بپردازید.

    You can start a React project right now or dive deeper on all the syntax used in this tutorial.