Siavash Aghazadeh
Siavash Aghazadeh
خواندن ۷ دقیقه·۹ ساعت پیش

📐 React Design Pattern's (part-1)

سلام 👋
من تصمیم گرفتم خیلی خلاصه و ساده بیام و بهترین و مهم ترین(هر چند نمیشه گفت که کدوم بهترینه) Design Pattern های ریکت و توضیح بدم تا درکشون خیلی راحت تر بشه. البته واقعا متوجه شدم به همشون توی یک پارت نمیشه پرداخت، و اینکه اگه بخوام همشون و توی یک پارت توضیح بدم طولانی میشه. پس نتیجه گرفتم چند تا پارتشون کنم ولی با مثال کامل برای هر کدوم.
توی پارت اول به این پترن ها میپردازیم:

  • Higher-Order Component (HOC)
  • Render Props
  • Container/Presentational
  • Compound


روش توضیح دادن هم اینجوریه که اول میام توضیح میدیم که اصلا مشکلمون چی بوده و بعد چجوری با اون پترن خاص حلش کردیم. بریم سر اصل مطلب.

1- Higher-Order Component (HOC)

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

  • در نتیجه HOC یک پترن برای اشتراک‌گذاری منطق یا همون logic بین کامپوننت‌هاست. این الگو باعث میشه که بدون تغییر تو ساختار یا محتوای کامپوننت‌ها، ویژگی‌ها یا منطق جدیدی رو به اونها اضافه کنیم.این پترن یک تابعه که یک کامپوننت رو می‌گیره و یک کامپوننت جدید با ویژگی‌ها و منطق اضافه‌شده برمیگردونه.
فقط باید دقت کرد که HOC ‌ها نباید state داخلی خودشون رو مدیریت کنن و باید فقط منطق و ویژگی‌ها رو به کامپوننت‌ها منتقل کنن.
کلاً این پترن، ref را انتقال نمیده. (که البته با forwardRef حل میشه)

در کل، Higher-Order Components به شما کمک می‌کنن تا کد تمیزتر، قابل نگهداری‌تر و انعطاف‌پذیرتر داشته باشید و منطق مشترک رو به راحتی تو پروژه‌های React خودتون استفاده کنید.


2- Render Props

تو دنیای React، معمولاً یک کامپوننت مسئول رندر کردن محتوای خودشه. این کامپوننت می‌تونه داخل JSX، المنت‌های مختلفی رو رندر کنه. اما بعضی وقت ها ممکنه بخواهیم کنترل بیشتری روی نحوه‌ی رندر کردن محتوای داخل کامپوننت داشته باشیم، و اینجاست که مفهوم Render Prop وارد میشه.
در واقع، Render Prop یک پراپه که به کامپوننت داده می‌شه و مقدار اون یک تابع هستش. این تابع در نهایت یک JSX element برمی‌گردونه که همونطور که می‌دونید، همون محتوای UI که باید رندر بشه هست. این ویژگی به کامپوننت این امکان رو می‌ده که رندرینگ خودش رو به تابعی که بهش داده شده واگذار کنه. بنابراین، کامپوننت خودش مستقیماً تصمیم نمی‌گیره که چه چیزی نمایش بده، بلکه تصمیم‌گیری رو به تابعی که از بیرون براش ارسال شده، می‌سپاره.

البته این نکته رو هم در نظر داشته باشین که لازم نیست کل ui اون کامپوننت رو تغییر بدیم میتونیم حتی فقط قسمتی ازش رو منحصر به فرد کنیم. مثلا ما یک Card داریم که میخوایم فقط Header اون تو جاهای مختلف فرق کنه

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

در نتیجه پترن Render Prop یک روش انعطاف‌پذیر برای مدیریت رندر کامپوننت‌ها تو React هستش که به ما این امکان رو میده که منطق رندرینگ رو از نمایش UI جدا کنیم. این پترن باعث میشه که کامپوننت‌ها قابل استفاده مجدد باشن و تغییرات تو نحوه‌ی نمایش داده‌ها راحت‌تر انجام بشه.

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


3- Container/Presentational Pattern

فرض کنیم یک کامپوننت به نام UserProfile داریم که توی اون اطلاعات کاربر نمایش داده میشه و یکسری عملیات مثل ویرایش یا به‌روزرسانی اطلاعات کاربر هم انجام میشه. اگر بخوایم Logic و UI رو تو همین یک کامپوننت پیاده‌سازی کنیم، کد شلوغ و پیچیده‌ میشه.

الگوی Container/Presentational که با نام‌های Container/View یا Smart/Dumb هم معروف هستش، یکی دیگه از Design Pattern هستش که به‌طور گسترده توی برنامه‌های React استفاده میشه. تو این پترن، کامپوننت‌ها به دو نوع Container و Presentational تقسیم میشن. هدف اصلیش هم جداسازی منطق و داده، از نحوه نمایش UI هستش. این جداسازی باعث میشه تغییرات تو نحوه نمایش یا منطق به راحتی انجام بشه بدون اینکه بخش های دیگه رو تحت تأثیر قرار بده.

- البته یکی از معایبی که این پترن داره اینه که، تعداد کامپوننت های پروژه تقریبا دوبرابر میشه که اینم خودش باعث سردرگمی های زیادی میشه بین کامپوننت ها.
- نیاز به ارسال تعداد زیادی props بین کامپوننت‌ها وجود داره، که مدیریت اون ها را دشوار می‌کنه.
- ممکنه توی نام گذاری هاشون هم به مشکل بخوریم.

بریم سراغ مثال :
کامپوننت‌های Presentational فقط مسئول نمایش (UI) هستن. این کامپوننت‌ها از داده‌هایی که از والد خودشون (Container) به صورت props دریافت می‌کنن استفاده می‌کنن و معمولاً بدون هیچ منطق پیچیده ای هستن.

کامپوننت‌های Container مسئول مدیریت داده‌ها و منطق هستن. این نوع کامپوننت‌ها معمولاً به Redux یا API‌ های خارجی وصل میشن و داده‌ها رو دریافت میکنن و به کامپوننت‌های Presentational ارسال می‌کنن.

در نتیجه Container/Presentational با جداسازی منطق از UI به ما کمک می‌کنه تا کدی تمیزتر، قابل نگهداری‌تر و تست‌پذیرتر داشته باشیم. این پترن باعث می‌شه کامپوننت‌های ما بهتر ساختاربندی بشن: کامپوننت‌های Container فقط منطق و داده‌ها را مدیریت می‌کنن، و کامپوننت‌های Presentational صرفاً روی UI تمرکز دارن. با این جداسازی، توسعه و به‌روزرسانی برنامه آسون‌ تر میشه و استفاده مجدد از کامپوننت‌ها تو بخش‌های مختلف امکان‌پذیرتره.


4- Compound Pattern

فرض کنیم شما یک فرم ورود (Login) ساده دارید که شامل فیلدهای نام کاربری، رمز عبور، دکمه‌ی ورود و پیام خطا هستش. مشکل اینجاست که همه این موارد باید به صورت مستقل از هم عمل کنن، اما با همدیگه به عنوان یک کامپوننت کامل یکپارچه بشن. مثلاً، اگر خطایی رخ بده، پیام خطا باید نمایش داده بشه؛ یا دکمه‌ی ورود فقط وقتی فعال بشه که کاربر اطلاعات مورد نیاز رو وارد کرده باشه. تو یک فرم پیچیده‌تر، ممکنه فیلدهای بیشتری هم وجود داشته باشن.

پترن Compound به ما کمک می‌کنه کامپوننت‌های مرتبط رو به صورت یک مجموعه منسجم طراحی کنیم. تو این پترن، یک کامپوننت پدر (Container) با چنتا کامپوننت فرزند (Child) تعریف می‌شن و این کامپوننت‌های فرزند از طریق همین کامپوننت پدر به هم متصل و مرتبط می‌شن. این روش اجازه میده تا کاربر نهایی کنترل و انعطاف‌پذیری بیشتری روی ساختار داخلی کامپوننت‌ها داشته باشه و از اون‌ها در قالبی دلخواه استفاده کنه.

  • بریم سراغ مثال :
    میایم یک کامپوننت پدر (LoginForm) ایجاد میکنیم. تو اینجا، کامپوننت‌های فرزند شامل
    LoginInput , LoginButoon و LoginErrorMessage هستن، و هر کدوم از این کامپوننت‌ها به وضعیت فرم از طریق Context دسترسی دارند.
یکی دیگه از نکات مثبت این پترن اینه که هر بخش میتونه کاملا استایل مخصوص خودشو داشته باشه
توی مثال، این کامپوننت‌ها برای دسترسی به داده‌ها و متدهای مرتبط از LoginContext استفاده می‌کنند.

اول میایم کامپوننت پدر رو ایجاد میکنیم :

حالا توی همین کامپوننت، کامپوننت های فرزند رو هم ایجاد میکنیم:

و الان باید کامپوننت های فرزند رو به کامپوننت پدر اضافه کنیم :

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


با این ساختار، هر بخش از فرم ورود به عنوان بخشی از LoginForm شناخته میشه و به صورت مرتب و دسته‌بندی شده در کد قرار می‌گیره. این روش باعث بهبود خوانایی، سازمان‌دهی بهتر و امکان استفاده‌ی ماژولار از کامپوننت‌ها میشه.


پارت اول تموم شد، ممنون میشم شما هم نظرات خودتونو یا هر سوالی درباره هرکدوم از پترن ها دارین باهام به اشتراک بزارین. منتظر پارت بعدی باشین 🙏
امیدوارم مطالب مفید بوده باشه ✌️❤️

design patternreactدیزاین پترنالگو های طراحیfrontend
Front-end Developer
شاید از این پست‌ها خوشتان بیاید