در این مقاله قصد داریم تا state و ref در React را به طور کامل بررسی کرده و باهم مقایسه نماییم. همچنین بررسی می‌کنیم که با توجه به سناریوهای مختلف، استفاده از کدام یک می‌تواند یک انتخاب منطقی باشد.

هنگامی که در برنامه React خود با نیاز به ذخیره کردن داده‌ها مواجه می‌شویم، اولین سوالی که باید به آن پاسخ دهیم این است که آیا داده‌ها در طول دوره‌ای از چرخه عمر کامپوننت تغییر خواهند کرد؟ اگر تغییری اتفاق نیفتد، تعریف یک متغیر const معمولی مناسب است. اما اگر داده‌ها تغییر کنند، این نقطه همان جایی است که هوک‌های useState و useRef مطرح می‌شوند.

بررسی هوک‌های useState و useRef

هوک useState

هوک useState برای مدیریت state یک کامپوننت طراحی شده است که نشان دهنده داده‌هایی می‌باشد که می‌توانند در طول زمان تغییر کنند و برای رندر کامپوننت، داده‌های مهمی به حساب می‌آیند. می‌توانیم هوک useState را از React ایمپورت کرده و state را به کامپوننت خود اضافه نماییم.

import { useState } from 'react';

هوک useState معمولاً با یک value مقداردهی اولیه می‌شود و آرایه‌ای از متغیر state تعریف شده و تابع تنظیم کننده مربوط به آن را return می‌کند. به این صورت که:

import { useState } from "react";

function App() {
  const [count, setCount] = useState(0); //declared useState hook
  
  return (
    <>
      <h1>State example</h1>
      <div>
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
      </div>
    </>
  );
}
export default App;

در قطعه کد بالا:

  1. useState با مقدار ۰ مقداردهی اولیه می‌شود و یک متغیر count و تابع setCount را return می‌کند.
  2. متغیر count به صورت داینامیک توسط تابع setCount تنظیم می‌شود که count را ۱ واحد افزایش می‌دهد.
  3. هر بار که بر روی دکمه کلیک می‌کنیم، کامپوننت App دوباره رندر می‌شود و مقدار به‌روزرسانی شده را در متن دکمه به نمایش می‌گذارد.

درک درست مفهوم state ری‌اکت بسیار مهم است زیرا، یکی از پرکاربردترین مفاهیم می‌باشد.

هوک useRef

هوک useRef برای ایجاد ref در کامپوننت‌های React استفاده می‌شود. ref یک آبجکت با ویژگی current است که دارای یک value می‌باشد و اساسا،ً به یک المنت DOM یا نمونه‌ای از یک کامپوننت ارجاع می‌دهد. ما می‌توانیم با دسترسی به ویژگی current، value آن را بخوانیم و به‌روزرسانی کنیم.

const ref = useRef(initialValue)

ref.current = initialValue

در ادامه، یک قطعه کد کامل از ref در یک مثال عملی داریم:

import { useRef } from "react";

function App() {
  let ref = useRef(0); 
  
  function handleIncrease() {
    ref.current++;
    alert(`You have clicked it ${ref.current} times`);
  }
  return (
    <>
      <h1>Ref example</h1>
      <div>
        <button onClick={handleIncrease}>Click Me</button>
      </div>
    </>
  );
}

export default App;

در قطعه کد بالا:

  1. ابتدا userRef را از ری‌اکت import می‌کنیم.
  2. سپس در کامپوننت App، یک آبجکت ref با مقدار اولیه ۰ تعریف می‌کنیم.
  3. handleIncrease تابع کنترل کننده ما است که مقدار ref.current را ۱ واحد افزایش داده و سپس در قالب alert، مقدار فعلی را به کاربر نمایش می‌دهد.
  4. در JSX کامپوننت App، یک دکمه با prop onClick داریم و تابع handler handleIncrease را به آن پاس می‌دهیم.

اکنون که با نحوه کارکرد این دو هوک آشنا شدیم، در ادامه به مقایسه و بررسی اینکه چه زمانی برای استفاده مناسب هستند، خواهیم پرداخت.

مقایسه state و ref در React

Render Trigger

در ری‌اکت، stateها همیشه به دلیل مکانیزمی به نام reconciliation، که رابط کاربری را بر اساس تغییرات ایجاد شده در state یا props به‌روزرسانی می‌کند، باعث ایجاد یک رندر مجدد می‌شوند.

React در پس‌زمینه، state جدید را با state قبلی مقایسه می‌کند و حداقل تغییرات مورد نیاز برای به‌روزرسانی رابط کاربری که state جدید را منعکس می‌کند، محاسبه می‌نماید. این فرآیند، سازگاری با state یا propsهایی که تغییری در آن‌ها صورت گرفته است را تضمین می‌کند.

از طرف دیگر، هنگامی که تغییراتی در رابط کاربری ایجاد می‌شود refها باعث رندر مجدد نمی‌شوند. زیرا، آن‌ها مستقیماً به چرخه رندر کامپوننت مرتبط نمی‌باشند.

بنابراین، اگر یک رابط کاربری ثابت می‌خواهیم که به تغییرات داده‌ها واکنش نشان دهد، توصیه می‌شود از state استفاده کنیم. اما برای مدیریت مقادیر قابل تغییر بدون تأثیر بر روی رابط کاربری، بهتر است از ref استفاده نماییم.

تغییرپذیری

ما نمی‌توانیم state را مستقیما بعد از تنظیم تغییر دهیم زیرا، تابع setter است که state را به‌روزرسانی می‌کند. با استفاده از این رویکرد، React قابلیت پیش‌بینی و ثبات جریان داده را حفظ می‌کند. این موضوع همچنین باعث می‌شود تا فرآیند دیباگ کردن هم آسان‌تر شود.

برعکس، ref قابل تغییر است زیرا می‌توانیم مقدار ref current را خارج از فرآیند رندر، تغییر دهیم. برخلاف state، ما می‌توانیم مقادیر را در هر نقطه‌ای که می‌خواهیم تغییر دهیم، زیرا refها تابع به‌روزرسانی ندارند.

عملیات Read و Write

تابع setter هوک useState این امکان را به ما می‌دهد تا مقدار state را به‌روزرسانی کنیم. برای مثال:

const [state, setState] = useState(false)
function handleOpposite(){
  setState(!state)
 }

در مثال بالا:

  1. مقدار اولیه روی مقدار بولین false تنظیم شده است.
  2. تابع handleOpposite مقدار بولین state را نقض کرده و setState مقدار به‌روزرسانی شده true را در خود جای می‌دهد.

در این عملیات ساده،

  1. یک عملیات read ضمنی انجام شده است زیرا، قبل از نقیض کردن مقدار، باید به مقدار اولیه دسترسی داشته باشیم.
  2. زمانی که ! بر روی مقدار اولیه اعمال می‌شود، عملیات write رخ می‌دهد که مقدار موجود را به مقدار نقیض آن تغییر می‌دهد.

یک عملیات read صریح state زمانی اتفاق می‌افتد که ما به سادگی مستقیماً به متغیر state در JSX یک کامپوننت دسترسی داشته باشیم. برای مثال:

<button onClick={() => setCount((count) => count + 1)}>
  count is {count}
 </button>

{count} مقداری است که در حال حاضر به آن دسترسی داریم و بر این اساس در رابط کاربری نمایش داده می‌شود.

از طرف دیگر، دسترسی یا تغییر مقدار فعلی یک ref در طول rendering می‌تواند در فرآیند تطبیق React اختلال ایجاد کند و به طور بالقوه باعث ایجاد ناسازگاری بین DOM مجازی و DOM واقعی شود.

برای اطمینان از رفتار قابل پیش‌بینی و عملکرد بهینه در کامپوننت‌ها، بهتر است به دستورالعمل‌های React پایبند باشیم و از دسترسی یا تغییر refها در حین رندر خودداری کنیم.

تداوم در رندرها

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

ماندگاری برای حفظ یکپارچگی state برنامه بسیار مهم است و تضمین می‌کند که کامپوننت‌ها مطابق انتظار عمل می‌کنند.

به‌روزرسانی‌های Asynchronous

به‌روزرسانی‌ها در state به شکل asynchronous هستند. به این معنی که وقتی درخواستی برای به‌روزرسانی وجود دارد، این احتمال هست که به‌روزرسانی‌ها بلافاصله اجرا نشوند. React ممکن است اعمال برخی از تغییرات state را به زمان دیگری موکول کند، در حالی که کامپوننت‌های دیگر را یکباره به‌روزرسانی می‌نماید.

به‌روزرسانی‌های ref به صورت synchronous می‌باشد، جایی که تسک‌ها به صورت متوالی انجام می‌شوند. هر کار قبل از شروع منتظر می‌ماند تا کار قبلی تمام شود و اطمینان حاصل شود که آن‌ها به روش قابل پیش‌بینی و قطعی اجرا می‌شوند.

جمع‌بندی

در این مقاله سعی کردیم تا با مفهوم state و ref و همینطور هوک‌های useState و useRef، که داده‌های داینامیک را در برنامه‌های React مدیریت می‌کنند، بررسی کنیم. ما هر دو هوک را با هم مقایسه کردیم و با شباهت‌ها تفاوت‌های آن‌ها بیشتر آشنا شویم. همینطور به بررسی موقعیت‌های مناسب برای استفاده از هر کدام از آن‌ها پرداختیم.