ریاکت به صورت خودکار و هوشمند DOM (ساختار درختی صفحه وب) را بهروزرسانی میکند تا با خروجی رندر کامپوننتهای شما همخوانی داشته باشد. به همین دلیل، در شرایط عادی نیازی به دستکاری مستقیم DOM ندارید.
با این حال، گاهی اوقات مجبور میشوید به المانهای واقعی DOM که توسط ریآکت مدیریت میشوند دسترسی پیدا کنید؛ مثلاً برای فوکوس کردن روی یک اینپوت، اسکرول کردن صفحه به یک موقعیت خاص، یا محاسبه اندازه و موقعیت یک المان. از آنجا که ریآکت متد داخلی برای این کارها ندارد، شما به یک Ref متصل به آن المان نیاز خواهید داشت.
چیزهایی که در این درس یاد میگیرید:
-
چطور با استفاده از اتریبیوت
refبه یک گره DOM دسترسی پیدا کنیم؟ -
ارتباط بین ویژگی
refدر JSX و هوکuseRefچیست؟ -
چطور به المانهای DOM در یک کامپوننت فرزند دسترسی داشته باشیم؟
-
در چه شرایطی دستکاری مستقیم DOM توسط ما امن و بدون ریسک است؟
۱. دریافت Ref از یک المان (DOM Node)
برای دسترسی به المانی که توسط ریآکت مدیریت میشود، ابتدا هوک useRef را صدا بزنید تا یک رف با مقدار اولیه null ساخته شود:
const myRef = useRef(null);
سپس، این رف را به عنوان اتریبیوت ref به تگ JSX مورد نظر خود اختصاص دهید:
<div ref={myRef}>
در ابتدا مقدار myRef.current برابر با null است. اما به محض اینکه ریآکت المان واقعی را در مرورگر خلق کند، مرجع (Reference) آن المان را داخل myRef.current قرار میدهد. از این پس میتوانید در رویدادهای خود از تمام APIهای بومی مرورگر روی آن استفاده کنید:
// اسکرول نرم به سمت المان مورد نظر
myRef.current.scrollIntoView();
۲. مثالهای کاربردی و ملموس
الف) فوکوس چشمی روی فیلد متنی (Focus Input)
در این مثال، با کلیک روی دکمه، نشانگر موس مستقیماً وارد اینپوت میشود:
import { useRef } from 'react';
export default function SearchForm() {
const inputRef = useRef(null);
function handleFocus() {
// استفاده از متد نیتیو .focus() مرورگر
inputRef.current.focus();
}
return (
<div className="form-group">
<input ref={inputRef} placeholder="چیزی تایپ کنید..." />
<button onClick={handleFocus}>روی اینپوت فوکوس کن</button>
</div>
);
}
ب) مدیریت لیستی از رفها با "Ref Callback" (موقعیتهای پویا)
اگر تعداد المانها ثابت باشد، به راحتی چند رف مجزا میسازیم. اما اگر لیستی پویا از المانها داشته باشیم (مثلاً یک گالری تصاویر حاصل از map) چه؟ طبق قوانین ریآکت، شما نمیتوانید هوک useRef را داخل حلقهها یا متد .map() صدا بزنید.
راهحل اصولی، پاس دادن یک تابع به اتریبیوت ref است که به آن Ref Callback میگویند. این متد به شما اجازه میدهد المانها را در یک آرایه یا شیء Map ذخیره کنید:
import { useRef } from 'react';
export default function ImageGallery() {
// ذخیره ساختار Map در داخل رف برای نگهداری گرههای مختلف DOM
const elementsRef = useRef(new Map());
const items = [
{ id: 'img1', url: 'cat1.jpg' },
{ id: 'img2', url: 'cat2.jpg' }
];
function scrollToImage(id) {
const node = elementsRef.current.get(id);
node.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
return (
<>
<nav>
<button onClick={() => scrollToImage('img2')}>برو به تصویر دوم</button>
</nav>
<ul>
{items.map((item) => (
<li
key={item.id}
ref={(node) => {
if (node) {
elementsRef.current.set(item.id, node); // اضافه کردن به مپ
} else {
elementsRef.current.delete(item.id); // پاکسازی در صورت حذف المان
}
}}
>
<img src={item.url} alt="Gallery item" />
</li>
))}
</ul>
</>
);
}
۳. دسترسی به DOM کامپوننتهای فرزند
کامپوننتهای شخصی شما به صورت پیشفرض المانهای DOM خود را به بیرون ابراز نمیکنند. اگر یک رف را به کامپوننت سفارشی خود پاس دهید، ریآکت به صورت خودکار آن را به عنوان یک پروپ معمولی در نظر میگیرد. شما میتوانید این پروپ را در فرزند دریافت کرده و به المان نیتیو داخلی متصل کنید:
import { useRef } from 'react';
// کامپوننت فرزند رف را مانند پروپهای معمولی دریافت میکند
function MyInput({ ref, label }) {
return (
<label>
{label}
<input ref={ref} />
</label>
);
}
export default function MyForm() {
const customInputRef = useRef(null);
function handleClick() {
customInputRef.current.focus();
}
return (
<>
<MyInput ref={customInputRef} label="نام کاربری:" />
<button onClick={handleClick}>فوکوس</button>
</>
);
}
با این کار، customInputRef.current در کامپوننت پدر، دقیقاً به تگ <input> در اعماق کامپوننت فرزند اشاره خواهد کرد.
۴. چه زمانی ریآکت رفها را متصل میکند؟
در ریآکت، فرآیند بهروزرسانی کامپوننت شامل دو مرحله کلیدی است:
-
مرحله رندر (Render Phase): ریآکت کامپوننتها را صدا میزند تا بفهمد چه چیزی باید روی صفحه بیاید (در این مرحله هنوز DOM تغییر نکرده و رفها
nullیا دیتای قدیمی هستند). -
مرحله کامیت (Commit Phase): ریآکت تغییرات را روی DOM واقعی اعمال میکند.
ریآکت مقدار ref.current را دقیقاً در مرحله کامیت تنظیم میکند. درست قبل از اعمال تغییرات، مقدار رفها را null کرده و بلافاصله پس از آپدیت کامل درختِ مرورگر، گرههای جدید را به رفها متصل میکند. به همین دلیل است که همیشه باید درون رویدادها (Event Handlers) یا هوک useEffect به سراغ رفها بروید، نه در بدنه اصلی کامپوننت.
۵. اصول اخلاقی و مرزهای امن در دستکاری DOM
رفها یک «دریچه فرار» هستند. تا زمانی که کارهای شما غیرمخرب باشد (مانند فوکوس زدن، اسکرول کردن یا سنجش اندازه المان)، هیچ مشکلی پیش نخواهد آمد. اما اگر بخواهید المانهای تحت مدیریت ریآکت را به صورت دستی حذف کنید یا تغییر دهید، برنامه کرش خواهد کرد!
خطر بزرگ: تداخل با مدیریت ریآکت
به این نمونه خطرناک نگاه کنید:
// ❌ نمونه کد پرمخاطره و اشتباه
{showText && <p ref={textRef}>سلام دنیا</p>}
<button onClick={() => textRef.current.remove()}>
حذف مستقیم از DOM مرورگر
</button>
اگر کاربر با دستور بومی مرورگر (.remove()) المان پاراگراف را حذف کند، ریآکت روحش هم از این ماجرا باخبر نمیشود! حالا اگر استیت showText تغییر کند و ریآکت بخواهد دوباره آن المان را مدیریت یا حذف کند، چون گره را در دنیای واقعی پیدا نمیکند، برنامه با خطای شدید (Crash) متوقف میشود.
🟩 قاعده کلی: المانهایی را که ساختار یا حذف و اضافهشدنشان توسط استیتهای ریآکت مدیریت میشود، به صورت دستی دستکاری نکنید. اگر مایلید یک کار دستی انجام دهید (مثلاً تزریق المان با یک کتابخانه دیگر)، این کار را روی یک تگ
<div>کاملاً خالی انجام دهید که ریآکت هیچ دلیلی برای تغییر دادن فرزندان آن ندارد.
خلاصه نکات کلیدی (Recap)
-
با پاس دادن یک رف به ویژگی
<tag ref={myRef}>، ریآکت المان واقعی مرورگر را درmyRef.currentمیگذارد. -
کارهای غیرمخرب مانند جابجایی اسکرول، فوکوس و اندازهگیری المانها بهترین کاربردهای رفهای DOM هستند.
-
کامپوننتهای سفارشی به طور پیشفرض DOM خود را باز نمیکنند، مگراینکه رف را به عنوان پروپ به فرزند پاس داده و به تگهای بومی وصل کنید.
-
هرگز المانهایی را که ریآکت به صورت شرطی رندر میکند، مستقیماً حذف یا دگرگون نکنید تا جلوی کرشهای ناگهانی برنامه را بگیرید.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: منبع مستندات