هوک useActionState در React را برای مدیریت وضعیت سادهشده که توسط اکشنهای ناهمزمان فعال میشود، کاوش کنید. کارایی و تجربه کاربری اپلیکیشن خود را بهبود بخشید.
پیادهسازی useActionState در React: مدیریت وضعیت مبتنی بر اکشن
هوک useActionState در React، که در نسخههای اخیر معرفی شده، رویکردی پالایششده برای مدیریت بهروزرسانیهای وضعیت ناشی از اکشنهای ناهمزمان ارائه میدهد. این ابزار قدرتمند فرآیند مدیریت جهشها (mutations)، بهروزرسانی UI و مدیریت وضعیتهای خطا را ساده میکند، بهویژه هنگام کار با کامپوننتهای سرور ریاکت (RSC) و اکشنهای سرور. این راهنما به بررسی جزئیات useActionState میپردازد و مثالهای عملی و بهترین شیوهها برای پیادهسازی آن را ارائه میدهد.
درک نیاز به مدیریت وضعیت مبتنی بر اکشن
مدیریت وضعیت سنتی در React اغلب شامل مدیریت جداگانه وضعیتهای بارگذاری (loading) و خطا در کامپوننتها است. هنگامی که یک اکشن (مثلاً ارسال یک فرم، دریافت داده) باعث بهروزرسانی وضعیت میشود، توسعهدهندگان معمولاً این وضعیتها را با چندین فراخوانی useState و منطق شرطی بالقوه پیچیده مدیریت میکنند. useActionState یک راهحل تمیزتر و یکپارچهتر ارائه میدهد.
یک سناریوی ساده ارسال فرم را در نظر بگیرید. بدون useActionState، ممکن است داشته باشید:
- یک متغیر وضعیت برای دادههای فرم.
- یک متغیر وضعیت برای پیگیری اینکه آیا فرم در حال ارسال است (وضعیت بارگذاری).
- یک متغیر وضعیت برای نگهداری هرگونه پیام خطا.
این رویکرد میتواند منجر به کد طولانی و ناهماهنگیهای بالقوه شود. useActionState این نگرانیها را در یک هوک واحد ادغام میکند، منطق را ساده کرده و خوانایی کد را بهبود میبخشد.
معرفی useActionState
هوک useActionState دو آرگومان میپذیرد:
- یک تابع ناهمزمان (''اکشن'') که بهروزرسانی وضعیت را انجام میدهد. این میتواند یک اکشن سرور یا هر تابع ناهمزمان دیگری باشد.
- یک مقدار وضعیت اولیه.
این هوک یک آرایه حاوی دو عنصر را برمیگرداند:
- مقدار وضعیت فعلی.
- تابعی برای ارسال (dispatch) اکشن. این تابع به طور خودکار وضعیتهای بارگذاری و خطای مرتبط با اکشن را مدیریت میکند.
در اینجا یک مثال ساده آورده شده است:
import { useActionState } from 'react';
async function updateServer(prevState, formData) {
// شبیهسازی یک بهروزرسانی ناهمزمان سرور.
await new Promise(resolve => setTimeout(resolve, 1000));
const data = Object.fromEntries(formData);
if (data.name === "error") {
return 'Failed to update server.';
}
return `Updated name to: ${data.name}`;
}
function MyComponent() {
const [state, dispatch] = useActionState(updateServer, 'Initial State');
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
const result = await dispatch(formData);
console.log(result);
}
return (
);
}
در این مثال:
updateServerیک اکشن ناهمزمان است که بهروزرسانی سرور را شبیهسازی میکند. این تابع وضعیت قبلی و دادههای فرم را دریافت میکند.useActionStateوضعیت را با 'Initial State' مقداردهی اولیه کرده و وضعیت فعلی و تابعdispatchرا برمیگرداند.- تابع
handleSubmitتابعdispatchرا با دادههای فرم فراخوانی میکند.useActionStateبه طور خودکار وضعیتهای بارگذاری و خطا را در طول اجرای اکشن مدیریت میکند.
مدیریت وضعیتهای بارگذاری و خطا
یکی از مزایای کلیدی useActionState مدیریت داخلی وضعیتهای بارگذاری و خطا است. تابع dispatch یک promise را برمیگرداند که با نتیجه اکشن resolve میشود. اگر اکشن خطایی پرتاب کند، promise با آن خطا reject میشود. شما میتوانید از این برای بهروزرسانی UI استفاده کنید.
مثال قبلی را برای نمایش پیام بارگذاری و پیام خطا تغییر دهید:
import { useActionState } from 'react';
import { useState } from 'react';
async function updateServer(prevState, formData) {
// شبیهسازی یک بهروزرسانی ناهمزمان سرور.
await new Promise(resolve => setTimeout(resolve, 1000));
const data = Object.fromEntries(formData);
if (data.name === "error") {
throw new Error('Failed to update server.');
}
return `Updated name to: ${data.name}`;
}
function MyComponent() {
const [state, dispatch] = useActionState(updateServer, 'Initial State');
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
setIsSubmitting(true);
setErrorMessage(null);
try {
const result = await dispatch(formData);
console.log(result);
} catch (error) {
console.error("Error during submission:", error);
setErrorMessage(error.message);
} finally {
setIsSubmitting(false);
}
}
return (
);
}
تغییرات کلیدی:
- ما متغیرهای وضعیت
isSubmittingوerrorMessageرا برای پیگیری وضعیتهای بارگذاری و خطا اضافه کردیم. - در
handleSubmit، قبل از فراخوانیdispatch، مقدارisSubmittingرا بهtrueتنظیم میکنیم و هرگونه خطا را برای بهروزرسانیerrorMessageدریافت میکنیم. - دکمه ارسال را هنگام ارسال غیرفعال میکنیم و پیامهای بارگذاری و خطا را به صورت شرطی نمایش میدهیم.
useActionState با اکشنهای سرور در کامپوننتهای سرور ریاکت (RSC)
useActionState هنگام استفاده با کامپوننتهای سرور ریاکت (RSC) و اکشنهای سرور درخشش خود را نشان میدهد. اکشنهای سرور توابعی هستند که روی سرور اجرا میشوند و میتوانند مستقیماً منابع داده را تغییر دهند. آنها به شما اجازه میدهند عملیات سمت سرور را بدون نوشتن API endpoint انجام دهید.
توجه: این مثال نیازمند یک محیط ریاکت است که برای کامپوننتهای سرور و اکشنهای سرور پیکربندی شده باشد.
// app/actions.js (اکشن سرور)
'use server';
import { cookies } from 'next/headers'; //مثال، برای Next.js
export async function updateName(prevState, formData) {
const name = formData.get('name');
if (!name) {
return 'لطفاً یک نام وارد کنید.';
}
try {
// شبیهسازی بهروزرسانی پایگاه داده.
await new Promise(resolve => setTimeout(resolve, 1000));
cookies().set('userName', name);
return `نام به: ${name} بهروز شد`; //موفقیت!
} catch (error) {
console.error("Database update failed:", error);
return 'بهروزرسانی نام ناموفق بود.'; // مهم: یک پیام بازگردانید، نه یک خطا
}
}
// app/page.jsx (کامپوننت سرور ریاکت)
'use client';
import { useActionState } from 'react';
import { updateName } from './actions';
function MyComponent() {
const [state, dispatch] = useActionState(updateName, 'Initial State');
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
const result = await dispatch(formData);
console.log(result);
}
return (
);
}
export default MyComponent;
در این مثال:
updateNameیک اکشن سرور است که درapp/actions.jsتعریف شده است. این تابع وضعیت قبلی و دادههای فرم را دریافت میکند، پایگاه داده را بهروزرسانی میکند (شبیهسازی شده) و یک پیام موفقیت یا خطا برمیگرداند. نکته بسیار مهم این است که اکشن به جای پرتاب خطا، یک پیام برمیگرداند. اکشنهای سرور ترجیح میدهند پیامهای آموزنده بازگردانند.- کامپوننت به عنوان یک کامپوننت کلاینت (
'use client') علامتگذاری شده تا از هوکuseActionStateاستفاده کند. - تابع
handleSubmitتابعdispatchرا با دادههای فرم فراخوانی میکند.useActionStateبه طور خودکار بهروزرسانی وضعیت را بر اساس نتیجه اکشن سرور مدیریت میکند.
ملاحظات مهم برای اکشنهای سرور
- مدیریت خطا در اکشنهای سرور: به جای پرتاب خطا، یک پیام خطای معنادار از اکشن سرور خود بازگردانید.
useActionStateاین پیام را به عنوان وضعیت جدید در نظر میگیرد. این امر امکان مدیریت خطای روان در سمت کلاینت را فراهم میکند. - بهروزرسانیهای خوشبینانه (Optimistic Updates): اکشنهای سرور میتوانند با بهروزرسانیهای خوشبینانه برای بهبود عملکرد درکشده استفاده شوند. شما میتوانید UI را فوراً بهروزرسانی کنید و در صورت شکست اکشن، آن را به حالت قبل برگردانید.
- اعتبارسنجی مجدد (Revalidation): پس از یک جهش موفق، اعتبارسنجی مجدد دادههای کششده را در نظر بگیرید تا اطمینان حاصل شود که UI آخرین وضعیت را منعکس میکند.
تکنیکهای پیشرفته useActionState
۱. استفاده از Reducer برای بهروزرسانیهای وضعیت پیچیده
برای منطق وضعیت پیچیدهتر، میتوانید useActionState را با یک تابع reducer ترکیب کنید. این به شما امکان میدهد بهروزرسانیهای وضعیت را به روشی قابل پیشبینی و قابل نگهداری مدیریت کنید.
import { useActionState } from 'react';
import { useReducer } from 'react';
const initialState = {
count: 0,
message: 'Initial State',
};
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'SET_MESSAGE':
return { ...state, message: action.payload };
default:
return state;
}
}
async function updateState(state, action) {
// شبیهسازی عملیات ناهمزمان.
await new Promise(resolve => setTimeout(resolve, 500));
switch (action.type) {
case 'INCREMENT':
return reducer(state, action);
case 'DECREMENT':
return reducer(state, action);
case 'SET_MESSAGE':
return reducer(state, action);
default:
return state;
}
}
function MyComponent() {
const [state, dispatch] = useActionState(updateState, initialState);
return (
شمارش: {state.count}
پیام: {state.message}
);
}
۲. بهروزرسانیهای خوشبینانه با useActionState
بهروزرسانیهای خوشبینانه با بهروزرسانی فوری UI، گویی که اکشن موفقیتآمیز بوده، تجربه کاربری را بهبود میبخشند و در صورت شکست اکشن، بهروزرسانی را به حالت قبل برمیگردانند. این کار باعث میشود اپلیکیشن شما واکنشپذیرتر به نظر برسد.
import { useActionState } from 'react';
import { useState } from 'react';
async function updateServer(prevState, formData) {
// شبیهسازی یک بهروزرسانی ناهمزمان سرور.
await new Promise(resolve => setTimeout(resolve, 1000));
const data = Object.fromEntries(formData);
if (data.name === "error") {
throw new Error('Failed to update server.');
}
return `Updated name to: ${data.name}`;
}
function MyComponent() {
const [name, setName] = useState('Initial Name');
const [state, dispatch] = useActionState(async (prevName, newName) => {
try {
const result = await updateServer(prevName, {
name: newName,
});
return newName; // بهروزرسانی در صورت موفقیت
} catch (error) {
// بازگشت در صورت خطا
console.error("Update failed:", error);
setName(prevName);
return prevName;
}
}, name);
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
const newName = formData.get('name');
setName(newName); // بهروزرسانی خوشبینانه UI
await dispatch(newName);
}
return (
);
}
۳. Debouncing اکشنها
در برخی سناریوها، ممکن است بخواهید اکشنها را debounce کنید تا از ارسال بیش از حد مکرر آنها جلوگیری شود. این میتواند برای سناریوهایی مانند ورودیهای جستجو مفید باشد که فقط میخواهید پس از توقف تایپ کاربر برای یک دوره زمانی مشخص، یک اکشن را فعال کنید.
import { useActionState } from 'react';
import { useState, useEffect } from 'react';
async function searchItems(prevState, query) {
// شبیهسازی جستجوی ناهمزمان.
await new Promise(resolve => setTimeout(resolve, 500));
return `نتایج جستجو برای: ${query}`;
}
function MyComponent() {
const [query, setQuery] = useState('');
const [state, dispatch] = useActionState(searchItems, 'Initial State');
useEffect(() => {
const timeoutId = setTimeout(() => {
if (query) {
dispatch(query);
}
}, 300); // Debounce به مدت ۳۰۰ میلیثانیه
return () => clearTimeout(timeoutId);
}, [query, dispatch]);
return (
setQuery(e.target.value)}
/>
وضعیت: {state}
);
}
بهترین شیوهها برای useActionState
- اکشنها را خالص نگه دارید: اطمینان حاصل کنید که اکشنهای شما توابع خالص هستند (یا تا حد امکان به آن نزدیک هستند). آنها نباید عوارض جانبی به جز بهروزرسانی وضعیت داشته باشند.
- خطاها را به خوبی مدیریت کنید: همیشه خطاها را در اکشنهای خود مدیریت کنید و پیامهای خطای آموزنده به کاربر ارائه دهید. همانطور که در مورد اکشنهای سرور ذکر شد، بازگرداندن یک رشته پیام خطا از اکشن سرور را به جای پرتاب خطا ترجیح دهید.
- عملکرد را بهینه کنید: به پیامدهای عملکردی اکشنهای خود توجه داشته باشید، بهویژه هنگام کار با مجموعه دادههای بزرگ. استفاده از تکنیکهای memoization را برای جلوگیری از رندرهای غیرضروری در نظر بگیرید.
- دسترسیپذیری را در نظر بگیرید: اطمینان حاصل کنید که اپلیکیشن شما برای همه کاربران، از جمله افراد دارای معلولیت، قابل دسترس باقی میماند. ویژگیهای ARIA مناسب و ناوبری با صفحهکلید را فراهم کنید.
- تست کامل: تستهای واحد و تستهای یکپارچهسازی بنویسید تا اطمینان حاصل شود که اکشنها و بهروزرسانیهای وضعیت شما به درستی کار میکنند.
- بینالمللیسازی (i18n): برای اپلیکیشنهای جهانی، i18n را برای پشتیبانی از چندین زبان و فرهنگ پیادهسازی کنید.
- محلیسازی (l10n): اپلیکیشن خود را با ارائه محتوای محلی، فرمتهای تاریخ و نمادهای ارز برای مناطق خاص تنظیم کنید.
useActionState در مقابل سایر راهحلهای مدیریت وضعیت
در حالی که useActionState روشی مناسب برای مدیریت بهروزرسانیهای وضعیت مبتنی بر اکشن فراهم میکند، جایگزینی برای همه راهحلهای مدیریت وضعیت نیست. برای اپلیکیشنهای پیچیده با وضعیت سراسری که باید در چندین کامپوننت به اشتراک گذاشته شود، کتابخانههایی مانند Redux، Zustand یا Jotai ممکن است مناسبتر باشند.
چه زمانی از useActionState استفاده کنیم:
- بهروزرسانیهای وضعیت با پیچیدگی ساده تا متوسط.
- بهروزرسانیهای وضعیتی که به شدت با اکشنهای ناهمزمان گره خوردهاند.
- یکپارچهسازی با کامپوننتهای سرور ریاکت و اکشنهای سرور.
چه زمانی راهحلهای دیگر را در نظر بگیریم:
- مدیریت وضعیت سراسری پیچیده.
- وضعیتی که باید در تعداد زیادی از کامپوننتها به اشتراک گذاشته شود.
- ویژگیهای پیشرفته مانند دیباگینگ سفر در زمان (time-travel debugging) یا middleware.
نتیجهگیری
هوک useActionState در React روشی قدرتمند و زیبا برای مدیریت بهروزرسانیهای وضعیت ناشی از اکشنهای ناهمزمان ارائه میدهد. با ادغام وضعیتهای بارگذاری و خطا، کد را ساده کرده و خوانایی را بهبود میبخشد، بهویژه هنگام کار با کامپوننتهای سرور ریاکت و اکشنهای سرور. درک نقاط قوت و محدودیتهای آن به شما امکان میدهد رویکرد مدیریت وضعیت مناسبی را برای اپلیکیشن خود انتخاب کنید که منجر به کدی قابل نگهداریتر و کارآمدتر میشود.
با پیروی از بهترین شیوههای ذکر شده در این راهنما، میتوانید به طور موثر از useActionState برای بهبود تجربه کاربری و گردش کار توسعه اپلیکیشن خود استفاده کنید. به یاد داشته باشید که پیچیدگی اپلیکیشن خود را در نظر بگیرید و راهحل مدیریت وضعیتی را انتخاب کنید که به بهترین وجه با نیازهای شما مطابقت دارد. از ارسال فرمهای ساده گرفته تا جهشهای دادهای پیچیده، useActionState میتواند ابزاری ارزشمند در زرادخانه توسعه ریاکت شما باشد.