آشنایی جالب و کم‌تر دیده‌شده با React


۰


React محبوب‌ترین فریم‌ورک JavaScript در دنیا است، اما دلیل محبوبیتش فقط زیاد بودن استفاده‌کننده‌ها نیست؛ بلکه به این خاطر است که واقعاً عالی است. اکثر آموزش‌های React سریع می‌روند سراغ نمایش چگونگی استفاده و مثال‌ها و از بخش «چرا» می‌گذرند. این رویکرد مشکلی ندارد؛ اگر فقط می‌خواهید سریع شروع کنید و بازی کنید، مستندات رسمی React منابع زیادی برای شروع دارد.

اما این مطلب برای کسانی است که می‌پرسند: «چرا React؟ چطور React این‌طوری کار می‌کند؟ هدف طراحی APIها چه بوده؟»


چرا React؟

زندگی ساده‌تر است وقتی کامپوننت‌های UI از شبکه، منطق تجاری (business logic) یا وضعیت اپلیکیشن (app state) بی‌خبر باشند. با دادن همان props، همیشه همان داده را رندر کن.

وقتی React معرفی شد، اساساً مفهوم کار فریم‌ورک‌های JavaScript را تغییر داد. در حالی که دیگران بر مدل‌هایی مثل MVC یا MVVM تاکید داشتند، React رویکرد کاملاً جدیدی اتخاذ کرد و رندر کردن نمای (view) را از مدل جدا کرد و معماری Flux را به اکو سیستم فرانت‌اند JS معرفی کرد.

دلیل این کار چه بود؟ چرا بهتر از فریم‌ورک‌های MVC قدیمی و کدهای به هم ریخته jQuery بود؟

در سال ۲۰۱۳، فیسبوک تازه به سختی توانسته بود ویژگی چت را یکپارچه کند؛ ویژگی‌ای که باید در سرتاسر اپلیکیشن در صفحات مختلف در دسترس بود. این خودش یک اپلیکیشن پیچیده در دل اپلیکیشن پیچیده‌تر بود. تغییرات بی‌قابوول (uncontrolled mutation) روی DOM و رفتار ناهمزمان چند کاربره ورود و خروج داده (multi-user I/O) مشکلات دشواری برای تیم فیسبوک ایجاد کرده بود.

مثلاً، چطور می‌شود پیش‌بینی کرد چه چیزی روی صفحه رندر می‌شود وقتی که هر بخش از برنامه در هر زمان می‌تواند DOM را تغییر دهد؟ و چطور می‌توان تضمین کرد چیزی که رندر شده درست است؟

قبل از React هیچ کدام از فریم‌ورک‌های رایج چنین تضمینی نمی‌دادند. مشکلات race condition روی DOM از رایج‌ترین باگ‌ها در برنامه‌های وب اولیه بودند.

Non-determinism = parallel processing + mutable state” — مارتین اودرسکی (نویسنده زبان اسکالا)

کار اصلی تیم React رفع این مشکل بود. این کار را با دو نوآوری کلیدی انجام دادند:

  • «داده‌بندی یک‌طرفه» (Unidirectional data binding) با معماری Flux
  • وضعیت (state) کامپوننت غیرقابل تغییر است (immutable). به محض تنظیم شدن، نمی‌شود آن را مستقیماً تغییر داد. تغییرات وضعیت منجر به ایجاد یک رندر جدید با حالت جدید می‌شود.

«ساده‌ترین راهی که ما پیدا کرده‌ایم تا نماهایمان را بسازیم و رندر کنیم، این است که کاملاً از تغییر مستقیم پرهیز کنیم.» — تام اوچینو، کنفرانس JSConfUS ۲۰۱۳

Flux این مشکل تغییرات بی‌قابوول را مهار کرد. به جای اینکه به تعداد نامحدودی آبجکت (مدل) گوش‌فرمان بدهیم تا DOM را تغییر دهند، React یک راه واحد برای تغییر وضعیت کامپوننت معرفی کرد: ارسال (dispatch) به فروشگاه داده (store). وقتی وضعیت فروشگاه تغییر می‌کند، فروشگاه از کامپوننت می‌خواهد دوباره رندر شود.

Flux architecture

وقتی می‌پرسند «چرا باید به React اهمیت بدهیم؟» جواب من ساده است: چون ما می‌خواهیم رندرهای نمای قطعی (deterministic view renders) داشته باشیم، و React این کار را خیلی آسان‌تر می‌کند.

توجه: خواندن داده‌ها از DOM برای اجرای منطق دامنه (domain logic) الگوی اشتباهی است و خلاف هدف React است. به جای آن، داده‌ها را از فروشگاه بخوان و تصمیم‌گیری‌ها را قبل از زمان رندر انجام بده.

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


JSX

JSX توسعه‌ای از JavaScript است که به شما اجازه می‌دهد به صورت دکلارتیو کامپوننت‌های UI بسازید. مزایای مهم JSX عبارتند از:

  • نشانه‌گذاری ساده و دکلارتیو
  • همراه با کامپوننت و در همان فایل نگه داشته می‌شود
  • جداکردن بر اساس مسئولیت‌ها (مانند UI مقابل منطق وضعیت و side-effectها) نه بر اساس تکنولوژی‌ها (HTML، CSS یا JavaScript)
  • انتزاع تفاوت‌های DOM
  • انتزاع از فناوری‌های زیرین تا به راحتی بتوانید روی پلتفرم‌های مختلف target بگیرید — مثلاً ReactNative، VR، Canvas/WebGL، ایمیل و ...

قبل از JSX، اگر می‌خواستید UI دکلارتیو بنویسید، مجبور بودید از قالب‌های HTML استفاده کنید که استاندارد مشخص و یکسانی نداشتند و هر فریم‌ورک syntax مخصوص به خود داشت. مثلاً Angular با دستور *ngFor شناخته می‌شد.

JSX به عنوان یک superset از جاوااسکریپت همه امکانات زبان JS از جمله map، عملگرهای منطقی، expressions سه‌تایی (ternary)، فراخوانی توابع خالص (pure functions)، و الگوهای رشته (template literals) را به شما می‌دهد. این مزیت بزرگی نسبت به سایر فریم‌ورک‌هاست.

دو نکته ممکن است اختلال ایجاد کنند:

  • در JSX باید به جای class از className استفاده کنید.
  • هنگام نمایش لیست‌ها، هر آیتم باید یک کلید (key) یکتا و پایدار داشته باشد که هنگام اضافه یا حذف تغییر نکند.

React رو راه‌حلی برای CSS به‌صورت گذرا ارائه نکرده است. شما می‌توانید یک آبجکت سبک (JavaScript style object) به ویژگی style پاس بدهید، که نام سبک‌ها باید camelCase باشد، اما راه‌های دیگری هم وجود دارد:

  • فایل‌های CSS عادی که در header صفحه بارگذاری می‌شوند.
  • ماژول‌های CSS که به صورت محلی scoped هستند و با loader نظیر Webpack کار می‌کنند. Next.js به صورت پیش‌فرض این را پشتیبانی می‌کند.
  • styled-jsx که اجازه می‌دهد استایل را به شکل inline داخل کامپوننت‌ها تعریف کنید؛ این استایل‌ها فقط روی بخش محلی و زیرمجموعه‌ها اثر می‌گذارند و Next.js به صورت دلفال آن را فعال دارد.

Synthetic Events

React به جای eventهای DOM، یک wrapper به نام synthetic events ارائه می‌دهد. مزایا:

  1. تفاوت‌های بین پلتفرم‌های مختلف را پنهان می‌کنند و کار با eventها را در همه مرورگرها یکنواخت‌تر می‌کنند.
  2. خودکار مدیریت حافظه دارند؛ یعنی لازم نیست برای عناصر در حال اسکرول لیسنرهای زیادی اضافه و حذف کنید. این‌جا، همه eventها به نود ریشه React متصل می‌شوند و مدیریت حافظه به صورت خودکار انجام می‌شود.

نکته: پیش از React نسخه ۱۷ نمی‌شد به خصیصه‌های event در async دسترسی داشت به خاطر event pooling که در نسخه ۱۷ حذف شده است.


چرخه عمر (Lifecycle) کامپوننت‌ها

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

کتابخانه از نسخه ۰.۱۴ شروع به پشتیبانی از کلاسه برای lifecycle کرد. چرخه عمر به سه مرحله تقسیم می‌شود: Mounting، Updating و Unmounting.

React Lifecycle

داخل فاز به‌روزرسانی، سه مرحله وجود دارد:

React Update Cycle

  • Render: معمولاً تابع رندر باید یک تابع خالص (pure function) از props به JSX باشد و عوارض جانبی نداشته باشد.
  • Pre-Commit: می‌توانید با متد lifecycle به نام getSnapshotBeforeUpdate از DOM بخوانید. کاربرد نمونه‌اش برای گرفتن موقعیت اسکرول یا اندازه یک المنت پیش از بازنویسی DOM است.
  • Commit: در این مرحله DOM و ارجاعات به‌روزرسانی می‌شوند؛ می‌توانید در componentDidUpdate یا hook به نام useEffect اثر خود را اجرا کنید.

دن آبراموف یک دیاگرام جامع از lifecycle ارائه کرده که بسیار مفید است:

React Component Lifecycle Diagram

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

مدل فکری بهتر: هر بار که React رندر می‌کند، یک تابع قطعی که JSX برمی‌گرداند، اجرا می‌شود. این تابع خودش نباید عوارض جانبی (side-effects) داشته باشد؛ بلکه باید آن‌ها را برای اجرای React صف‌بندی کند.


React Hooks

در React 16.8، مفهوم React hooks معرفی شد. hooks توابعی هستند که به شما اجازه می‌دهند با چرخه عمر کامپوننت بدون کلاس کار کنید.

یک hook معمولاً عوارض جانبی ایجاد می‌کند؛ یعنی اثرات قابل مشاهده بیرون از تابع و غیر از خروجی تابع.

مثلاً useEffect به شما اجازه می‌دهد در زمان مناسب (مثل componentDidMount، componentDidUpdate، و componentWillUnmount) اثرات خود را اجرا کنید.

یکی از مزایای hooks این است که می‌توانید همه منطق مربوط به mount، update و unmount را در یک تابع نگه دارید بدون پراکندگی در چند متد.

hooks مزایای زیادی دارند:

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

در کل، بهتر است توابع و hooks را به کلاس‌ها ترجیح بدهید چون کد کمتر، منظم‌تر، قابل فهم‌تر، تست پذیرتر و قابل استفاده مجدد بیشتری خواهید داشت.


Container vs Presentation Components

برای ماژولولاریته و قابلیت استفاده مجدد بهتر، اجزای خود را به دو دسته تقسیم می‌کنم:

  • کامپوننت‌های container که به فروشگاه داده متصل‌اند و ممکن است side-effect داشته باشند.
  • کامپوننت‌های presentation که بیشتر تابع‌های خالص هستند و با props یکسان همیشه خروجی یکسان می‌دهند.

نکته: کامپوننت‌های خالص با React.PureComponent متفاوتند. React.PureComponent فقط محافظت از رندرهای اضافی است، ولی یک کامپوننت Pure بر پایه رفتار تابعی است.

کامپوننت‌های Presentation:

  • با شبکه کاری ندارند
  • در localStorage ذخیره یا خواندن نمی‌کنند
  • داده تصادفی تولید نمی‌کنند
  • مستقیم زمان سیستم را نمی‌خوانند (مثل Date.now())
  • مستقیماً با فروشگاه داده یا state خارجی تعامل ندارند
  • ممکن است از state محلی برای ورودی فرم استفاده کنند به شرطی که قابل تست و کنترل باشند

Higher Order Components (HOC)

HOC یک کامپوننت است که یک کامپوننت دیگر می‌گیرد و یک کامپوننت جدید برمی‌گرداند که قابلیت‌های بیشتری دارد.

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

برخلاف hooks یا render props، HOC بسیار قابل ترکیب است و می‌توانید رفتار مشترک را با چند خط کد به راحتی روی همه کامپوننت‌ها اعمال کنید.

نمونه‌ای کاربردی از HOC در دنیای واقعی:

1import LessonPage from '../features/lesson-pages/lesson-page.js';  
2import pageHOC from '../hocs/page-hoc.js';
3export default pageHOC(LessonPage);

بدون HOC باید هر بار ترکیب‌ها را دستی و در جاهای مختلف تکرار کنید که دردسر و پیچیدگی زیادی ایجاد می‌کند.


جمع‌بندی

  • چرا React؟ چون رندرهای نمای قطعی، داده‌بندی یک‌طرفه، و وضعیت غیرقابل تغییر کامپوننت‌ها را دارد.
  • JSX نشانه‌گذاری دکلارتیو و ساده در JS ایجاد می‌کند.
  • Synthetic events تفاوت‌های مرورگرها را پنهان و مدیریت حافظه eventها را ساده می‌کند.
  • چرخه عمر کامپوننت وضعیت را محافظت می‌کند و به سه فاز Mount، Update، Unmount تقسیم می‌شود.
  • React hooks کمک می‌کنند بدون کلاس lifecycle را مدیریت کنید و منطق را بهتر تکه‌تکه و اشتراک بگذارید.
  • Container و Presentation components به جدا کردن concerns و بهبود قابلیت تست و استفاده مجدد کمک می‌کنند.
  • HOC ها امکان اشتراک‌گذاری رفتار بین کامپوننت‌ها را به صورت قابل ترکیب فراهم می‌کنند.

گام‌های بعدی

برای درک بهتر React، مفاهیمی مثل توابع خالص، عدم تغییرپذیری (immutability)، curry کردن، اعمال جزئی (partial application) و ترکیب توابع را بهتر بشناسید. این‌ها را می‌توانید با ویدئو و تمرین‌های کدنویسی در EricElliottJS.com ببینید.

معمولاً React را با Redux، Redux-Saga و RITEway جفت کنید. برای ساده‌تر کردن کدهای Redux، Autodux و Immer را توصیه می‌کنند. برای transitionهای پیچیده، Redux-DSM مفید است.

وقتی پایه‌ها را گذاشتید، Next.js و Vercel در راه‌اندازی، CI/CD و استقرار سرورلس کمک شایان توجهی است؛ انگار تیم DevOps تمام‌وقت دارید اما هزینه کمتر.


امیدوارم این معرفی جامع به شما کمک کرده باشد بفهمید چرا React چنین جایگاه ویژه‌ای در دنیای توسعه UI دارد و چطور توانسته چالش‌های گذشته را حل کند و توسعه را ساده‌تر و لذت‌بخش‌تر نماید! موفق باشید.

جاوا اسکریپت

۰


نظرات


author
نویسنده مقاله: امیرمهدی رستمی

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

تمام حقوق این سایت متعلق به وبسایتcodebymeمیباشد.