چرا وضعیت (state) با استفاده از hook وضعیت در setInterval بهروزرسانی نمیشود؟
کد زیر را در نظر بگیرید:
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(prevTime => prevTime + 1); // <-- Change this line!
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
در این کد، می خواهیم کامپوننت ساعت (Clock) مقدار زمان را هر ثانیه افزایش دهید. با این حال، مقدار زمان بیشتر از یک افزایش نمییابد. این مشکل به دلیل رفتار useEffect و closure در جاوااسکریپت و نحوه استفاده از hookهای React به وجود میآید.
وقتی شما از setInterval استفاده میکنید، تابعی که به آن پاس دادهاید فقط به حالت اول متغیر time دسترسی دارد؛ در واقع، به دلیل عدم بروزرسانی useEffect در رندرهای بعدی، time همیشه برابر با مقدار اولیه (که 0 است) باقی میماند. این بدان معناست که هر بار که setInterval صدا زده میشود، از همان مقدار اولیه استفاده میشود و افزایش آن تأثیرگذار نخواهد بود.
برای حل این مشکل، شما باید از فرم قابلیت callback برای setTime استفاده کنید، مانند این:
setTime(prevTime => prevTime + 1);
این گونه شما به آخرین مقدار time دسترسی پیدا میکنید و مطمئن میشوید که مقدار جدید به درستی افزایش مییابد.
یا می توانید به صورت زیر time را بعنوان وابستگی useEffect تعریف کنیم تا با تغییر مقدار time، تابع useEffect نیز مجدد فراخوانی شود.
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = setTimeout(() => {
setTime(time + 1);
}, 1000);
return () => {
clearTimeout(timer);
};
}, [time]);
بهجز این، میتوانید یک hook مخصوص برای setInterval ایجاد کنید که کد شما تمیزتر و سادهتر شود. در زیر یک مثال از یک hook به نام useInterval آورده شده است:
function useInterval(callback, delay) {
const intervalRef = React.useRef();
const callbackRef = React.useRef(callback);
React.useEffect(() => {
callbackRef.current = callback;
}, [callback]);
React.useEffect(() => {
if (typeof delay === 'number') {
intervalRef.current = window.setInterval(() => callbackRef.current(), delay);
return () => window.clearInterval(intervalRef.current);
}
}, [delay]);
return intervalRef;
}
شما میتوانید از این hook در کامپوننت ساعت خود استفاده کنید و بسیار سادهتر و تمیزتر کد نویسی کنید. همچنین میتوانید قابلیت توقف (pause) و ادامه (resume) را با تغییر مقدار delay به null پیادهسازی کنید.