
اگر مدتی با React کار کرده باشی، احتمالاً با یکی از این موقعیتها روبهرو شدی:
state آپدیت میشود، ولی داخل setInterval مقدار قدیمی چاپ میشود
یک event handler انگار «در گذشته گیر کرده»
یا لاگها با چیزی که روی UI میبینی نمیخوانند
و معمولاً جواب این است:
«این به خاطر Closure است»
ولی Closure در React دقیقاً یعنی چه؟
یک تابع، متغیرهایی را که موقع ساخته شدنش وجود داشتهاند،حتی بعد از پایان آن scope، در حافظه نگه میدارد.
function outer() { let count = 0 return function inner() { console.log(count) } } const fn = outer() fn() // 0
تابع inner هنوز count را میشناسد،با اینکه outer دیگر اجرا نمیشود.این رفتار اسمش Closure است.
چون React اینطوری کار میکند:
با هر render
متغیرها دوباره ساخته میشوند و توابع دوباره ساخته میشوند
اما بعضی توابع قدیمی زنده میمانند(مثل setInterval، event handlerها، effectها)
و اینجا Closure خودش را نشان میدهد.
function Counter() { const [count, setCount] = React.useState(0) React.useEffect(() => { const id = setInterval(() => { console.log('interval sees:', count) }, 1000) return () => clearInterval(id) }, []) return ( <button ={() => setCount(count + 1)}> count: {count} </button> ) }
با کلیک، عدد روی صفحه زیاد میشود ✅
ولی داخل console، همیشه 0 چاپ میشود ❌
useEffect فقط یکبار اجرا شده
تابع داخل setInterval همان موقع ساخته شده
آن تابع، count مربوط به رندر اول را در Closure خودش نگه داشته
به زبان ساده:
این interval فقط «گذشته» را میبیند، نه state فعلی را
.Render 1:
count = 0
interval → count = 0 را ذخیره میکند
Render 2:
count = 1
interval هنوز همان closure قدیمی را دارد
Closure خودش آپدیت نمیشود؛باید دوباره ساخته شود.
useEffect(() => { const id = setInterval(() => { console.log(count) }, 1000) return () => clearInterval(id) }, [count])
✔ همیشه مقدار جدید را میبیند
✖ interval در هر تغییر destroy و create میشود
برای بعضی سناریوها خوب است، برای بعضی نه.
اگر هدف فقط آپدیت state است:
setCount(prev => prev + 1)
این روش چرا خوبه؟
به Closure وابسته نیست
React مقدار آخر state را تضمین میکند
باگهای Closure را دور میزند
وقتی میخواهی:
effect فقط یکبار اجرا شود
ولی همیشه به آخرین مقدار دسترسی داشته باشی
function Counter() { const [count, setCount] = React.useState(0) const countRef = React.useRef(count) React.useEffect(() => { countRef.current = count }, [count]) React.useEffect(() => { const id = setInterval(() => { console.log(countRef.current) }, 1000) return () => clearInterval(id) }, []) return ( <button ={() => setCount(c => c + 1)}> {count} </button> ) }
اینجا:
ref با render عوض نمیشود
مقدارش همیشه بهروز است
interval همیشه مقدار جدید را میخواند
این الگو در پروژههای بزرگ خیلی رایج است
const handleClick = () => { setTimeout(() => { console.log(value) }, 2000) }
اگر value قبل از ۲ ثانیه تغییر کند:
مقدار قدیمی چاپ میشود
دلیل؟
همان Closure.
Closure باگ React نیست ویژگی JavaScript است
React فقط باعث میشود بیشتر دیده شود
هر وقت در React دیدی:
داده قدیمی است
state درست به نظر نمیرسد
لاگها عجیباند
از خودت بپرس:
این تابع کی ساخته شده؟
چه متغیرهایی را در Closure خودش نگه داشته؟
به مقدار جدید نیاز دارم یا مقدار زمان ساخت؟