ESC را فشار دهید تا بسته شود

آموزش استفاده از Context API در React

کتابخانه React به عنوان یکی از محبوب ترین کتابخانه های Frontend قابلیت های کاربردی و جذابی را به توسعه دهندگان خود ارائه میکند. یکی از جالب ترین و کاربردی ترین این قابلیت ها Context API نام دارد. در این مقاله بصورت تخصصی به معرفی، بیان کاربردها و نحوه استفاده از React Context API خواهیم پرداخت. پا با ما همراه باشید.

کاربرد Context API در React چیست؟

اگر تجربه کار کردن با کتابخانه ری اکت (یا سایر کتابخانه ها و فریمورک های جاوااسکریپتی مانند Vue) را داشته باشید، احتمالا میدانید که یکی از اصلی ترین مفاهیم برای اجرای Reactivity در این کتابخانه ها مفهوم State است. State ها مقادیر تغییر پذیری هستند که شما در ساختار برنامه خود و درون کامپوننت ها تعریف میکنید تا از طریق آن بتوانید فرایند های اپلیکیشن را کنترل و مدیریت کنید. ساده ترین مثال برای استیت، ساخت یک counter است که با کلیک بر روی یک دکمه مقدار آن افزایش پیدا میکند.

import { useState } from "react";

export default function App() {
  const [counter, setCounter] = useState(0);

  return (
    <div className="App">
      <h1>Your Counter: {counter}</h1>
      <button onClick={() => setCounter(counter + 1)}>Add</button>
    </div>
  );
}

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

کاربرد اصلی Context تعریف و اشتراک گذاری یک مقدار (State) در بین چندین کامپوننت و یا درختی از کامپوننت های React است. به این صورت که با شما با ساختن یک Context Provider و قراردادن مجموعه ای از کامپوننت ها در داخل آن یک مقدار را در آن کامپوننت و همه زیر مجموعه های آن share میکنید. برای درک بهتر موضوع قسمت بعدی را مطالعه کنید.

آموزش استفاده از Context در React با مثال عملی

فرض کنید که شما نیاز دارید تا برای اپلیکیشن خود یک State سراسری به نام theme تعریف کنید که مقدار آن میتواند light و یا dark باشد. احتمالا مقدار theme را در همه کامپوننت های خود نیاز دارید و میخواهید بر اساس آن رنگ بندی و سایر ویژگی های کامپوننت خود را تغییر دهید. برای این کار کافی است طبق مراحل زیر یک Context تعریف کرده و از آن استفاده کنید.

1- ساخت یک Context جدید

در ابتدا نیاز دارید که یک Context جدید بسازید. برای این کار میتوانید از تابع createContext در React استفاده کنید:

import { createContext } from 'react';

export const ThemeContext = createContext({
  theme: 'light'
});

2- قراردادن اپلیکیشن درون Context Provider

در مرحله بعدی نیاز دارید که کل اپلیکیشن خود (یا قسمتی از آن که نیاز دارد به مقدار theme دسترسی داشته باشد) را درون Context Provider قرار دهید تا بتوانید مقدار theme را در آن به اشتراک بگذارید:

import { ThemeContext } from './contexts/ThemeContext';

export const App = () => {
  return (
  	<ThemeContext.Provider value={{ theme: 'light' }}>
      <MyApplication />
    </ThemeContext.Provider>
  );
}

همانطور که در کد بالا می بینید، ما کل اپلیکیشن خود را درون Context provider قرار دادیم و حالا کل کامپوننت های زیر مجموعه آن به مقدار مورد نظر دسترسی دارند. حالا نیاز داریم که در یکی از کامپوونت های زیر مجموعه این مقدار را دریافت کنید و از آن استفاده نماییم.

3- استفاده از مقدار Context به کمک هوک useContext

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

import { useContext } from 'react';
import { ThemeContext } from './contexts/ThemeContext';

export const ChildComponent = () => {

	const { theme } = useContext(ThemeContext);
	
	return (
		<div className={`theme-${theme}`}>
			<h1>Application Theme is: {theme}</h1>
		</div>
	);
}

تغییر دادن مقدار Context در زیر مجموعه ها به کمک State

در مثال بالا یک حالت بسیار ساده از نحوه استفاده Context در React را نشان دادیم. اما در این مثال مقدار theme یک مقدار ثابت و غیر قابل تغییر است و نمیتوان مقدار آن را در کامپوننت های زیر مجموعه تغییر داد. برای رفع این مشکل کافی است مثال بالا به روش زیر انجام شود:

1- ساخت Context جدید

مانند مثال مرحله قبل، در ابتدا نیاز داریم که یک Context جدید بسازیم اما با اندکی تفاوت!

import { createContext } from 'react';

export const ThemeContext = createContext({
  theme: 'light',
  setTheme: () => false
});

اگر به کد بالا دقت کنید در اینجا علاوه بر تعریف مقدار theme یک تابع setTheme هم درون بدنه Context ساختیم. این تابع قرار است وظیفه بروزرسانی مقدار theme را بر عهده داشته باشد. برای درک بهتر کاربرد این تابع مراحل بعدی را دنبال کنید.

2- قرار دادن اپلیکیشن درون Context Provider

مانند مثال قبلی دوباره باید کل اپلیکیشن خود را درون Context Provider ای که ساختیم قرار بدهیم:

import { useState } from 'react';
import { ThemeContext } from './contexts/ThemeContext';

export const App = () => {

	const [theme, setTheme] = useState('light');
	
  return (
  	<ThemeContext.Provider value={{ theme, setTheme }}>
      <MyApplication />
    </ThemeContext.Provider>
  );
}

احتمالا یک تفاوت بزرگ در این مثال با مثال قبلی می بینید. در مثال قبلی ما یک مقدار ثابت (استاتیک) را به عنوان value به Context Provider خود تزریق کردیم، اما در این روش با ساخت یک state ، مقدار state و تابع تغییر دهنده state را به Provider خود ارسال کردیم. با این روش مقدار theme و تابع setTheme در سرتاسر اپلیکیشن ما در دسترس خواهند بود. این به این معناست که ما در همه کامپوننت های زیر مجموعه، میتوانیم به کمک تابع setTheme مقدار theme را در همه جا اپلیکیشن بروزرسانی کنیم.

3- دریافت و تغییر مقادیر Context

همانطور که بالاتر گفته شد اکنون ما به مقدار theme و تابع setTheme در همه جای برنامه خود دسترسی داریم. بنابراین:

import { useContext } from 'react';
import { ThemeContext } from './contexts/ThemeContext';

export const ChildComponent = () => {

	const { theme, setTheme } = useContext(ThemeContext);
	
	const handleChangeTheme = () => {
		if(theme === 'dark') {
			setTheme('light')
		} else {
			setTheme('dark')
		}
	}
	
	return (
		<div className={`theme-${theme}`}>
			<h1>Application Theme is: {theme}</h1>
			<button onClick={handleChangeTheme}>Change Theme</button>
		</div>
	);
}

در کد بالا، یک دکمه اضافه کردیم که با کلیک کردن کاربر بر روی آن تابع handleChangeTheme اجرا میشود. این تابع با فراخوانی تابع سراسری setTheme مقدار جدید theme را تعیین کرده و آن را در کل اپلیکیشن و محدوده زیرمجموعه Context تغییر میدهد. به همین سادگی!

آموزش Context با Typescript

اگر در پروژه خود از Typescript استفاده میکنید برای ساخت و استفاده از Context باید به روش زیر عمل کنید.

1- تعریف interface در زمان ساخت Context

در جایی که میخواهید Context خود را بسازید باید برای مقادیر موجود در Context تایپ های مناسب انتخاب کنید. برای تبدیل مثال قبلی به Typescript به روش زیر عمل کنید:

import { createContext, Dispatch, SetStateAction } from 'react';

// Define the type for the theme
export type Theme = 'light' | 'dark';

// Define the type for the context
interface ThemeContextType {
  theme: Theme;
  setTheme: Dispatch<SetStateAction<Theme>>;
}

export const ThemeContext = createContext<ThemeContextType>({
  theme: 'light',
  setTheme: () => false
});

2- تعریف تایپ در زمان ساخت State

با انجام مرحله قبلی احتمالا یک خطای تایپ اسکریپتی در قسمت تعریف state خود با useState دریافت میکنید. برای رفع این خطا تغییر زیر را در جمله useState خود اعمال کنید:

import { useState } from 'react';
import { ThemeContext, Theme } from './contexts/ThemeContext';

export const App = () => {

	const [theme, setTheme] = useState<Theme>('light');
	
  return (
  	<ThemeContext.Provider value={{ theme, setTheme }}>
      <MyApplication />
    </ThemeContext.Provider>
  );
}

در کد بالا، همانطور که می بینید، تایپ استیت را به مقدار Theme تغییر دادیم که با interface ای که برای ThemeContext ساخته بودیم تطابق داشته باشد.

جلوگیری از Prop Drilling به کمک Context

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

MyApplication
	- Hero
		- Card
			- Button
				- Icon

در ساختار بالا، اگر بخواهید یک مقدار را از کامپوونت والد MyApplication به کامپوننت Icon برسانید، باید مقدار مورد نظر را بصورت props از بالا به پایین ارسال کنید! به این پدیده ارسال چندین مرحله ای دیتا از والد اصلی به چند مرحله پایین تر Prop Drilling گفته میشود. Prop Drilling علاوه بر اینکه کدبیس شما را از حالت استاندارد خارج کرده و اصول Clean Code را نقض میکند، میتواند تاثیر منفی بسیار زیادی بر روی پرفورمنس اپلیکیشن بگذارد و آن را به شدت کُند بکند. استفاده از Context در چنین شرایطی میتواند بسیار راهگشا باشد.

Best Practice ها در استفاده از React Context

مثل هر موضوع دیگری در برنامه نویسی، برای ساخت و استفاده از Context ها هم بی نهایت راه و روش مختلف وجود دارد. ولی برای استفاده بهینه و رعایت اصول کدنویسی تمیز بهتر است در هنگام استفاده از Context به نکات زیر توجه کنید:

1- برای هر کاربرد Context های جداگانه بسازید

Context را برای یک هدف خاص استفاده کنید و داده‌ها یا عملکردهای نامرتبط را در یک Context مشترک قرار ندهید. به عنوان مثال برای نگهداشتن اطلاعات کاربری UserContext ، برای مدیریت اطلاعات تم ThemeContext و … را جداگانه بسازید و از ادغام کردن همه این موارد در یک Context مشترک خودداری کنید. ادغام کردن Context های مختلف در یک Context باعث ایجاد ری رندر های اضافه در اپلیکیشن شده و پرفورمنس برنامه شما را کاهش میدهد.

2- به جای استفاده مستقیم از useContext از هوک های سفارشی استفاده کنید

برای تمیز تر شدن کد شما و جلوگیری از ایمپورت کردن فایل Context در چندین جای مختلف بهتر است یک هوک سفارشی طراحی کرده و درون آن از useContext استفاده کنید. در مثال بالا ما میتوانید یک هوک سفارشی به نام useTheme به شکل زیر بسازیم:

export const useTheme = () => {

    const context = useContext(ThemeContext);
  
    return context;
}

و سپس هر جا که نیاز به مقادیر موجود در Context داشتید، از هوک سفارشی خود به جای فراخوانی مستقیم useContext استفاده کنید:

import { useTheme } from './hooks';

export const ChildComponent = () => {

	const { theme, setTheme } = useTheme();
	
	return (
		<div className={`theme-${theme}`}>
			<h1>Application Theme is: {theme}</h1>
		</div>
	);
}

3- مقادیر Context را Memoize کنید

میتوانید با استفاده از هوک useMemo ، مقادیر Context خود را کش کنید و از ری رندر های غیرضروری اپلیکیشن جلوگیری نمایید. با اینکار فقط در حالتی که مقادیر تغییر کنند کامپوننت ها ری رندر میشوند:

export const ThemeProvider: React.FC = ({ children }) => {
  const [theme, setTheme] = useState<Theme>('light');

  const value = React.useMemo(() => ({ theme, setTheme }), [theme, setTheme]);

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
};

4- از Context برای داده های غیر سراسری و سطحی استفاده نکنید

اگر نیازمندی شما برای انتقال مقادیر با دو یا سه مرحله Props Drilling برطرف میشود از Context استفاده نکنید. زیرا استفاده بیش از حد از Context میتواند وابستگی غیر ضروری بین کامپوننت ها را افزایش داده و سبب مشکلات عملکردی و کاهش پرفورمنس شود. همچنین توصیه میشود برای نیازمندی های پیچیده تر و مدیریت داده های که نیاز به عملکرد های منطقی حرفه ای تری دارند از کتابخانه های مدیریت استیت پیشرفته مانند Redux و Mobx و … استفاده کنید.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *