تفکر به سبک React

mohsen1 ماه قبل
ارسال شده در
react/docs/v19

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

شروع با موکاپ (mockup)

تصور کنید که شما یک API JSON و یک طرح از یک طراح دارید.

API JSON داده‌هایی به شکل زیر برمی‌گرداند:

      [
  { 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" }
]
    

طرح به این شکل است:

موکاپ برنامه

برای پیاده‌سازی یک رابط کاربری در ری‌اکت، معمولاً این پنج مرحله را باید دنبال کنید.

مرحله 1: تقسیم رابط کاربری به سلسله مراتب کامپوننت‌ها

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

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

  • برنامه‌نویسی--از همان تکنیک‌هایی که در مورد تقسیم یک تابع یا شی به توابع و اشیا جدید استفاده می کنید، اینجا نیز استفاده کنید. یکی از این تکنیک‌ها اصل مسئولیت واحد است؛ به این معنی که یک کامپوننت باید در حالت ایده‌آل تنها یک کار انجام دهد. اگر بعدا بزرگتر شد، باید به زیرکامپوننت‌های کوچکتر تقسیم شود.
  • CSS--در نظر بگیرید که برای چه مواردی می‌خواهید انتخاب‌گرهای کلاس ایجاد کنید. (با این حال، کامپوننت‌ها کمی بزرگتر و دانه درشت تر هستند.)
  • طراحی--در نظر بگیرید که چگونه می‌خواهید لایه‌های طراحی را سازماندهی کنید.

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

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

تقسیم صفحه به کامپوننت ها
  • FilterableProductTable (خاکستری) شامل کل برنامه است.
  • SearchBar (آبی) ورودی کاربر را دریافت می‌کند.
  • ProductTable (نیلی کمرنگ) لیست را بنا بر ورودی کاربر نمایش و فیلتر می‌کند.
  • ProductCategoryRow (سبز) یک عنوان برای هر دسته نمایش می‌دهد.
  • ProductRow (زرد) یک ردیف برای هر محصول نمایش می‌دهد.

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

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

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

مرحله 2: ساخت یک نسخه استاتیک در ری‌اکت

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

برای ساخت یک نسخه استاتیک از برنامه که فقط مدل داده را رندر می‌کند، باید کامپوننت‌هایی را بسازید که از دیگر کامپوننت‌ها استفاده مجدد می کنند و داده‌ها را با استفاده از props به هم پاس می دهند. ویژگی ها (Props) روشی برای انتقال داده‌ها از والد به فرزند است. (اگر با مفهوم state آشنا هستید، برای ساخت این نسخه استاتیک از state استفاده نکنید. State فقط برای تعامل‌پذیری برنامه طراحی شده است، یعنی داده‌ای که در طول زمان تغییر می‌کند. از آنجا که این یک نسخه استاتیک از برنامه است، به آن نیاز ندارید.)

حال می‌توانید "از بالا به پایین" شروع به ساخت کنید. مثلن با ساختن کامپوننت‌های بالاتر در سلسله مراتب (مانند FilterableProductTable) شروع کنید و یا "از پایین به بالا" با کار بر روی کامپوننت‌های پایین‌تر (مانند ProductRow) شروع کنید. در مثال‌های ساده‌تر، معمولاً از بالا به پایین رفتن آسان‌تر است و در پروژه‌های بزرگتر، پایین به بالا روش آسان‌تری است.

مشاهده کد در CodeSandBox

      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} />;
}
    
      body {
  padding: 5px
}
label {
  display: block;
  margin-top: 5px;
  margin-bottom: 5px;
}
th {
  padding-top: 10px;
}
td {
  padding: 2px;
  padding-right: 40px;
}
    

(اگر این کد ترسناک به نظر می‌رسد، ابتدا از مطالعه شروع سریع شروع کنید!)

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

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

مرحله 3: پیدا کردن نمای حداقلی اما کامل از وضعیت UI

برای اینکه UI را تعاملی کنید، باید به کاربران اجازه دهید داده‌های مدل شما را تغییر دهند. برای این کار می توانید از state استفاده کنید.

به state به چشم حداقل داده قابل تغییر ( و به خاطر سپردن تغییر ) که برنامه برای کارکرد درست به آن نیاز دارد نگاه کنید. مهم‌ترین اصل برای ساختاردهی state این است که آن را بر اساس اصل DRY (خود را تکرار نکنید) انجام دهید. حداقل نمای مطلق از state مورد نیاز برنامه خود را شناسایی کرده و همه چیزهای دیگر را از روی آن درست کنید. به عنوان مثال، اگر یک لیست خرید می‌سازید، می‌توانید اقلام را به عنوان یک آرایه در state ذخیره کنید. اگر همچنین می‌خواهید تعداد اقلام در لیست را نمایش دهید، تعداد اقلام را به عنوان یک مقدار state دیگر ذخیره نکنید؛ به جای آن، طول آرایه محصولات را بخوانید.

حالا به تمام قطعات داده در این مثال فکر کنید:

  • لیست اصلی محصولات
  • متن جستجویی که کاربر وارد کرده است
  • مقدار چک باکس
  • لیست فیلتر شده محصولات

کدامیک از این‌ها وضعیت هستند؟ مواردی که وضعیت نیستند را شناسایی کنید:

  • آیا در طول زمان تغییر نمی‌کند؟ اگر این‌طور است، state نیست.
  • آیا از یک والد به صورت props عبور می‌کند؟ اگر این‌طور است، state نیست.
  • آیا می‌توانید آن را بر اساس state یا props موجود در کامپوننت خود محاسبه کنید؟ اگر این‌طور است، قطعاً state نیست!

آنچه باقی می‌ماند احتمالاً state است.

بیایید یک بار دیگر آن‌ها را یکی یکی بررسی کنیم:

  • لیست اصلی محصولات به عنوان props عبور می‌کند، بنابراین state نیست.
  • متن جستجو به نظر می‌رسد state باشد زیرا در طول زمان تغییر می‌کند و نمی‌توان آن را از هیچ چیزی محاسبه کرد.
  • مقدار چک باکس به نظر می‌رسد state باشد زیرا در طول زمان تغییر می‌کند و نمی‌توان آن را از هیچ چیزی محاسبه کرد.
  • لیست فیلتر شده محصولات state نیست زیرا می‌توان آن را با گرفتن لیست اصلی محصولات و فیلتر کردن آن بر اساس متن جستجو و مقدار چک باکس محاسبه کرد.

این به این معنی است که فقط متن جستجو و مقدار چک باکس state هستند! کار به خوبی انجام شد!

Props در مقابل State

دو نوع "مدل" داده در ری‌اکت وجود دارد: props و state. این دو بسیار متفاوتند:

  • Props. آن‌ها به یک کامپوننت والد اجازه می‌دهند داده‌ها را به یک کامپوننت فرزند انتقال دهند و نمایش آن را سفارشی کنند. به عنوان مثال، یک Form می‌تواند یک ویژگی color به یک Button انتقال دهد.
  • State وضعیت به یک کامپوننت اجازه می‌دهد که وضعیت برخی اطلاعات را دنبال کرده و در پاسخ به تعاملات کاربر تغییر دهد. به عنوان مثال، یک Button ممکن است وضعیت isHovered (آیا موس روی دکمه هست یا نه ) را دنبال کند.

مدل Props و state متفاوتند، اما با هم کار می‌کنند. یک کامپوننت والد اغلب برخی اطلاعات را در state نگه می‌دارد (تا بتواند آن را تغییر دهد) و آن را به پایین به عنوان props به کامپوننت‌های فرزند عبور می‌دهد. اشکالی ندارد اگر تفاوت آنها هنوز در اول کار برایتان مبهم به نظر برسد. کمی تمرین لازم است تا برایتان کاملن جا بیفتد!

مرحله 4: شناسایی جایی که state شما به آن تعلق دارد

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

برای هر قطعه state در برنامه:

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

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

حال بیایید استراتژی خود را بر روی آن‌ها اعمال کنیم:

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

به این ترتیب، مقادیر state در FilterableProductTable خواهند بود.

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

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

سپس filterText و inStockOnly را به عنوان props به ProductTable و SearchBar انتقال دهید:

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

حال می‌توانید عملکرد برنامه را بررسی کنید. مقدار اولیه filterText را از useState('') به useState('fruit') در کد sandbox ویرایش کنید. خواهید دید که هم متن ورودی جستجو و هم جدول به‌روزرسانی می‌شوند:

مشاهده برنامه در CodeSandBox

      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} />;
}
    
      body {
  padding: 5px
}
label {
  display: block;
  margin-top: 5px;
  margin-bottom: 5px;
}
th {
  padding-top: 5px;
}
td {
  padding: 2px;
}
    

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

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

در sandbox بالا، ProductTable و SearchBar ورودی هایfilterText و inStockOnly را برای رندر کردن جدول، ورودی متنی و چک باکس دریافت می کنند. به عنوان مثال، SearchBar به روش زیر مقدار ورودی را می تواند تغییر میدهد:

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

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

مرحله 5: افزودن جریان داده معکوس

در حال حاضر برنامه به درستی با props و state ای که به سمت پایین سلسله مراتب جریان دارند، رندر می‌شود. اما برای تغییر state بر اساس ورودی کاربر، نیاز دارید جریان داده به سمت برعکس را نیز پشتیبانی کنید: کامپوننت‌های فرم که در عمق سلسله مراتب هستند باید state در FilterableProductTable را به‌روزرسانی کنند.

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

اما می‌خواهید به محض اینکه کاربر ورودی‌های فرم را تغییر داد، state نیز به‌روزرسانی شود تا این تغییرات را منعکس کند. state متعلق به 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} />
    

درون SearchBar، شما رویدادهای onChange را اضافه کرده و state والد را در آن‌ها ست کنید:

      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)}
    

حالا برنامه به طور کامل کار می‌کند! مشاهده اجرای برنامه در CodeSandBox

      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} />;
}
    
      body {
  padding: 5px
}
label {
  display: block;
  margin-top: 5px;
  margin-bottom: 5px;
}
th {
  padding: 4px;
}
td {
  padding: 2px;
}
    

همه‌چیز درباره مدیریت رویدادها و به‌روزرسانی وضعیت را می توانید در بخش افزودن تعامل‌پذیری یاد بگیرید.

بعد از این آموزش به کجا برویم

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

رای
0
ارسال نظر
مرتب سازی:
اولین نفری باشید که نظر می دهید!