📚 بخش ۲۰: پروژه عملی — ساخت Counter با useState

۱. مقدمه
شمارنده (Counter) یکی از اولین پروژههایی است که در یادگیری React ساخته میشود.
ممکنه ساده به نظر بیاد، ولی پشت این پروژه کوچک، مفاهیم بسیار مهمی درباره:
- تعریف و استفاده از State
- تغییر State با تابع Setter
- رندر مجدد کامپوننت
- جلوگیری از رندر بیدلیل
نهفته است.
ما در این بخش:
- نسخه سادهی Counter را میسازیم.
- نسخه پیشرفته با چند قابلیت اضافه میکنیم.
- مشکلات و خطاهای رایج را بررسی میکنیم.
- بهینهسازی انجام میدهیم.
۲. نسخه اولیه — سادهترین Counter
کد
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // مقدار اولیه 0
const increase = () => {
setCount(count + 1);
};
const decrease = () => {
setCount(count - 1);
};
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>شمارنده: {count}</h1>
<button onClick={increase}>افزایش</button>
<button onClick={decrease}>کاهش</button>
</div>
);
}
export default Counter;
تحلیل خط به خط
import { useState } from 'react';
هوک useState را از React وارد کردیم.const [count, setCount] = useState(0);
- count → مقدار فعلی شمارنده
- setCount → تابع تغییر شمارنده
- مقدار اولیه صفر
- توابع
increaseوdecrease:
- برای افزایش/کاهش count هر بار
setCountرا صدا میزنیم.
- در JSX با
{count}مقدار را نمایش میدهیم.
📌 هر بار روی یکی از دکمهها کلیک شود:
- تابع Setter صدا زده میشود.
- state تغییر میکند.
- کامپوننت دوباره رندر میشود.
- مقدار جدید count جایگزین مقدار قبلی در UI میشود.
۳. نسخه پیشرفته — افزودن دکمه RESET و محدودیت
در این نسخه:
- شمارنده بین ۰ تا ۱۰ حرکت میکند.
- دکمه Reset مقدار را دوباره به صفر برمیگرداند.
کد پیشرفته:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increase = () => {
if (count < 10) setCount(prev => prev + 1);
};
const decrease = () => {
if (count > 0) setCount(prev => prev - 1);
};
const reset = () => {
setCount(0);
};
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>شمارنده: {count}</h1>
<button onClick={increase}>افزایش</button>
<button onClick={decrease}>کاهش</button>
<button onClick={reset}>ریست</button>
</div>
);
}
export default Counter;
نکات مهم این نسخه:
- استفاده از callback form (
prev => prev + 1) برای اطمینان از اینکه مقدار جدید بر اساس آخرین مقدار فعلی محاسبه شود. - شرط گذاشتن برای جلوگیری از خروج از محدوده.
۴. نسخه سوم — پویا با مقدار اولیه از Props
میتوان مقدار اولیه شمارنده را از والد دریافت کرد.
این باعث میشود کامپوننت انعطافپذیرتر شود.
کد:
import { useState } from 'react';
function Counter({ initialValue = 0 }) {
const [count, setCount] = useState(initialValue);
const increase = () => setCount(prev => prev + 1);
const decrease = () => setCount(prev => prev - 1);
const reset = () => setCount(initialValue);
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>شمارنده: {count}</h1>
<button onClick={increase}>افزایش</button>
<button onClick={decrease}>کاهش</button>
<button onClick={reset}>ریست</button>
</div>
);
}
export default Counter;
و در والد:
function App() {
return <Counter initialValue={5} />;
}
📌 حالا کاربر میتواند شمارنده را از هر مقداری که میخواهد شروع کند.
۵. بهینهسازی — جلوگیری از رندر اضافی فرزندان
فرض کنیم داخل کامپوننت Counter یک کامپوننت فرزند داریم که به count وابسته نیست:
function Child() {
console.log("Child render");
return <p>من فرزند هستم</p>;
}
اگر Counter رندر شود، این فرزند هم دوباره اجرا میشود. برای جلوگیری:
import { memo } from 'react';
const Child = memo(function Child() {
console.log("Child render");
return <p>من فرزند هستم</p>;
});
✅ حالا فقط زمانی اجرا میشود که props آن تغییر کند.
۶. اشتباهات رایج در پروژه Counter
❌ تعیین state بدون استفاده از Setter:
count++;
⛔ باعث آپدیت UI نمیشود.
❌ بهروزرسانی state به صورت متوالی بدون callback → ممکن است مقدار نهایی اشتباه شود.
❌ قرار دادن مقدار اولیه به صورت متغیر محاسبه شده، بدون استفاده از Lazy Initialization اگر سنگین باشد → باعث اجرای غیرضروری در هر رندر میشود.
۷. نسخه Lazy Initialization
وقتی مقدار اولیه محاسبهاش سنگین است:
const [count, setCount] = useState(() => {
console.log("محاسبه سنگین مقدار اولیه...");
return 0;
});
📌 تابع فقط یکبار اجرا میشود (در اولین رندر).
۸. تمرینهای پیشنهادی
- Counter با سرعت: وقتی دکمه increase نگه داشته شود، هر ۲۰۰ میلیثانیه شمارنده افزایش یابد (با استفاده از
setIntervalوuseEffect). - Counter چندگانه: چند شمارنده مختلف بسازید که مستقل کار کنند.
- Counter اشتراکی: دو شمارنده بسازید که روی یک state والد کار کنند.
۹. جمعبندی
- ساخت Counter بهترین تمرین برای یادگیری تغییر state، استفاده از callback و جلوگیری از رندر اضافی است.
- میتوان با افزودن props، شرطها و قابلیتهای دیگر، آن را پیشرفته کرد.
- نکته کلیدی: state باید immutable باشد و تغییرش همیشه با Setter انجام شود.