مقدمه
یکی از مهمترین مفاهیم در React، State یا «وضعیت» است. State دادههایی هستند که در طول اجرای برنامه تغییر میکنند و هر زمان مقدار آنها تغییر کند، رابط کاربری (UI) نیز بهصورت خودکار بهروزرسانی میشود.
در نسخههای اولیه React، مدیریت State تنها در Class Componentها امکانپذیر بود و توسعهدهندگان برای استفاده از State مجبور بودند از متدهایی مانند this.setState() استفاده کنند. اما از نسخه React 16.8 به بعد، قابلیتی به نام Hooks معرفی شد که امکان استفاده از State و سایر قابلیتهای React را در Functional Componentها فراهم میکند.
امروزه تقریباً تمام پروژههای جدید React از Hookها استفاده میکنند؛ زیرا کد را سادهتر، خواناتر و قابل نگهداریتر میکنند.
در این آموزش یاد میگیرید چگونه با استفاده از Hookها، State را در کامپوننتهای React مدیریت کنید و بهترین روش استفاده از useState را در پروژههای واقعی بیاموزید.
پیشنیازها
برای دنبال کردن این آموزش بهتر است با موارد زیر آشنا باشید:
- JavaScript ES6
- مفاهیم پایه React
- JSX
- کامپوننتهای تابعی (Functional Components)
- npm و Node.js
اگر هنوز React را نصب نکردهاید، پیشنهاد میشود ابتدا آخرین نسخه Node.js را نصب کرده و سپس یک پروژه React ایجاد کنید.
State چیست؟
State مجموعهای از دادههاست که داخل یک کامپوننت نگهداری میشود و هر زمان مقدار آن تغییر کند، React کامپوننت را مجدداً Render میکند.
برای مثال فرض کنید یک شمارنده داریم.
در ابتدا مقدار آن صفر است.
Count: 0
اگر کاربر روی دکمه کلیک کند:
Count: 1
با هر کلیک مقدار افزایش پیدا میکند.
Count: 2
Count: 3
Count: 4
این مقدار همان State برنامه است.
Hook چیست؟
Hookها توابع ویژهای هستند که React در اختیار توسعهدهندگان قرار داده است تا بدون استفاده از Class Component بتوانند قابلیتهای مختلف React را در Functional Componentها استفاده کنند.
برخی از Hookهای پرکاربرد عبارتاند از:
- useState
- useEffect
- useContext
- useReducer
- useMemo
- useCallback
- useRef
در این مقاله تمرکز ما روی useState خواهد بود.
Hook useState
useState سادهترین و پرکاربردترین Hook در React است.
این Hook دو مقدار در اختیار شما قرار میدهد:
- مقدار فعلی State
- تابعی برای تغییر مقدار State
ساختار کلی آن به شکل زیر است.
const [state, setState] = useState(initialValue);
در این دستور:
stateمقدار فعلی است.setStateتابع تغییر مقدار است.initialValueمقدار اولیه State محسوب میشود.
اولین مثال با useState
ابتدا Hook را وارد پروژه کنید.
import { useState } from "react";
سپس یک State تعریف کنید.
const [count, setCount] = useState(0);
در این مثال:
- مقدار اولیه برابر 0 است.
- هر زمان
setCountاجرا شود، مقدار جدید جایگزین خواهد شد.
نمایش مقدار State
اکنون مقدار را داخل JSX نمایش میدهیم.
function App() {
const [count, setCount] = useState(0);
return (
<h1>{count}</h1>
);
}
در اولین Render خروجی برابر خواهد بود با:
0
تغییر مقدار State
اکنون یک دکمه اضافه میکنیم.
<button
onClick={() => setCount(count + 1)}
>
Increase
</button>
با هر بار کلیک:
0
↓
1
↓
2
↓
3
بدون اینکه صفحه Refresh شود، React مقدار جدید را نمایش میدهد.
چرا نباید مقدار State را مستقیماً تغییر دهیم؟
گاهی افراد تازهکار این کار را انجام میدهند.
count = count + 1;
یا
count++;
این روش اشتباه است.
دلیل آن این است که React تنها زمانی متوجه تغییر State میشود که از تابع Setter استفاده کنید.
روش صحیح:
setCount(count + 1);
به این ترتیب React متوجه تغییر شده و کامپوننت را دوباره Render میکند.
فرآیند Render در React
زمانی که تابع Setter اجرا میشود:
- مقدار جدید ذخیره میشود.
- کامپوننت دوباره اجرا میشود.
- JSX مجدداً تولید میشود.
- React فقط بخشهایی از DOM را که تغییر کردهاند بهروزرسانی میکند.
به همین دلیل React عملکرد بسیار بالایی در بهروزرسانی رابط کاربری دارد.
مدیریت انواع مختلف State با useState
در مثال قبلی فقط یک عدد را داخل State ذخیره کردیم، اما useState میتواند تقریباً هر نوع دادهای را نگهداری کند.
برای مثال:
- String
- Number
- Boolean
- Array
- Object
- حتی Function
در ادامه هرکدام را بررسی میکنیم.
استفاده از String
فرض کنید میخواهیم نام کاربر را ذخیره کنیم.
import { useState } from "react";
function App() {
const [name, setName] = useState("Ali");
return (
<>
<h2>{name}</h2>
<button onClick={() => setName("Sara")}>
Change Name
</button>
</>
);
}
در ابتدا خروجی:
Ali
پس از کلیک روی دکمه:
Sara
استفاده از Boolean
Boolean معمولاً برای نمایش یا مخفی کردن بخشهای مختلف رابط کاربری استفاده میشود.
const [isLoggedIn, setIsLoggedIn] = useState(false);
در JSX:
{
isLoggedIn
? <p>Welcome</p>
: <p>Please Login</p>
}
تغییر وضعیت:
<button
onClick={() => setIsLoggedIn(true)}
>
Login
</button>
یا برای تغییر مقدار فعلی:
setIsLoggedIn(!isLoggedIn);
استفاده از Array
یکی از رایجترین کاربردهای React، نگهداری لیستی از اطلاعات است.
const [users, setUsers] = useState([
"Ali",
"Sara",
"Reza"
]);
نمایش لیست:
<ul>
{
users.map(user => (
<li key={user}>{user}</li>
))
}
</ul>
اضافه کردن عضو جدید
در React نباید مستقیماً آرایه را تغییر دهید.
روش اشتباه:
users.push("Nima");
یا
users[0] = "Amir";
این روش باعث Render مجدد نمیشود.
روش صحیح:
setUsers([
...users,
"Nima"
]);
خروجی:
Ali
Sara
Reza
Nima
استفاده از Spread Operator (...) باعث میشود یک آرایه جدید ساخته شود و React تغییر را تشخیص دهد.
استفاده از Object
گاهی لازم است چند ویژگی مرتبط را داخل یک State نگهداری کنیم.
const [user, setUser] = useState({
name: "Ali",
age: 25,
city: "Tehran"
});
نمایش اطلاعات:
<h2>{user.name}</h2>
<p>{user.age}</p>
<p>{user.city}</p>
بروزرسانی Object
اشتباه رایج:
user.name = "Sara";
یا
setUser({
name: "Sara"
});
روش دوم اگرچه از Setter استفاده میکند، اما تمام فیلدهای دیگر حذف خواهند شد.
روش صحیح:
setUser({
...user,
name: "Sara"
});
اکنون فقط مقدار name تغییر میکند و سایر ویژگیها حفظ خواهند شد.
چند State یا یک State؟
فرض کنید اطلاعات زیر را داریم:
- نام
- ایمیل
- سن
دو روش وجود دارد.
روش اول:
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [age, setAge] = useState(0);
روش دوم:
const [user, setUser] = useState({
name: "",
email: "",
age: 0
});
هر دو روش صحیح هستند.
اگر دادهها به هم مرتبط هستند (مثل اطلاعات یک کاربر)، استفاده از یک Object معمولاً خواناتر است. اما اگر دادهها مستقل باشند، تعریف Stateهای جداگانه نگهداری کد را سادهتر میکند.
مقدار اولیه State فقط یکبار استفاده میشود
یکی از نکاتی که بسیاری از توسعهدهندگان تازهکار با آن اشتباه میکنند، این است که مقدار اولیه useState فقط در اولین Render کامپوننت استفاده میشود.
const [count, setCount] = useState(0);
اگر بعداً مقدار 0 را تغییر دهید، React دوباره از آن استفاده نخواهد کرد؛ زیرا مقدار فعلی State را در حافظه نگه میدارد.
اگر لازم باشد State بر اساس تغییرات Props یا دادههای خارجی بهروزرسانی شود، باید از Hookهایی مانند useEffect استفاده کنید که در بخشهای بعدی به آن خواهیم پرداخت.
استفاده از مقدار قبلی State
در بسیاری از مواقع مقدار جدید State به مقدار قبلی آن وابسته است.
برای مثال، افزایش شمارنده:
setCount(count + 1);
این روش معمولاً درست کار میکند، اما اگر چندین بار پشت سر هم مقدار State را تغییر دهید، ممکن است نتیجه مورد انتظار را دریافت نکنید.
React به منظور افزایش کارایی، بروزرسانیهای State را Batch میکند؛ یعنی چند تغییر را با هم پردازش میکند.
به همین دلیل بهتر است در چنین شرایطی از Callback Function استفاده کنید.
روش پیشنهادی
setCount(previousCount => previousCount + 1);
یا به شکل کوتاهتر:
setCount(prev => prev + 1);
در اینجا:
prevآخرین مقدار واقعی State است.- React همیشه مقدار صحیح را در اختیار شما قرار میدهد.
مثال
فرض کنید میخواهید با یک کلیک، مقدار شمارنده سه واحد افزایش پیدا کند.
روش اشتباه:
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
خروجی:
1
نه 3!
دلیل این موضوع این است که هر سه دستور از مقدار اولیه count استفاده میکنند.
روش صحیح:
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
اکنون خروجی:
3
خواهد بود.
این روش همیشه برای زمانی که مقدار جدید به مقدار قبلی وابسته است توصیه میشود.
استفاده از State در فرمها
یکی از رایجترین کاربردهای useState مدیریت فرمهاست.
مثال:
import { useState } from "react";
function App() {
const [name, setName] = useState("");
return (
<>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<h2>{name}</h2>
</>
);
}
در این مثال:
- مقدار ورودی داخل State ذخیره میشود.
- با هر تایپ کاربر، مقدار State تغییر میکند.
- مقدار جدید بلافاصله روی صفحه نمایش داده میشود.
مدیریت چند فیلد فرم
فرض کنید فرم ثبتنام داریم.
const [form, setForm] = useState({
firstName: "",
lastName: "",
email: ""
});
تغییر مقدار:
onChange={(e) =>
setForm({
...form,
firstName: e.target.value
})
}
برای سایر فیلدها نیز به همین روش عمل میکنیم.
بروزرسانی پویا
روش حرفهایتر استفاده از نام فیلد است.
<input
name="email"
onChange={handleChange}
/>
تابع:
function handleChange(event){
setForm({
...form,
[event.target.name]: event.target.value
});
}
اکنون همین تابع برای تمام فیلدهای فرم قابل استفاده است.
قوانین Hookها
React برای Hookها قوانین مشخصی دارد.
1- فقط در بالاترین سطح کامپوننت
درست:
const [count, setCount] = useState(0);
اشتباه:
if(isLoggedIn){
const [count, setCount] = useState(0);
}
Hook نباید داخل:
- if
- else
- switch
- for
- while
- try/catch
قرار بگیرد.
2- فقط داخل Functional Component یا Hook سفارشی
درست:
function App(){
const [count,setCount]=useState(0);
}
اشتباه:
function calculate(){
const [count,setCount]=useState(0);
}
اشتباهات رایج
تغییر مستقیم State
اشتباه:
user.name="Ali";
استفاده از push
اشتباه:
users.push("Sara");
استفاده از splice
اشتباه:
users.splice(0,1);
تغییر مستقیم Object
اشتباه:
person.age=35;
فراموش کردن Spread Operator
اشتباه:
setUser({
name:"Ali"
});
صحیح:
setUser({
...user,
name:"Ali"
});
چه زمانی از چند State استفاده کنیم؟
اگر دادهها کاملاً مستقل هستند:
const [theme, setTheme] = useState("light");
const [language, setLanguage] = useState("fa");
const [count, setCount] = useState(0);
این روش خوانایی بیشتری دارد.
اما اگر دادهها متعلق به یک موجودیت هستند:
const [user, setUser] = useState({
name:"Ali",
email:"",
phone:""
});
استفاده از یک Object انتخاب مناسبتری است.
نکات مهم برای پروژههای واقعی
- از تغییر مستقیم State خودداری کنید.
- برای آرایهها و آبجکتها همیشه یک نمونه جدید ایجاد کنید.
- زمانی که مقدار جدید به مقدار قبلی وابسته است، از فرم تابعی
setState(prev => ...)استفاده کنید. - State را تا حد امکان کوچک و ساده نگه دارید.
- دادههای مشترک بین چند کامپوننت را در State محلی نگه ندارید؛ برای این کار از Context API یا کتابخانههایی مانند Redux و Zustand استفاده کنید.
پروژه عملی مدیریت State با useState
تا اینجا با نحوه کار useState آشنا شدیم. اکنون یک مثال عملی میسازیم تا ببینیم چگونه میتوان در یک پروژه واقعی از این Hook استفاده کرد.
فرض کنید میخواهیم یک فرم ساده ایجاد کنیم که اطلاعات کاربر را دریافت کرده و پس از ثبت، آن را نمایش دهد.
ایجاد State
ابتدا اطلاعات فرم را داخل یک Object نگهداری میکنیم.
import { useState } from "react";
function App() {
const [user, setUser] = useState({
firstName: "",
lastName: "",
email: ""
});
}
ساخت فرم
<form>
<input
name="firstName"
placeholder="First Name"
/>
<input
name="lastName"
placeholder="Last Name"
/>
<input
name="email"
placeholder="Email"
/>
</form>
مدیریت تغییرات فرم
برای اینکه برای هر Input یک تابع جداگانه ننویسیم، یک Handler عمومی ایجاد میکنیم.
function handleChange(event){
const { name, value } = event.target;
setUser({
...user,
[name]: value
});
}
اکنون تمام Inputها از همین تابع استفاده میکنند.
<input
name="firstName"
value={user.firstName}
onChange={handleChange}
/>
<input
name="lastName"
value={user.lastName}
onChange={handleChange}
/>
<input
name="email"
value={user.email}
onChange={handleChange}
/>
نمایش اطلاعات
اکنون اطلاعات را زیر فرم نمایش میدهیم.
<div>
<h3>{user.firstName}</h3>
<h3>{user.lastName}</h3>
<p>{user.email}</p>
</div>
با هر بار تایپ، اطلاعات بلافاصله روی صفحه بروزرسانی میشوند.
ریست کردن فرم
پس از ثبت اطلاعات معمولاً فرم باید پاک شود.
function resetForm(){
setUser({
firstName:"",
lastName:"",
email:""
});
}
دکمه:
<button onClick={resetForm}>
Reset
</button>
استفاده از چند useState
گاهی بهتر است هر داده State جداگانهای داشته باشد.
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
این روش باعث میشود تغییر هر State مستقل از بقیه باشد.
چه زمانی از useReducer استفاده کنیم؟
اگر تعداد Stateها زیاد شود یا بروزرسانی آنها پیچیده باشد، استفاده از useReducer انتخاب مناسبتری خواهد بود.
برای مثال:
- فرمهای بزرگ
- فروشگاه اینترنتی
- سیستم احراز هویت
- داشبوردهای مدیریتی
در این شرایط useReducer خوانایی و مدیریت کد را بهبود میدهد.
چه زمانی از Context API استفاده کنیم؟
اگر چندین کامپوننت به یک State مشترک نیاز داشته باشند، ارسال داده از طریق Props (Prop Drilling) بهمرور دشوار میشود.
در این حالت بهتر است از Context API استفاده کنید.
نمونههایی از دادههای مشترک:
- اطلاعات کاربر
- زبان سایت
- حالت Dark Mode
- تنظیمات برنامه
بهترین روشها (Best Practices)
هنگام استفاده از useState رعایت نکات زیر باعث میشود کد تمیزتر و قابل نگهداریتر باشد.
State را کوچک نگه دارید
بهجای ذخیره اطلاعات غیرضروری، فقط دادههایی را نگه دارید که واقعاً در رابط کاربری استفاده میشوند.
از تغییر مستقیم دادهها خودداری کنید
برای آرایهها و آبجکتها همیشه از روشهای Immutable مانند Spread Operator استفاده کنید.
Stateهای مرتبط را گروهبندی کنید
اگر چند مقدار به یک موجودیت تعلق دارند (مانند اطلاعات کاربر)، آنها را داخل یک Object قرار دهید.
از Callback در بروزرسانیهای وابسته استفاده کنید
بهجای:
setCount(count + 1);
از این روش استفاده کنید:
setCount(prev => prev + 1);
از نگهداری دادههای مشتقشده خودداری کنید
فرض کنید نام و نام خانوادگی را دارید.
نیازی نیست مقدار Full Name را داخل State ذخیره کنید.
بهتر است هنگام Render آن را محاسبه کنید.
const fullName = `${firstName} ${lastName}`;
از State بیش از حد استفاده نکنید
هر دادهای لازم نیست داخل State قرار بگیرد.
اگر مقداری در طول اجرای برنامه تغییر نمیکند، آن را بهصورت یک متغیر معمولی تعریف کنید.
جمعبندی
Hook useState یکی از مهمترین قابلیتهای React است که مدیریت State را در کامپوننتهای تابعی بسیار ساده کرده است. با استفاده از این Hook میتوانید انواع دادهها مانند اعداد، رشتهها، آرایهها و آبجکتها را مدیریت کنید و با هر تغییر، رابط کاربری را بهصورت خودکار بهروزرسانی کنید.
برای پروژههای کوچک و متوسط، useState معمولاً بهترین انتخاب است؛ اما با پیچیدهتر شدن منطق برنامه یا نیاز به اشتراکگذاری State بین چندین کامپوننت، بهتر است از ابزارهایی مانند Context API، useReducer یا کتابخانههای مدیریت State مانند Redux Toolkit یا Zustand استفاده کنید.
از همراهی شما با پارمین کلود سپاسگزاریم.
نظرات کاربران