فارسی

الگوهای پیشرفته React Context Provider را برای مدیریت مؤثر وضعیت، بهینه‌سازی عملکرد و جلوگیری از رندرهای مجدد غیرضروری در برنامه‌های خود کاوش کنید.

الگوهای React Context Provider: بهینه‌سازی عملکرد و جلوگیری از مشکلات رندر مجدد

API کانتکست ری‌اکت (React Context API) یک ابزار قدرتمند برای مدیریت وضعیت کلی (global state) در برنامه‌های شماست. این API به شما امکان می‌دهد تا داده‌ها را بین کامپوننت‌ها به اشتراک بگذارید بدون اینکه نیاز به پاس دادن دستی props در هر سطح داشته باشید. با این حال، استفاده نادرست از کانتکست می‌تواند منجر به مشکلات عملکردی، به‌ویژه رندرهای مجدد غیرضروری شود. این مقاله به بررسی الگوهای مختلف Context Provider می‌پردازد که به شما در بهینه‌سازی عملکرد و جلوگیری از این مشکلات کمک می‌کند.

درک مشکل: رندرهای مجدد غیرضروری

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

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

الگوی ۱: مموایز کردن مقدار با useMemo

ساده‌ترین رویکرد برای جلوگیری از رندرهای مجدد غیرضروری، مموایز کردن (memoize) مقدار کانتکست با استفاده از useMemo است. این کار تضمین می‌کند که مقدار کانتکست فقط زمانی تغییر می‌کند که وابستگی‌های آن تغییر کرده باشند.

مثال:

فرض کنید یک `UserContext` داریم که داده‌های کاربر و تابعی برای به‌روزرسانی پروفایل او را فراهم می‌کند.


import React, { createContext, useState, useMemo } from 'react';

const UserContext = createContext(null);

function UserProvider({ children }) {
  const [user, setUser] = useState({
    name: 'John Doe',
    email: 'john.doe@example.com',
    location: 'New York, USA'
  });

  const updateUser = (newUserData) => {
    setUser(prevState => ({ ...prevState, ...newUserData }));
  };

  const contextValue = useMemo(() => ({
    user,
    updateUser,
  }), [user, setUser]);

  return (
    
      {children}
    
  );
}

export { UserContext, UserProvider };

در این مثال، useMemo تضمین می‌کند که `contextValue` فقط زمانی تغییر می‌کند که وضعیت `user` یا تابع `setUser` تغییر کند. اگر هیچ‌کدام تغییر نکنند، کامپوننت‌هایی که از `UserContext` استفاده می‌کنند، دوباره رندر نخواهند شد.

مزایا:

معایب:

الگوی ۲: تفکیک مسئولیت‌ها با کانتکست‌های چندگانه

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

مثال:

به‌جای یک `UserContext` واحد، می‌توانیم کانتکست‌های جداگانه‌ای برای داده‌های کاربر و ترجیحات کاربر ایجاد کنیم.


import React, { createContext, useState } from 'react';

const UserDataContext = createContext(null);
const UserPreferencesContext = createContext(null);

function UserDataProvider({ children }) {
  const [user, setUser] = useState({
    name: 'John Doe',
    email: 'john.doe@example.com',
    location: 'New York, USA'
  });

  const updateUser = (newUserData) => {
    setUser(prevState => ({ ...prevState, ...newUserData }));
  };

  return (
    
      {children}
    
  );
}

function UserPreferencesProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const [language, setLanguage] = useState('en');

  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  return (
    
      {children}
    
  );
}

export { UserDataContext, UserDataProvider, UserPreferencesContext, UserPreferencesProvider };

اکنون، کامپوننت‌هایی که فقط به داده‌های کاربر نیاز دارند می‌توانند از `UserDataContext` استفاده کنند و کامپوننت‌هایی که فقط به تنظیمات تم نیاز دارند می‌توانند از `UserPreferencesContext` استفاده کنند. تغییرات در تم دیگر باعث رندر مجدد کامپوننت‌هایی که از `UserDataContext` استفاده می‌کنند نخواهد شد و بالعکس.

مزایا:

معایب:

الگوی ۳: توابع انتخابگر (Selector) با هوک‌های سفارشی

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

مثال:

با استفاده از `UserContext` اصلی، می‌توانیم هوک‌های سفارشی برای انتخاب ویژگی‌های خاص کاربر ایجاد کنیم.


import React, { useContext } from 'react';
import { UserContext } from './UserContext'; // Assuming UserContext is in UserContext.js

function useUserName() {
  const { user } = useContext(UserContext);
  return user.name;
}

function useUserEmail() {
  const { user } = useContext(UserContext);
  return user.email;
}

export { useUserName, useUserEmail };

اکنون، یک کامپوننت می‌تواند از `useUserName` استفاده کند تا فقط زمانی که نام کاربر تغییر می‌کند رندر مجدد شود و از `useUserEmail` استفاده کند تا فقط زمانی که ایمیل کاربر تغییر می‌کند رندر مجدد شود. تغییرات در سایر ویژگی‌های کاربر (مانند موقعیت مکانی) باعث رندر مجدد نخواهد شد.


import React from 'react';
import { useUserName, useUserEmail } from './UserHooks';

function UserProfile() {
  const name = useUserName();
  const email = useUserEmail();

  return (
    

Name: {name}

Email: {email}

); }

مزایا:

معایب:

الگوی ۴: مموایز کردن کامپوننت با React.memo

React.memo یک کامپوننت مرتبه بالاتر (HOC) است که یک کامپوننت تابعی را مموایز می‌کند. این کار از رندر مجدد کامپوننت در صورتی که props آن تغییر نکرده باشد، جلوگیری می‌کند. شما می‌توانید این روش را با کانتکست ترکیب کنید تا عملکرد را بیشتر بهینه‌سازی کنید.

مثال:

فرض کنید کامپوننتی داریم که نام کاربر را نمایش می‌دهد.


import React, { useContext } from 'react';
import { UserContext } from './UserContext';

function UserName() {
  const { user } = useContext(UserContext);
  return 

Name: {user.name}

; } export default React.memo(UserName);

با پیچیدن `UserName` در `React.memo`، این کامپوننت فقط در صورتی رندر مجدد می‌شود که پراپ `user` (که به‌طور ضمنی از طریق کانتکست ارسال شده) تغییر کند. با این حال، در این مثال ساده، `React.memo` به تنهایی از رندرهای مجدد جلوگیری نمی‌کند زیرا کل آبجکت `user` هنوز به عنوان یک پراپ پاس داده می‌شود. برای اینکه واقعاً مؤثر باشد، باید آن را با توابع انتخابگر یا کانتکست‌های جداگانه ترکیب کنید.

یک مثال مؤثرتر، ترکیب `React.memo` با توابع انتخابگر است:


import React from 'react';
import { useUserName } from './UserHooks';

function UserName() {
  const name = useUserName();
  return 

Name: {name}

; } function areEqual(prevProps, nextProps) { // Custom comparison function return prevProps.name === nextProps.name; } export default React.memo(UserName, areEqual);

در اینجا، `areEqual` یک تابع مقایسه سفارشی است که بررسی می‌کند آیا پراپ `name` تغییر کرده است یا خیر. اگر تغییر نکرده باشد، کامپوننت دوباره رندر نخواهد شد.

مزایا:

معایب:

الگوی ۵: ترکیب کانتکست و ردیوسرها (useReducer)

ترکیب کانتکست با useReducer به شما امکان می‌دهد منطق وضعیت پیچیده را مدیریت کرده و رندرها را بهینه‌سازی کنید. useReducer یک الگوی مدیریت وضعیت قابل پیش‌بینی فراهم می‌کند و به شما اجازه می‌دهد تا وضعیت را بر اساس اکشن‌ها به‌روز کنید، که نیاز به پاس دادن چندین تابع setter از طریق کانتکست را کاهش می‌دهد.

مثال:


import React, { createContext, useReducer, useContext } from 'react';

const UserContext = createContext(null);

const initialState = {
  user: {
    name: 'John Doe',
    email: 'john.doe@example.com',
    location: 'New York, USA'
  },
  theme: 'light',
  language: 'en'
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'UPDATE_USER':
      return { ...state, user: { ...state.user, ...action.payload } };
    case 'TOGGLE_THEME':
      return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
    case 'SET_LANGUAGE':
      return { ...state, language: action.payload };
    default:
      return state;
  }
};

function UserProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    
      {children}
    
  );
}

function useUserState() {
  const { state } = useContext(UserContext);
  return state.user;
}

function useUserDispatch() {
    const { dispatch } = useContext(UserContext);
    return dispatch;
}


export { UserContext, UserProvider, useUserState, useUserDispatch };

اکنون، کامپوننت‌ها می‌توانند با استفاده از هوک‌های سفارشی به وضعیت دسترسی پیدا کرده و اکشن‌ها را dispatch کنند. برای مثال:


import React from 'react';
import { useUserState, useUserDispatch } from './UserContext';

function UserProfile() {
  const user = useUserState();
  const dispatch = useUserDispatch();

  const handleUpdateName = (e) => {
    dispatch({ type: 'UPDATE_USER', payload: { name: e.target.value } });
  };

  return (
    

Name: {user.name}

); }

این الگو رویکرد ساختاریافته‌تری را برای مدیریت وضعیت ترویج می‌کند و می‌تواند منطق پیچیده کانتکست را ساده‌تر کند.

مزایا:

معایب:

الگوی ۶: به‌روزرسانی‌های خوش‌بینانه (Optimistic Updates)

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

مثال:

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


import React, { useContext, useState } from 'react';
import { UserContext } from './UserContext';

function LikeButton({ postId }) {
  const { dispatch } = useContext(UserContext);
  const [isLiking, setIsLiking] = useState(false);

  const handleLike = async () => {
    setIsLiking(true);
    // Optimistically update the like count
    dispatch({ type: 'INCREMENT_LIKES', payload: { postId } });

    try {
      // Simulate an API call
      await new Promise(resolve => setTimeout(resolve, 500));

      // If the API call is successful, do nothing (the UI is already updated)
    } catch (error) {
      // If the API call fails, revert the optimistic update
      dispatch({ type: 'DECREMENT_LIKES', payload: { postId } });
      alert('Failed to like post. Please try again.');
    } finally {
      setIsLiking(false);
    }
  };

  return (
    
  );
}

در این مثال، اکشن `INCREMENT_LIKES` بلافاصله dispatch می‌شود و اگر فراخوانی API ناموفق باشد، برگردانده می‌شود. این کار تجربه کاربری واکنش‌گراتری را فراهم می‌کند.

مزایا:

معایب:

انتخاب الگوی مناسب

بهترین الگوی Context Provider به نیازهای خاص برنامه شما بستگی دارد. در اینجا خلاصه‌ای برای کمک به انتخاب شما آورده شده است:

نکات اضافی برای بهینه‌سازی عملکرد کانتکست

نتیجه‌گیری

API کانتکست ری‌اکت یک ابزار قدرتمند است، اما استفاده صحیح از آن برای جلوگیری از مشکلات عملکردی ضروری است. با درک و به کارگیری الگوهای Context Provider که در این مقاله مورد بحث قرار گرفت، می‌توانید به طور مؤثر وضعیت را مدیریت کرده، عملکرد را بهینه‌سازی کنید و برنامه‌های ری‌اکت کارآمدتر و واکنش‌گراتری بسازید. به یاد داشته باشید که نیازهای خاص خود را تحلیل کرده و الگویی را انتخاب کنید که به بهترین وجه با الزامات برنامه شما مطابقت دارد.

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

الگوهای React Context Provider: بهینه‌سازی عملکرد و جلوگیری از مشکلات رندر مجدد | MLOG