logo
داکیومنت ریکت - قسمت نهم
ریکت

ریکت

29 شهریور 1402

سلام دوستان امیدوارم خوب باشید و همیشه درحال یادگیری🧑‍💻. توی قسمت نهم داکیومنت ریکت می‌خوایم ref رو بصورت کامل بررسی کنیم و همچنین تفاوت هاش رو با استیت ها بفهمیم.

اگه قسمت های قبلی رو از دست دادی میتونی از این لینک قسمت های قبلی رو هم ببینی.

Refs

زمانی که بخوایم یکسری اطلاعات رو داخل کامپوننت نگه داریم اما رندر مجدد اتفاق نیوفته از ref استفاده می‌کنیم. خوب از این هوک به اینصورت میتونیم داخل کامپوننت استفاده کنیم:

const ref = useRef(0);

و useRef یک آبجکت برمیگردونه که فیلد current مقدار رو به ما میده.

{ 
  current: 0 // The value you passed to useRef
}

یک نکته مهم، ref امکان mutable بودن رو داره، یعنی:

ref.current = ref.current + 1;

در واقع ref مثل یک جیب مخفی کامپوننت هامون میمونه که ریکت اون رو track نمی‌کنه (escape hatch).

ref ها مثل state میتونند به هر چی اشاره کنند- رشته، آبجکت و... اینو در نظر داشته باشید با تغییر مقدار ref کامپوننت ما رندر مجدد نمیشه در حالی که استیت ها باعث رندر مجدد کامپوننت می‌شوند.



مثال: building a stopwatch

فرض کنید یک stopwatch میخوایم درست کنیم. دو تا استیت تعریف می‌کنیم، یکی برای اینکه زمانی که تایمر ما استارت خورده رو ذخیره کنه و استیت بعدی برای اینکه زمان فعلی رو داخلش ذخیره کنیم، و استیت now هر 10ms آپدیت میشه و در نهایت زمان سپری شده رو بصورت زیر میتونیم بدست بیاریم:

secondsPassed = (now - startTime) / 1000;

این عملکرد دکمه start ما هست. برای دکمه stop باید ما clearInterval رو صدا بزنیم. لازم هست interval id ایی رو که قبلا اومدم setInterval رو باهاش فراخوانی کردیم، رو بهش بدیم. چون interval id رو باید یک جایی نگه داریم و برای رندر مجدد هم استفاده نمیشه در نتیجه در ref قرار میدیم.



برای اینکه بهتر متوجه این موضوع بشید، بیایید intervalRef.current رو داخل تابع start لاگ کنید. اینجا می‌بینید هر بار که ما تایمر رو استارت می‌کنیم این id تغییر میکنه و همین id رو زمانی که دکمه stop رو میزنیم به clearInterval پاس میدیم تا متوقف بشه.

تفاوت refs و state

refsstate
useRef(initialValue) returns { current: initialValue }useState(initialValue) returns the current value of a state variable and a state setter function ( [value, setValue])
Doesn’t trigger re-render when you change it.Triggers re-render when you change it.
Mutable—you can modify and update current’s value outside of the rendering process.“Immutable”—you must use the state setting function to modify state variables to queue a re-render.
You shouldn’t read (or write) the current value during rendering.You can read state at any time. However, each render has its own snapshot of state which does not change.

یک نکته مهم - فرض این دکمه رو دارید که مقدار کلیک انجام شده رو با ref قرار هست نشون بدیم:

<button onClick={handleClick}>
  You clicked {countRef.current} times
</button>

و تابع handleClick هم به این شکل هست:

  function handleClick() {
    // This doesn't re-render the component!
    countRef.current = countRef.current + 1;
  }

اینجا با هربار کلیک رو دکمه مقدار ref آپدیت میشه اما توی UI تفاوت ایی ایجاد نمیشه یا بهتره بگیم رندر مجدد انجام نمیشه و این جمله "You clicked 0 times" روی دکمه نشون داده میشه.

برای حل این مشکل همینطور که احتمالا حدس زدین از state ها باید استفاده کنیم.

چه زمانی از ref استفاده می‌کنیم:‌

  1. ذخیره timeout id
  2. ذخیره و دستکاری عناصر dom ( که در قسمت بعدی بهش اشاره می‌کنیم)
  3. اگر کامپوننت ما لازم بود مقادیر را ذخیره کند اما توی رندر کردن logic تاثیر گذار نیست از ref استفاده کنید.

نکته: در زمان رندر شدن کامپوننت ref.current رو نه بخونید و نه مقدار دهی کنید.


دستکاری DOM با Ref

ریکت بصورت اتوماتیک DOM رو مطابق با اون چیزی که انتظار داریم آپدیت می‌کنه و اغلب نیاز به دست بردن به DOM نیست. اما ممکنه نیاز به دستکاری DOM ایی که توسط ریکت مدیریت میشه، داشته باشیم - برای مثال focus a node یا اسکرول کردن تا محل node. پس برای این کار به ref نیاز داریم.

خوب اول میاییم Ref رو داخل کامپوننت مون تعریف می‌کنیم:

const myRef = useRef(null);

در تگ هامون ما از ویژگی (attribute) ref استفاده می‌کنیم و ref ایی که تعریف کردیم رو پاس میدیم.

با اینکار ما از طریق myRef.current میتونیم به این المنت داخل DOM دسترسی داشته باشیم و بر فرض داخل event handler ها ازش استفاده کنیم و همچنین به API های مرورگر دسترسی داشته باشیم بطور مثال:

// You can use any browser APIs, for example:
myRef.current.scrollIntoView();

مثال: Focusing a text input

بطور مثال ما یک ref به نام inputRef تعریف می‌کنیم و این رو به input پاس میدیم. حالا داخل فانکشن handleClick اومدیم گفتیم هر موقع روی این دکمه کلیک شد روی input فوکوس انجام بده.



حالا اگه ما inputRef.current رو بیایم چاپ کنیم میاد اون تگ ایی که ref رو بهش پاس دادیم رو برامون چاپ میکنه. در واقع inputRef.current داره به این تگ اشاره میکنه.

خوب حالا فرض کنید توی همین مثال بالا ما input رو به کامپوننت جدا ببریم حالا باید چطوری بهش دسترسی داشته باشیم با ref ؟



در حالتی که الان استفاده شده داخل کد ما ارور داریم. علتش این هست که در حالت پیشفرض ریکت اجازه نمیده از داخل یک کامپوننت بیاییم به DOM کامپوننت دیگه دسترسی داشته باشیم حتی اگه فرزندش باشه!

برای اینکه یک کامپوننت، DOM اش رو بیاد در معرض (expose) قرار بده باید از forwardRef استفاده کنیم.کامپوننت مون رو با forwardRef میاییم تعریف می‌کنیم:

const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />;
});

در آرگومان دوم کامپوننت بعد از props میتونیم ref رو داشته باشیم و به input بیاییم پاس بدیم.



داخل design systems، این متداول هست که low-level component ها مثل buttons, inputs و... بیان و DOM node های خودشون رو در معرض (expose) قرار بدند تا در صورت نیاز بتونیم بصورت مستقیم با DOM تعامل داشته باشم. اما high-level component ها مثل forms, lists و... اینکار رو نمی‌کنند برای اینکه اطمینان داشته باشیم اتفاقات تصادفی روی این کامپوننت ها اثر مخربی نذارند.

یک بحث مهم داخل آشکار کردن DOM هست. ما زمانی که میاییم کل DOM رو آشکار می‌کنیم به والد اجازه این رو هم میدیم که علاوه بر استفاده کردن از ()focus بیاد به قسمت های دیگه هم دسترسی داشته باشه مثلا یک استایل رو تغییر بده.

برای اینکه این آشکار شدن رو محدود کنیم میتونیم از useImperativeHandle استفاده کنیم و داخل اون بگیم چه مواردی رو فقط آشکار کنه.

const MyInput = forwardRef((props, ref) => {
  const realInputRef = useRef(null);
  useImperativeHandle(ref, () => ({
    // Only expose focus and nothing else
    focus() {
      realInputRef.current.focus();
    },
  }));
  return <input {...props} ref={realInputRef} />;
});

بحث دیگه این هست که با استفاده از ref المان های DOM رو که توسط react داره مدیریت میشه رو تغییر ندیم. بطور مثال اضافه کردن فرزند یا حذف اون که توسط خود رو ریکت انجام میشه رو با ref دستکاری نکنیم چون باعث ارور میشه.

به مثال پایین دقت کنید، زمانی که دکمه Toggle with setState رو کلیک می‌کنیم پیام نمایش و ناپدید میشه. حالا اگر دکمه Remove from the DOM رو بزنیم میاد المان رو از DOM حذف می‌کنه. در نهایت اگر دوباره دکمه Toggle with setState رو بزنیم، ریکت سعی می‌کنه که المان رو نشون بده ولی همچنین المان ایی داخل DOM نیست و با خطا رو به رو میشیم. در واقع ما اومدیم DOM رو تغییر دادیم و حالا ریکت نمیدونه چطوری مدیریتش کنه.



پس بهتر هست کار های غیر مخرب مثل focus و scrolling رو با ref انجام بدیم.


خوب بچه ها امیدوارم این قسمت براتون مفید بوده باشه. خوشحال میشم اگه انتقادی یا پیشنهادی دارید برام بنویسید تا محتوای بهتری تولید بشه ❤️

منابع:

https://react.dev/learn/referencing-values-with-refs

https://react.dev/learn/manipulating-the-dom-with-refs

این پست برات مفید بود؟

0

heart

0

like

0

happy

0

sad