React.js In-depth Review
Functional Vs Class
Functional Components :
یک تابع JavaScript است که JSX برمیگرداند و مدیریت state، lifecycle و side effectها با Hooks انجام میشود.
Class Components :
کامپوننت کلاسی با class ساخته میشود و از this.state و متدهای lifecycle مثل componentDidMount استفاده میکند.
// Functional Component
function Welcome({ name }) {
return <h1>Hello {name}</h1>;
}
// Class Component
class WelcomeClass extends React.Component {
render() {
return <h1>Hello {this.props.name}</h1>;
}
}
اگر جایی کد کلاسی دیدی، بدون با یک پروژهی قدیمی طرفی
Props Vs State
- خب state و props تا حدودی شبیه به هم هستند زمانی که مقدارشون تغییر کنه کامپوننت re-render میشه
Props :
- دادههایی که از کامپوننت والد به فرزند ارسال میشوند.
- فقط خواندنی (immutable)
State :
- دادههای داخلی خود کامپوننت که میتوانند با setState تغییر میکند.
function Counter({ start }) {
// State = حافظه داخلی کامپوننت
const [count, setCount]
= React.useState(start);
return (
<div>
<p>مقدار: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
// Props = ورودی
<Counter start={5} />;
Hooks
useState
برای مدیریت state در کامپوننت تابعی استفاده میشود و میتوانند از هر نوعی باشند (string, number, array, object)
const [value, setValue]
= React.useState(0);
<button onClick={() => setValue(value + 1)}>+1</button>;
نکته : استیت های رو نمیشه مستقیما آپدیت کرد چون:
اولا که ری اکت یک قابلیتی داره به اسم Batching که در پشت صحنه چندین بروزرسانی استیت رو به صورت یکجا اعمال میکنه و یک بار فقط re-render رو انجام میده
مورد بعدی این هست که ساختار خود استیت تغییر ناپذیر یا immutable هست چون ما میخوایم به تاریخچه استیت ها دسترسی داشته باشیم
useEffect
برای انجام side effectها:
- فراخوانی API
- هندل event listener
- تغییر title صفحه
- برگشت استیت ها به حالت اولیه(cleanup)
useEffect(() => {
console.log("کامپوننت mount شد ");
return () => {
console.log("کامپوننت unmount شد ");
};
}, [stateName]
); // کامپوننت update شد
useRef
برای نگهداشتن reference به DOM یا یک مقدار پایدار که بین رندرها تغییر نکند.
کاربردهای رایج:
- دسترسی مستقیم به DOM
- نگهداشتن timer / interval
- هندل cache مقادیر
const inputRef = useRef(null);
<input ref={inputRef} />;
useContext
برای اشتراک state بین چند کامپوننت بدون نیاز به prop drilling.
کاربردهای رایج:
- Theme (dark / light)
- Language (i18n)
- Auth user
const ThemeContext = React.createContext("light");
function Button() {
const theme = useContext(ThemeContext);
return <button>{theme}</button>;
}
درصورت تغيير مقدار Context چه اتفاقى ميوفته؟
اگر ما یک مقدار رو توسط Context به کامیوننت هامون پاس داده باشيم و اين مقدار تغيير بكنه ، خود Context به تمام Consumer ها اطلاع ميده كه re-Render بشن !
مثلا فرض کنيد اطلاعات کاربر رو تو Context ذخيره كردیم و نام كاربر رو تو هدر سايت نمايش ميدیم. درصورتیکه این Context خالی بشه ( با Logout كردن كاربر ) كاميوننت هدر re-render ميشه تا اسم کاربر از هدر یاک بشه.
- استفادهی بیش از حد از Context = رندرهای ناخواسته
useMemo
برای memoize کردن مقدار و جلوگیری از محاسبات سنگین تکراری.
🔹 موارد استفاده:
- محاسبات سنگین و پرهزینه
- فیلتر کردن و مرتب کردن آرایههای بزرگ
- تبدیل دادههای پیچیده
const result = useMemo(() => heavyCalc(data), [data]
);
useCallback
برای memoize کردن تابع، مخصوصاً زمانی که به child component پاس داده میشود.
const handleClick = useCallback(() => setCount(c => c + 1), []
);
اغلب همراه با React.memo استفاده میشود.
useReducer
برای مدیریت state پیچیدهتر، شبیه Redux ولی داخل کامپوننت.
🔹 موارد استفاده:
- فرمهای پیچیده با فیلدهای زیاد
- مدیریت state های پیچیده
- مربوط به کشیدن و رها کردن = drag & drop
function reducer(state, action) {
switch (action.type) {
case "inc":
return { count: state.count + 1 };
default:
return state;
}
}
const [state, dispatch]
= useReducer(reducer, { count: 0 });
قوانین استفاده از Hooks
- فقط در بالای تابع استفاده شود.
- فقط در کامپوننت تابعی یا custom hook استفاده شوند.
- از useContext برای دسترسی به context
- از useReducer برای مدیریت state پیچیده
- از useCallback برای memoizing functions
- از useMemo برای memoizing values
- از useRef برای دسترسی مستقیم به DOM elements
- داخل if، loop یا function نباشند
key در React
- برای کمک به React در تشخیص کدام عناصر تغییر کردهاند (diffing).
- بدون react ، key مجبور میشود کل لیست را دوباره render کند.
- باید unique و stable باشد
{
items.map(item => <li key={item.id}>{item.name}</li>);
}
استفاده از index = دردسر در آینده
React Fragment?
کامپوننت هایی که در React ايجاد ميكنيم از اين ٢ حالت خارج نيستند :
- کامپوننت ما يک Element برميكرداند ( Return ميكند)
- کامیوننت ما دو یا چند Element برميكرداند (Return ميكند)
در ری اکت ، اگر قصد داشتہ باشیم ٢ یا چند Element برگردانیم ( Return کنیم ) باید تمامی آنها را داخل يک Element مثل div قرار دهيم . چون طبق قانون JSX، تمامى المان ها (Elements) بايد يك والد داشته باشند . پس بهتره از ویژگی Fragment استفاده کنیم که توسط خود react معرفی شده
<React.Fragment>
<FirstCompnents />
<SecondCompnents />
</React.Fragment>
// Or
<>
<FirstCompnents />
<SecondCompnents />
</>
مزیت استفاده از Fragment :
استفاده از React.Fragment باعث ميشود كد شما تميزتر و خواناتر باشد .
كاميوننت شما سريعتر Render ميشود و از حافظه كمترى استفاده میكند .
استفاده از div به دلیل اینکه ویژگی های بیشتری نسبت به Fragment دارد باعث سنگین شدن صفحه و بارگذاری دیرتر صفحه میشود ، پس زمانیکه از React.Fragment استفاده كنيد سرعت بارگذاری صفحه نیز سريعتر خواهد بود .
اگر از div استفاده کنید ، به دلیل تو در تو شدن Element ها در DOM ، دیباگ کردن کد سخت تر خواهد شد ، در مقابل استفاده از Fragment باعث ميشود Element كمترى در DOM قرار بگیرد و دیباگ كد راحت تر باشد .
Conditional Rendering
- یک مقهوم به شدت کاربردی که به ما کمک میکنه کامپوننت ها یا المان های خودمون رو به صورت شرطی رندر کنیم
{
isGoal ? <MadeGoal /> : <MissedGoal />;
}
Higher-Order-Component or HOC
خب HOC یکی از مهمترین ویژگی های کتابخانه ری اکت هستش که به ما اجازه میده از یک منطق (Logic) در چندین کامپوننت استفاده کنیم
یکی از مهمترین ویژگی های HOC در ریكت برای Reusable کردن یک تیكه كد کاربرد دارن.
و HOC در ری اكت کامپوننت هارو ویرایش نمېکنه، بلكه یک نسخه بروز شده از اون کامپوننت میسازه.
در نهایت Higher Order Component درريكت، pure function هست وهيچ side Effect نداره.
import React, { useState, useEffect } from 'react';
// HOC
function withSimpleLoading<P extends object>(WrappedComponent: React.ComponentType<P>) {
return function EnhancedComponent(props: P) {
const [isLoading, setIsLoading]
= useState<boolean>(true);
useEffect(() => {
const timer = setTimeout(() => setIsLoading(false), 1500);
return () => clearTimeout(timer);
}, []
);
return isLoading ? <div>Loading...</div> : <WrappedComponent {...props} />;
};
}
type SimpleDataFetcherProps = {
message: string;
};
// Component
function SimpleDataFetcher({ message }: SimpleDataFetcherProps) {
return <div>{message}</div>;
}
const EnhancedSimpleDataFetcher = withSimpleLoading(SimpleDataFetcher);
function App() {
return <EnhancedSimpleDataFetcher message="Data Loaded!" />;
}
export default App;
- خیلی وقتها در اپلیکیشنهات، موقع fetch کردن داده از API، باید یه چیزی رو به کاربر نشون بدی که داره لود میشه.
- با یک HOC مثل withSimpleLoading میتونی این منطق رو یک بار بنویسی و به هر کامپوننتی که نیاز به نمایش وضعیت بارگذاری داره، اضافه کنی. دیگه لازم نیست توی هر کامپوننت، useState و useEffect مربوط به loading رو تکرار کنی.
Virtual DOM
- بطور خلاصه و ساده Virtual DOM یک نسخه مجازی و کوچک شده از DOM اصلى وبسايت شماست كه React از اون برای اعمال سریع تغییرات روی سایت استفاده میکنه. انجام اينكار باعث ميشه كه فقط همان المانی که تغيیر داشته ، در اU بروزرسانی بشه . در React ، به ازای هر Element در DOM واقعی ، يک Element در دام مجازی وجود دارد . ( یعنى در ازاى هر div در DOM ، يك div با همان خصوصيات و ویزگی ها در دام مجازی وجود دارد)
Pure Component
- کامپوننت هایی که هیچ مقدار یا استیتی رو در خارج از کامپوننت خود تغییر نمیده و باعث re-render نمیشه
ReactDOM
اگه با ری اکت کار کرده باشید حتما به ReactDOM در ری اکت یا اسم react-dom در رى اكت برخوردید!
خب ReactDOM یكسری متد برای كار با DOM مرورگر در اختيار ما و رى اكت ميزاره تا بتونيم DOM رو باهاش مدیریت كنیم.
در حقیقت به کمک ReactDOM در ريكت میتونیم DOM اصلى رو دستكارى كنيم.
پکیج ReactDOM چندين متود وتابع در اختيار ما ميزاره مثل:
- render
- findDOMNode
- unmountComponentAtNode
- createPorta
React Memo
در حقيقت memo به ما اجازه ميده از re-Render اضافى كامپوننت تا زمانيكه Props اون تغييرى نكرده، جلوگیری کنیم.
تو تیکه کد زیر، ما كامپوننت Music رو memo کردیم. خروجی یک كامپوننت جديد هست به اسم : MemoizedMusic
export function Music({ title, description }) {
return (
<div>
<div>Music title: {title}</div>
<div>Desc: {description}</div>
</div>
);
}
export const MemoizedMusic = React.memo(Music);
در واقع خروجى كامپوننت MemoizedMusic با خود كامپوننت Music كاملا يكسانه! با اين تفاوت كه كامپوننت MemoizedMusic فقط يكبار render ميشه وتا زمانى كه props هاش تغيير نكنه ، re-Render نميشه !
استفاده درست از memo
جاهایی که فکر میکنید کامپوننت با Props هاى يكسان render ميشه !
یکی از بهترین جاهایی که میشه از react.memo استفاده كرد ، کامپوننت هایی هستن که اغلب با Props هاى يكسان render ميشن!
استفاده اشتباه از memo
درصورت استفاده از memo در جاى اشتباه ، Performance اپيكيشن شما كاهش پيدا خواهد كرد!
فرض کنید یک کامپوننتى رو به memo داديد كه هميشه Props هاى متفاوتى داره .
با انجام اینکار ، ری اکت هربار که Props هاى اين كاميوننت رو با نسخه كش شده بررسى ميكنه ،هميشه به false ميرسه ( يعنى Props ها متفاوت هستن!)
وخب اينجا يعنى رى اكت يک مرحله مقايسه اضافه انجام داده كه خودش باعث كاهش Performance اپيكيشن ميشه .
نه تنها هيچ مزيتى بدست نمياريد، كلى قضيه رو پيچيده تر هم ميكنيد!
React 18.2.0 +
useTransition
ببین useTransition به React میگوید:
این آپدیت state فوری نیست؛ میتوانی آن را با اولویت پایینتر انجام بده تا UI قفل نشود.
در React بعضی آپدیتها باید فوری باشند (مثلاً تایپ در input)، اما بعضیها میتوانند کمی بعد انجام شوند (مثلاً فیلتر کردن یک لیست بزرگ).
و اینجا useTransition کمک میکند React این دو نوع آپدیت را از هم اولویتبندی کند.
const [isPending, startTransition]
= useTransition();
startTransition
- تابعی که آپدیت های غیرضروری رو درونش قرار میدیم
isPending
- این بولین نشان میدهد آیا TRANSITION در حال انجام هست یا نه.
مشکل بدون useTransition
فرض کن یک input داری که یک لیست بزرگ را فیلتر میکند.
وقتی کاربر تایپ میکند:
- و state input تغییر میکند
- لیست بزرگ دوباره render میشود
- حالا UI ممکن است لگ بزند
مثال : فرض کن یک لیست بزرگ داریم و با تایپ کاربر فیلتر میکنیم.؟
import { useState, useTransition } from "react";
function SearchList({ items }) {
const [query, setQuery]
= useState("");
const [list, setList]
= useState(items);
const [isPending, startTransition]
= useTransition();
const handleChange = e => {
const value = e.target.value;
setQuery(value); // آپدیت فوری
startTransition(() => {
const filtered = items.filter(item => item.includes(value));
setList(filtered); // آپدیت کماولویت
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <p>Loading...</p>}
<ul>
{list.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
</div>
);
}
وقتی کاربر تایپ میکند:
1️⃣ خب setQuery سریع اجرا میشود → input بدون لگ تایپ میشود
2️⃣ فیلتر کردن لیست داخل startTransition انجام میشود
3️⃣ و React آن را با اولویت پایینتر انجام میدهد
4️⃣ اگر render طول بکشد isPending فعال میشود
چه زمانی از useTransition استفاده میشود؟
معمولاً برای کارهای سنگین UI مثل:
- فیلتر کردن لیستهای بزرگ
- رندر جدولهای بزرگ
- و search در داده زیاد
- و navigation در بعضی SPAها
- تغییر تبهایی که data زیاد دارند هدف اصلی:
جلوگیری از lag در UI
یک تشبیه
فرض کن React یک رستوران است:
- سفارش فوری → input typing 🍔
- سفارش سنگین → فیلتر ۱۰هزار آیتم 🍕
حالا useTransition میگه
اول برگر را بده دست مشتری، بعد برو سراغ پیتزا.
useDeferredValue
خب useDeferredValue یک مقدار رو “به تعویق میندازه” تا React مجبور نشه هر تغییر کوچیک رو فوراً رندر کنه.
یعنی چی؟
یعنی وقتی مقدار اصلی (مثل ورودی سرچ) سریع تغییر میکنه، React میگه:
«باشه… ولی من الان سرم شلوغه، یه نسخه کندتر و تأخیردار از این مقدار میسازم که کارهای سنگین رو باهاش انجام بدم. تو فقط تغییرات اصلی رو ثبت کن، من بعداً رندر میکنم!»
نتیجه:
پس UI سریع باقی میمونه، ولی کارهای سنگین عقبتر انجام میشن.
مثال واقعی (مثلاً سرچ لحظهای)
فرض کن یه input داری و هر بار تایپ میکنی یک لیست عظیم باید فیلتر بشه.
بدون useDeferredValue → هر تایپ = رندر مجدد سنگین = لگ و مکث
با useDeferredValue → تایپ سریع و روان، فیلتر شدن با تأخیر کوچیک
مثال کدی
const [value, setValue]
= useState("");
const deferredValue = useDeferredValue(value);
const filteredList = useMemo(() => {
return bigList.filter(item => item.includes(deferredValue));
}, [deferredValue]
);
- خب value = چیزی که کاربر تایپ میکنه (سریع تغییر میکنه)
- و deferredValue = نسخه “آرامتر” و به تأخیر افتاده
- فیلتر فقط روی deferredValue انجام میشه، نه ورودی لحظهبهلحظه
خلاصه
خب useDeferredValue کمک میکنه مقدارهایی که زیاد و سریع تغییر میکنن رو «به تأخیر بندازی» تا بخشهای سنگین UI بلافاصله رندر نشن. اینطوری ورودی کاربر سریع میمونه، ولی رندرهای سنگین پشت صحنه با تأخیر اجرا میشن.
Hook use()
در React 19 هوک use() برای خواندن Promise یا Context داخل کامپوننت استفاده میشه.
قبل از React19 اگر داده async داشتی باید:
useEffect
useState
loading state
میساختی.
اما use() اجازه میده Promise رو مستقیم داخل render بخونی و React خودش با Suspense مدیریت میکنه.
یعنی React صبر میکنه Promise resolve بشه.
// فرض کن API داریم:
async function fetchUser() {
const res = await fetch("/api/user");
return res.json();
}
// کامپوننت:
import { use } from "react";
function UserProfile({ userPromise }) {
const user = use(userPromise);
return <h1>{user.name}</h1>;
}
// استفاده:
<Suspense fallback={<p>Loading...</p>}>
<UserProfile userPromise={fetchUser()} />
</Suspense>;
مزیت use نسبت به useEffect چیه؟
- سادهتر شدن data fetching
- حذف state های loading
- هماهنگی با Suspense
- مناسب برای Server Components
React Forget (React Compiler)
خب React Forget یک کامپایلر هوشمند است که خودش memoization انجام میدهد.
قبلا باید دستی مینوشتیم:
- useMemo
- useCallback
- React.memo
ولی Forget خودش تشخیص میدهد چه زمانی کامپوننت باید re-render شود.
یعنی:
درواقع React خودش optimize میکند
// قبل
const handleClick = useCallback(() => {
console.log("clicked");
}, []
);
// با React Forget
function Button() {
const handleClick = () => {
console.log("clicked");
};
return <button onClick={handleClick}>Click</button>;
}
کامپایلر خودش اینو optimize میکنه.
خب پس React Forget چه مشکلی رو حل میکنه؟
- کاهش re-render غیر ضروری
- حذف نیاز به memoization دستی
- بهبود performance بدون پیچیده شدن کد
useFormStatus
این هوک از react-dom میاد.
برای فهمیدن وضعیت submit شدن فرم استفاده میشه.
یعنی بفهمیم:
- pending
- submitting
بدون state دستی.
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button disabled={pending}>{pending ? "Submitting..." : "Submit"}</button>
);
}
// استفاده
<form action={createUser}>
<input name="name" />
<SubmitButton />
</form>;
وقتی submit بشه باتن disable میشه.
- مدیریت آسان loading فرم
- بدون useState
- هماهنگ با Server Actions
useOptimistic
برای Optimistic UI استفاده میشه.
یعنی UI قبل از جواب سرور آپدیت میشه.
مثلا:
- لایک
- ارسال کامنت
- اضافه کردن آیتم
// فرض کن کامنت اضافه میکنیم.
import { useOptimistic } from "react";
function Comments({ comments }) {
const [optimisticComments, addOptimistic]
= useOptimistic(
comments,
(state, newComment) => [...state, newComment]
,
);
async function handleSubmit(formData) {
const text = formData.get("text");
addOptimistic({ text });
await sendComment(text);
}
return (
<>
{optimisticComments.map((c, i) => (
<p key={i}>{c.text}</p>
))}
<form action={handleSubmit}>
<input name="text" />
<button>Send</button>
</form>
</>
);
}
- تجربه کاربری سریعتر
- کاهش latency احساس شده
- مناسب برای تعاملات real-time
Actions (Server Actions)
در React 19 و Next.js میتوانیم توابع سرور را مستقیم از فرم صدا بزنیم. بدون API route.
"use server";
export async function createUser(formData) {
const name = formData.get("name");
await db.user.create({
data: { name },
});
}
// استفاده
<form action={createUser}>
<input name="name" />
<button>Create</button>
</form>;
- حذف API route
- امنیت بیشتر
- ساده شدن data mutation