هوکهای React — توابعی هستند که امکان استفاده از state، افکتها، context و سایر قابلیتهای React را در کامپوننتهای تابعی بدون نوشتن کلاس فراهم میکنند. هوکها که در React 16.8 معرفی شدند، رویکرد توسعه را رادیکالاً تغییر دادند و الگوهای پیچیدهای مانند HOC و render props را جایگزین کردند. به گفته React, 2024، ۱۵ هوک داخلی وجود دارد که تمام سناریوهای مدیریت کامپوننت را پوشش میدهند.
مهمترین نکات
هوکهای React — مجموعهای از توابع داخلی React هستند که امکان «اتصال» به مکانیزمهای داخلی فریمورک را بدون ایجاد کلاس فراهم میکنند. قبل از React 16.8، کامپوننتهای تابعی فقط میتوانستند props بپذیرند و JSX برگردانند — هر منطق پیچیدهای نیاز به کامپوننت کلاسی داشت.
مشکل کامپوننتهای کلاسی — جداسازی منطق مرتبط. کدی که به یک موجودیت تعلق داشت (مثلاً اشتراک WebSocket) بهاجبار بین componentDidMount، componentDidUpdate و componentWillUnmount تقسیم میشد. هوکها این را حل میکنند: تمام منطق در یک useEffect گروهبندی میشود.
مشکل دوم — استفاده مجدد از منطق. برای استفاده مجدد از منطق state بین کامپوننتهای کلاسی باید از HOC (کامپوننتهای مرتبه بالاتر) یا render props استفاده میشد — هر دو الگو تودرتو اضافی در درخت کامپوننت ایجاد میکردند. هوکهای سفارشی این مشکل را بدون تودرتو حل میکنند.
useState — پایهترین هوک. یک مقدار اولیه میپذیرد و یک چندتایی برمیگرداند: مقدار فعلی و تابعی برای بهروزرسانی آن. برخلاف this.setState در کلاسها، useState اشیاء را ادغام نمیکند — state جدید کاملاً جایگزین state قبلی برای فراخوانی خاص useState میشود.
import React, { useState } from 'react';
function Form() {
const [text, setText] = useState('');
const [submitting, setSubmitting] = useState(false);
async function handleSubmit() {
setSubmitting(true);
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify({ text })
});
setSubmitting(false);
}
return (
<div>
<input
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={handleSubmit}
disabled={submitting}>
{submitting ? 'Saving...' : 'Submit'}
</button>
</div>
);
}
setter useState میتواند تابعی دریافت کند که مقدار قبلی را گرفته و مقدار جدید را برمیگرداند. فرم تابعی زمانی الزامی است که state جدید از state قبلی محاسبه میشود و فراخوانیهای دستهای setState ممکن است — هر فراخوانی مقدار قبلی بهروز را دریافت خواهد کرد.
useEffect — هوک برای انجام عوارض جانبی در کامپوننتهای تابعی. دو آرگومان میپذیرد: تابع-افکت و آرایه وابستگیها. React افکت را بعد از هر رندر اجرا میکند، اما با تعیین وابستگیها — فقط وقتی هرکدام از آنها تغییر کرده است.
آرایه خالی وابستگیها [] به این معناست که افکت یک بار بعد از اولین رندر اجرا میشود (مشابه componentDidMount). برگرداندن تابع از افکت cleanup است که هنگام unmount (مشابه componentWillUnmount) و قبل از اجرای مجدد افکت اجرا میشود.
import React, { useState, useEffect } from 'react';
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = new WebSocket(`wss://chat/${roomId}`);
socket.onmessage = (event) => {
setMessages(prev => [...prev, JSON.parse(event.data)]);
};
// Cleanup - بستن سوکت
return () => socket.close();
}, [roomId]);
return <ul>
{messages.map((msg, i) =>
<li key={i}>{msg.text}</li>
)}
</ul>;
}
وابستگیهای useEffect — تنها ارتباط بین افکت و دنیای خارج هستند. اگر در افکت از متغیری از props یا state استفاده میشود، باید در آرایه وابستگیها باشد. نادیده گرفتن این قانون منشأ باگهای دادههای قدیمی (stale closure) است.
useContext — هوک برای خواندن مقدار context. شیء context ایجاد شده با React.createContext را میپذیرد و مقدار فعلی را برمیگرداند. اگر context تغییر کند، کامپوننت بهطور خودکار با مقدار جدید دوباره رندر میشود.
قبل از ظهور useContext، دسترسی به context در کامپوننتهای تابعی نیاز به پیچیدهسازی در Context.Consumer داشت که تودرتو ایجاد میکرد. useContext کاملاً Consumer را حذف میکند — کافی است هوک را در هر جای کامپوننت تابعی فراخوانی کنید.
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext('روشن');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button
className={`btn btn-${theme}`}
>
{theme === 'dark' ? 'moon' : '☀️'}
</button>;
}
useContext جایگزین Redux یا state سراسری نیست. React توصیه میکند از context برای مقادیر کمتغییر استفاده کنید — تم، زبان، احراز هویت. برای دادههایی که مکرراً بهروز میشوند، هر رندر تمام مصرفکنندگان context را دوباره محاسبه میکند که میتواند عملکرد را کاهش دهد.
useReducer — جایگزین useState برای stateهایی با منطق بهروزرسانی پیچیده. تابع reducer و state اولیه را میپذیرد، state فعلی و تابع dispatch را برمیگرداند. Reducer state فعلی و action را دریافت کرده، state جدید را برمیگرداند — یک تابع خالص بدون عوارض جانبی.
useReducer بهویژه زمانی مفید است که چندین action (set, reset, increment, addItem) یک state را مدیریت میکنند. به جای چندین useState با setterهای مختلف — یک reducer با انواع action مشخص. این منطق را قابل پیشبینی و بهراحتی قابل آزمایش میکند.
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'setStep':
return { ...state, step: action.step };
case 'reset':
return { count: 0, step: 1 };
default:
throw new Error('Unknown action: ' + action.type);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, {
count: 0,
step: 1
});
return (
<>
<p>Count: {state.count}</p>
<button
onClick={() => dispatch({ type: 'increment' })}>+</button>
<button
onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</>
);
}
useRef یک شیء قابل تغییر ایجاد میکند که بین رندرها حفظ میشود. تغییر current باعث رندر مجدد نمیشود. کاربرد اصلی — دسترسی به عناصر DOM و ذخیره مقادیر قابل تغییر (تایمرها، مقادیر قبلی، نمونههای کتابخانهها).
useMemo نتیجه تابع را حافظهگذاری میکند. مقدار را فقط وقتی یکی از وابستگیها تغییر کند دوباره محاسبه میکند. برای محاسبات سنگین استفاده کنید — فیلتر کردن آرایههای بزرگ، مرتبسازی، قالببندی دادهها. useCallback — حالت خاص useMemo برای حافظهگذاری توابع.
import React, { useRef, useMemo, useCallback } from 'react';
function SearchPage({ items, query, onItemSelect }) {
const inputRef = useRef(null);
// فوکوس خودکار هنگام mount
useEffect(() => {
inputRef.current?.focus();
}, []);
// ذخیره نتیجه فیلتر شده
const filtered = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase()
.includes(query.toLowerCase())
);
}, [items, query]);
// ذخیره callback
const handleSelect = useCallback((id) => {
onItemSelect(id);
}, [onItemSelect]);
return (
<div>
<input ref={inputRef}
value={query}
onChange={e => setQuery(e.target.value)}
/>
<ul>
{filtered.map(item =>
<li key={item.id}>
<button
onClick={() => handleSelect(item.id)}>
{item.name}
</button>
</li>
)}
</ul>
</div>
);
}
قانون مهم: useRef, useMemo و useCallback — ابزارهای بهینهسازی هستند، نه ساختارهای اجباری. همه چیز را در useMemo نپیچید — ابتدا عملکرد را اندازهگیری کنید. بهینهسازی زودهنگام کد را پیچیده میکند. React کامپوننتها را سریع رندر میکند؛ useMemo فقط زمانی نیاز است که محاسبات واقعاً سنگین باشند (O(n²) و بالاتر).
React دو قانون سختگیرانه برای هوکها معرفی کرد. اول: هوکها را فقط در سطح بالایی فراخوانی کنید — نه داخل شرطها، حلقهها یا توابع تودرتو. دوم: هوکها را فقط از کامپوننتهای تابعی React یا هوکهای سفارشی فراخوانی کنید. پلاگین ESLint eslint-plugin-react-hooks بهطور خودکار هر دو قانون را بررسی میکند.
رایجترین خطا — نقض ترتیب فراخوانی هوکها. React به ترتیب فراخوانی هوکها بین رندرها متکی است. اگر هوک داخل شرط باشد و در یکی از رندرها فراخوانی نشود، تمام هوکهای بعدی جابهجا میشوند و باعث باگ میشوند.
| خطا | چه اتفاقی میافتد | راهحل |
|---|---|---|
| هوک در شرط | ترتیب هوکها بهم میخورد | منطق شرط را داخل هوک منتقل کنید |
| Stale closure | افکت از مقدار قدیمی استفاده میکند | همه وابستگیها را به آرایه اضافه کنید |
| حلقه بینهایت | useEffect بدون وابستگی state را تغییر میدهد | وابستگیها را مشخص کنید یا از useReducer استفاده کنید |
| تغییر ref در رندر | عوارض جانبی در بدنه تابع | جهشها را به useEffect منتقل کنید |
دومین خطای رایج — stale closure در useEffect. اگر در افکت از متغیری استفاده شود که در وابستگیها مشخص نشده، افکت مقدار آن را در لحظه ایجاد بستار «گرفته» است. راهحل — همیشه تمام متغیرهای استفادهشده را در آرایه وابستگیها قرار دهید. اگر وابستگیها زیاد هستند، افکت را به چند بخش تقسیم کنید.
سوالات متداول
React به ترتیب فراخوانی هوکها بین رندرها متکی است. اگر هوک در شرط فراخوانی شده و در یکی از رندرها کار نکرده باشد، تمام هوکهای بعدی «جابهجا میشوند» — state آنها بهم میریزد. قانون ESLint hooks/exhaustive-deps در این باره هشدار میدهد.
useLayoutEffect همگام است و قبل از اینکه مرورگر تغییرات را ترسیم کند اجرا میشود. useEffect ناهمگام است — بعد از ترسیم. از useLayoutEffect فقط زمانی استفاده کنید که باید DOM را قبل از دیدن تغییرات توسط کاربر اندازهگیری یا تغییر دهید (موقعیت popover، بازیابی اسکرول).
داخل useEffect از AbortController استفاده کنید. یک controller ایجاد کنید، signal را به fetch بدهید و در تابع cleanup controller.abort() را فراخوانی کنید. برای مرورگرهای قدیمی پرچم cancelled کافی است: return () => { cancelled = true; } با بررسی قبل از setState.
هوک سفارشی — تابع JavaScript با پیشوند use که از هوکهای داخلی استفاده میکند. زمانی ایجاد کنید که همان منطق با هوکها در دو یا چند کامپوننت تکرار میشود. مثالها: useDebounce، useLocalStorage، useMediaQuery. هوکهای سفارشی جایگزین HOC و render props میشوند.
در Strict Mode React عمداً کامپوننت را در حالت توسعه دو بار mount و unmount میکند تا خطاهای مربوط به افکتهای پاکنشده را پیدا کند. اگر cleanup به درستی نوشته شده باشد، فراخوانی دوگانه مشکلی ایجاد نمیکند. در حالت production این اتفاق نمیافتد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید