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

ریکت

21 مرداد 1402

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

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

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

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

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

برای پیاده سازی لازم هست 5 قدم رو انجام بدیم.

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

توی تقسیم کردن طرح به کامپوننت های کوچک تر میتونید از قانون single responsibility principle استفاده کنید، یعنی هر کامپوننت یک کار رو انجام بده. اگه یک کامپوننت هم شروع کرد به بزرگ شدن باز اون رو هم به کامپوننت های کوچکتر که زیر مجموعه اون هستن تقسیم می‌کنیم.

بطور مثال داخل طرحی که داریم میتونیم به این شکلی تقسیم کنیم:


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

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

*FilterableProductTable
  *SearchBar
  *ProductTable
     *ProductCategoryRow
     *ProductRow


قدم 2: یک نسخه استاتیک درست کنیم

خوب حالا وقت اینه که کامپوننت هامون رو بنویسیم. راه ساده اینه که اول UI و طرح کلی رو پیاده سازی کنیم یا اصطلاحا static version بسازیم و بعدش تعامل (کار کردن با state ها و ...) رو بهش اضافه کنیم. ساختن static version بیشتر نیاز به تایپ کردن داره و اضافه کردن تعامل بیشتر نیاز به فکر کردن.

وقتی که اومدیم کامپوننت هامون رو نوشتیم برای اینکه دیتا ها رو به کامپوننت هامون پاس بدیم از props استفاده می‌کنیم. در واقع props کمک میکنه که دیتا از والد به فرزند برسه. اگه با مفهوم state آشنا هستین، ما از state ها داخل static version استفاده نمیکنیم. state ها برای دیتا هایی مناسب هستن که در زمان ممکنه تغییر کنند و زمانی که ما میاییم static version میسازیم اصلا نیاز به state نداریم.

همچنین برای توسعه کامپوننت ها دو حالت وجود داره یا بیاییم از بالا برنامه که اینجا FilterableProductTable هست شروع کنیم به توسعه و همینطوری پیش بریم تا کامپوننت های فرزند(top down)، و یا از پایین ترین نقطه کامپوننت که اینجا ProductRow هست شروع کنیم بریم به والد برسیم(bottom up). توی پروژه های بزرگ تر پیشنهاد میشه از bottom-up استفاده کنید.

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} />;
}



قدم 3: استفاده کامل و حداقلی از استیت ها

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

  1. لیست اصلی محصولات
  2. مقدار سرچ ایی که کاربر انجام میده
  3. مقدار checkbox
  4. لیست فیلتر شده

خوب حالا کدوم یک از این دیتا ها رو میشه به عنوان یک state تعریف کرد؟ چند تا سوال از خودمون میپرسیم.

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

اگه موارد بالا رو خیر باشن قطعا جز state ها حساب میشه.


خوب حالا بریم سراغ اون 4 جریان دیتا ایی که داخل برنامه داشتیم و ببینیم اینا state هستن یا نه؟

  1. لیست اصلی محصولات چون از prop میاد پس state نیست.
  2. مقدار سرچ ایی که کاربر انجام میده یک state هست چون در گذر زمان عوض میشه.
  3. مقدار checkbox هم همینطور در گذر زمان عوض میشه پس یک state هست.
  4. لیست فیلتر شده یک state نیست چون ما لیست اصلی محصولات رو که از prop میاد رو داریم با همون prop میتونیم فیلتر رو انجام بدیم و نیاز به state جدید نیست.


قدم 4: استیت ما کجا باید ایجاد بشه؟

حالا نوبت این هست که مشخص کنیم این استیت ایی که داریم، کدوم کامپوننت وظیفه تغییر استیت یا مالکیت اون رو داره؟

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

  1. تمام کامپوننت هایی که دارن براساس استیت آیتمی رو رندر می‌کنند رو تشخیص بدیم.
  2. نزدیک ترین والد مشترک بینشون رو پیدا کنیم.
  3. در نهایت تشخیص میدیم استیت ما کجا باشه.

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

بطور مثال در پروژه ایی که داریم سه گام بصورت زیر میشه:

  1. مشخص کنیم کدوم کامپوننت داره از state ما استفاده میکنه. در پروژه ما ProductTable و SearchBar هستش.
  2. قدم بعد باید والد مشترک اینا رو پیدا کنیم که FilterableProductTable هستش.
  3. در آخر تصمیم میگیرم کجا state ما باید باشه که در FilterableProductTable قرار میگیره.

همونطور که میدونید state ها رو با کمک هوک useState میسازیم و بعدش بصورت prop به کامپوننت هامون میدیم.

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

بعد از filterText و inStockOnly رو به کامپوننت های ProductTable و SearchBar پاس میدیم.

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


قدم 5: ایجاد جریان داده معکوس

تا الان اپ ما همه چیز هاش کامل شده و کار میکنه فقط این موضوع مونده که چطوری مقدار inStockOnly و filterText رو باید عوض کنیم تا سرچ انجام بشه؟

اینجا از مفهومی به نام inverse data flow استفاده می‌کنیم. الان مالک state های filterText و inStockOnly کامپوننت FilterableProductTable هستش حالا میخوایم این اجازه رو داشته باشیم که کامپوننت SearchBar بیاد اینا رو تغییر بده باید چیکار کنیم؟

باید فانکشن set جفت state ها رو بصورت prop به SearchBar بدیم و اونجا میایم اونو صدا میزنیم تا state ما آپدیت بشه.

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

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

و داخل کامپوننت SearchBar میایم تابع set رو به onChange فیلدی که داریم پاس میدیم.


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

منبع:

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

0

heart

0

like

0

happy

0

sad