ویرگول
ورودثبت نام
علی مقیمی
علی مقیمی
علی مقیمی
علی مقیمی
خواندن ۴ دقیقه·۲ روز پیش

تکنیک های بهینه سازی context برای افزایش performance در React

ابزار Context توی React ابزار قدرتمندیه، ولی اگر بدون دقت استفاده بشه می‌تونه به یکی از بزرگ‌ترین دلایل افت Performance تبدیل بشه. مخصوصاً وقتی stateهایی داریم که زیاد تغییر می‌کنن (مثلاً هر ثانیه). توی این مطلب، مهم‌ترین نکات عملی برای بهینه‌سازی Context رو به‌صورت مرحله‌به‌مرحله مرور می‌کنیم.

هر تغییری در Context به معنای re-render شدن همه مصرف‌کننده‌ها

مهم‌ترین نکته‌ای که خیلی وقت ها ممکنه نادیده گرفته میشه اینه که:

با هر تغییر value در Context، تمام کامپوننت‌هایی که از اون Context استفاده می‌کنن re-render می‌شن.

حتی اگر اون کامپوننت، اون state ای که تغییر کرده را استفاده نکرده باشه و بهش مربوط نباشه. بصورت کلی اگر کامپوننتی هوک useContext رو در خودش فراخوانی کنه، با تغییر هر state درون context، دوباره رندر میشه که این رندر غیرضروری و اضافی هست و باعث کاهش کارایی برنامه میشه.
با توجه به این مساله، Context اصلاً برای stateهایی که زیاد و با فرکانس بالا تغییر می‌کنن (مثل time، scroll، mouse position) گزینه‌ی خوبی نیست.

ولی با این روش هایی که تو ادامه بررسی می کنیم میشه این re-render های اضافی و غیرضروری رو حذف کرد.

1️⃣ جداسازی State ها و Actionها در Context:

منظور از Action ها همون توابع setState ای هستن که تو کانتکست وجود دارن. اما هدف از این جداسازی، اینه که:

  • کامپوننت‌هایی که فقط از این action ها استفاده کردن (و state ای رو set کردن)، با تغییر state دوباره render نمی‌شن.

  • درخت رندرها خیلی کوچیک‌تر می‌شه(یعنی کامپوننت هایی که دوباره رندر میشن، کمتر میشن)

نحوه پیاده سازی به این شکله:

StateContext -> data ActionsContext -> setData, updateSomething
  • یک Context برای state

  • یک Context جدا برای actions / setters

<StateContext.Provider value={states}> <ActionsContext.Provider value={actions}> {children} </ActionsContext.Provider> </StateContext.Provider>

2️⃣ قراردادن value مربوط به Provider در useMemo داخل کانتکست:

وقتی کانتکست رو این شکلی می نویسیم:

<MyContext.Provider value={{ state, setState }}>

در هر render، یک آبجکت جدید ساخته می‌شه ❌
و React فکر می‌کنه Context تغییر کرده. درحالیکه مقادیر داخل value ممکنه تغییری نداشته باشن. در نتیجه یه re-render اضافی برای تمام کامپوننت های تحت پوشش خودش اعمال می کنه.

پس باید اون رو داخل useMemo قرار بدیم:

const value = useMemo( () => ({ state, setState }), [state] );

3️⃣ جداسازی و بیرون آوردن state های پرنوسان:

Stateهایی مثل:

  • time (هر ۱ ثانیه)

  • websocket data

  • animation progress

❌ نباید داخل Context باشن. چون هربار که با فرکانس بالا تغییر می کنن؛ re-render های زیادی رو به اپ تحمیل می کنن. (کلا کانتکست برای چنین متغیرهایی طراحی نشده.)

راه‌حل بهتر:

  • useSyncExternalStore

  • local state

  • یا حتی event-based pattern

  • قراردادن در ref

4️⃣ جداسازی Context (سراسری) طبق مسئولیت یا feature-based:

با توجه به مساله ی re-rendering کانتکست که بالاتر بررسی کردیم؛ بهتره کانتکست ها رو بر مبنای ویژگی(feature-based) از هم جدا کنیم(یا بر هر مبنای دیگه ای که مناسب دیدیم).

این کار باعث میشه کامپوننت هایی که با بعضی state ها تو کانتکست ارتباطی ندارن؛ با تغییر این state ها بطور بیهوده re-render نشن و اینطوری re-render اضافی حذف میشه.

  • UserContext

  • ThemeContext

  • AuthContext

5️⃣ استفاده از context selector:

یه پکیجی به نام use-context-selector هست که امکان انتخاب بخش مشخصی از Context را فراهم می‌کنه.
در این حالت، کامپوننت فقط زمانی re-render می‌شه که همان state یا مقداری که انتخاب کرده تغییر کنه، نه با هر تغییر در کل Context.
به این ترتیب، مشکل re-renderهای اضافی که معمولاً هنگام استفاده‌ی مستقیم از useContext به‌وجود میاد، تا حد زیادی برطرف می‌شه.

مثال استفاده از use-context-selector
مثال استفاده از use-context-selector

6️⃣ استفاده از External store:

تو React یه هوک خیلی خفن داریم به اسم useSyncExternalStore که عملاً میاد یه store برامون درست می‌کنه (یه چیزی شبیه Redux، ولی خیلی سبک‌تر).

با این هوک می‌تونیم stateهامون رو بین چند تا کامپوننت به اشتراک بذاریم، بدون اینکه مجبور باشیم Provider درست کنیم یا کل درخت کامپوننت ها رو بهش وصل کنیم.

نکته‌ی جذابش اینه که performance خیلی بالایی داره؛ چون هر کامپوننت فقط به همون بخشی از store که بهش subscribe کرده گوش می‌ده و فقط وقتی همون مقدار عوض بشه re-render می‌شه.
نه اینکه با هر تغییر کوچیک، نصف اپ دوباره رندر بشه 😅

این یعنی:

  • کامپوننت‌های بینابینی اصلاً درگیر نمی‌شن

  • re-renderهای اضافی حذف می‌شن

  • درخت render کوچیک‌تر و تمیزتر می‌شه (در واقع هرس می‌شه)

من خودم تو پروژه‌هایی که state پرنوسان داشتن تجربه‌ی خیلی خوبی باهاش داشتم و بهبود performance کاملاً محسوس بود.

7️⃣ قرار دادن consumer ها در memo:

این راه هم می تونه مکمل باشه و جلوی re-render های عمیق رو بخاطر تغییر props ها بگیره. اما باید با دقت استفاده بشه که موثر واقع بشه.

جمع‌بندی

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

اگه stateهایی داریم که:

  • زیاد تغییر می‌کنن

  • به همه‌ی کامپوننت‌ها ربطی ندارن

  • یا باعث re-renderهای زنجیره‌ای شدن

می تونیم با استفاده از راه هایی که بررسی کردیم؛ performance اپلیکیشن رو بهبود بدیم.

خیلی ممنون که تا اینجا همراه بودین.

اگر دوست داشتین برام تو کامنتا بنویسین که شما از چه راه هایی برای بهینه سازی کانتکست استفاده می کنین.

با آرزوی سلامتی و موفقیت برای همه ی شما عزیزان.

reactjsfrontendبهینه سازیcontext
۰
۰
علی مقیمی
علی مقیمی
شاید از این پست‌ها خوشتان بیاید