Refs के ज़रिए वैल्यू का रेफरन्स लेना

जब आप किसी कौम्पोनॅन्ट को कुछ जानकारी “याद” रखवाना चाहते हों, लेकिन आप नहीं चाहते कि वह जानकारी नए रेंडर को ट्रिगर करे, तब आप एक ref का इस्तेमाल कर सकते हैं।

You will learn

  • अपने कौम्पोनॅन्ट में ref कैसे ऐड करें
  • ref के वैल्यू को कैसे अपडेट करें
  • ref state से कैसे अलग होते हैं
  • ref को सुरक्षित तरीके से कैसे इस्तेमाल करें

अपने कौम्पोनॅन्ट में ref ऐड करना

आप React से useRef हुक इम्पोर्ट करके अपने कौम्पोनॅन्ट में एक ref ऐड कर सकते हैं:

import { useRef } from 'react';

अपने कौम्पोनॅन्ट के अंदर, useRef हुक को कॉल करें और इनिशियल वैल्यू पास करें जिसे आप केवल आर्गुमेंट के रूप में रेफरन्स करना चाहते हैं। उदाहरण के लिए, यहां वैल्यू 0 के लिए एक ref है:

const ref = useRef(0);

useRef एक इस तरह के ऑब्जेक्ट को रिटर्न करता है:

{
current: 0 // The value you passed to useRef
}
An arrow with 'current' written on it stuffed into a pocket with 'ref' written on it.

Illustrated by Rachel Lee Nabors

आप उस ref के करंट वैल्यू को ref.current प्रॉपर्टी से एक्सेस कर सकते हैं। यह वैल्यू म्यूटेबल है, मतलब आप इसे रीड और राइट दोनों कर सकते हैं। यह आपके कौम्पोनॅन्ट का एक सीक्रेट पॉकेट की तरह है जिसे React ट्रैक नहीं करता। (यही वजह है जो इसे React में एकतरफा डेटा फ्लो से इसे एक “एस्केप हैच” बनाता है - इसके बारे में नीचे और अधिक जानकारी है!)

यहाँ, बटन पर हर क्लिक से ref.current इन्क्रीमेंट होगा:

import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert('You clicked ' + ref.current + ' times!');
  }

  return (
    <button onClick={handleClick}>
      Click me!
    </button>
  );
}

यह ref एक नंबर को पॉइंट कर रहा है, लेकिन state की तरह, आप कुछ भी पॉइंट कर सकते हैं: एक स्ट्रिंग, एक ऑब्जेक्ट, या एक फंक्शन तक। state की तुलना में, ref एक प्लैन जावास्क्रिप्ट ऑब्जेक्ट है जिसमें आप current प्रॉपर्टी को रीड कर सकते हैं और उसे मॉडिफाय भी कर सकते हैं।

ध्यान दें यहाँ हर इन्क्रीमेंट के बाद भी कौम्पोनॅन्ट फिर से रेंडर नहीं होता है। जैसा कि state के साथ होता है, React ref को फिर से रेंडर करने से रोक कर रखता है। हालांकि, state सेट करने से कौम्पोनॅन्ट फिर से रेंडर हो जाता है। ref को बदलने से कौम्पोनॅन्ट फिर से रेंडर नहीं होता है!

उदाहरण: एक स्टॉपवॉच बनाए

आप एक कौम्पोनॅन्ट में ref और state को ऐड कर सकते हैं। उदाहरण के लिए, आइए एक स्टॉपवॉच बनाते हैं जिसे यूज़र एक बटन दबाकर शुरू या बंद कर सकता है। यह डिस्प्ले करने के लिए कि यूज़र द्वारा “Start” बटन दबाए जाने के बाद से कितना समय बीत चुका है, आपको इस बात का ध्यान रखना होगा कि स्टार्ट बटन कब दबाया गया था और करंट समय क्या है इस जानकारी का इस्तेमाल रेंडरिंग के लिए किया जाता है, इसीलिए आप इसे state में रखेंगे:

const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);

जब यूज़र “Start” दबाता है, तब आप समय अपडेट करने के लिए हर 100 मिलीसेकंड के बाद setInterval का इस्तेमाल करेंगे:

import { useState } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);

  function handleStart() {
    // Start counting.
    setStartTime(Date.now());
    setNow(Date.now());

    setInterval(() => {
      // Update the current time every 10ms.
      setNow(Date.now());
    }, 10);
  }

  let secondsPassed = 0;
  if (startTime != null && now != null) {
    secondsPassed = (now - startTime) / 1000;
  }

  return (
    <>
      <h1>Time passed: {secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>
        Start
      </button>
    </>
  );
}

जब “Stop” बटन दबाया जाता है, आपको मौजूदा इंटरवल को रद्द करने की जरूरत है ताकि यह now state वेरिएबल को अपडेट न करें। आप clearInterval को कॉल करके ऐसा कर सकते हैं, लेकिन आपको इसे इंटरवल आईडी देना होगा जिसे पहले setInterval कॉल द्वारा लौटाया गया था जब यूज़र ने Start दबाया था। आपको कहीं इंटरवल आईडी रखने की जरूरत है। चूंकि इंटरवल आईडी का इस्तेमाल रेंडर के लिए नहीं किया जाता है, आप इसे ref में रख सकते हैं :

import { useState, useRef } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);
  const intervalRef = useRef(null);

  function handleStart() {
    setStartTime(Date.now());
    setNow(Date.now());

    clearInterval(intervalRef.current);
    intervalRef.current = setInterval(() => {
      setNow(Date.now());
    }, 10);
  }

  function handleStop() {
    clearInterval(intervalRef.current);
  }

  let secondsPassed = 0;
  if (startTime != null && now != null) {
    secondsPassed = (now - startTime) / 1000;
  }

  return (
    <>
      <h1>Time passed: {secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>
        Start
      </button>
      <button onClick={handleStop}>
        Stop
      </button>
    </>
  );
}

जब कुछ इनफॉर्मेशन रेंडरिंग के लिए यूज होती है, तब उसे state में रखें। और जब कुछ इनफ़ॉर्मेशन सिर्फ़ event-handler को चाहिए या उसे बदलने से री-रेंडर करने की ज़रूरत नहीं होती है, तब ref का इस्तेमाल करना ज़्यादा अच्छा रहेगा!

ref और state के बीच अंतर

शायद आप सोच रहे हैं कि state की तुलना में ref कम “स्ट्रिक्ट” लगता हैं—उदाहरण के लिए, आप इन्हें म्यूटेट कर सकते हैं इससे आपको हमेशा स्टेट सेटिंग फंक्शन का इस्तेमाल नहीं करना होगा। लेकिन ज्यादातर मामलों में, आप state का इस्तेमाल करना चाहेंगे। Ref एक “एस्केप हैच” हैं जिसकी आपको अक्सर ज़रूरत नहीं होगी। यहां बताया गया है कि state और ref की तुलना कैसे की जाती है:

refsstate
useRef(initialValue) { current: initialValue } को रिटर्न करता है।useState(initialValue) करंट state वेरिएबल की वैल्यू और state सेट करने वाले फंक्शन को रिटर्न करता है ([value, setValue])।
जब आप इसे बदलते हैं तो यह दोबारा रेंडर नहीं होता है।जब आप इसे बदलते हैं तो यह दोबारा रेंडर होता है।
“म्यूटेबल”—आप रेंडरिंग प्रोसेस के बाहर current वेरिएबल की वैल्यू को मॉडिफाई और अपडेट कर सकते हैं।“इमम्यूटेबल”—क्यू को दोबारा रेंडर कराने के लिए, आपको state सेटिंग फंक्शन से state वेरिएबल्स को मॉडिफाई करना पड़ता है।
रेंडरिंग के दौरान current वैल्यू को रीड (या राइट) नहीं करना चाहिए।आप किसी भी समय state को रीड कर सकते हैं। हालांकि, हर रेंडर के पास अपनी state का स्नैपशॉट होता है जो बदलता नहीं है।

यहाँ एक काउंटर बटन है जो state के साथ इम्पलीमेंट किया गया है:

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      You clicked {count} times
    </button>
  );
}

यहाँ count का वैल्यू बताया गया है, इसलिए उसके लिए state वैल्यू का इस्तेमाल करना सही है। जब काउंटर की वैल्यू setCount() से सेट की जाती है, React कॉम्पोनेंट को दोबारा रेंडर करता है और स्क्रीन नए काउंट को दिखाता है।

अगर आप इसे ref के साथ इम्पलीमेंट करने की कोशिश करेंगे, तो React कॉम्पोनेंट को दोबारा रेंडर नहीं करेगा, इसलिए आप कभी भी काउंट में बदलाव नहीं देख पाएंगे! देखें कि इस बटन पर क्लिक करने से उसका टेक्स्ट अपडेट नहीं होता है:

import { useRef } from 'react';

export default function Counter() {
  let countRef = useRef(0);

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

  return (
    <button onClick={handleClick}>
      You clicked {countRef.current} times
    </button>
  );
}

इसीलिए रेंडर के दौरान ref.current को रीड करने से कोड अनरीलाऐबल होजाता है। अगर आपको उसकी जरूरत है, तो ref के बजाय state का इस्तेमाल करें।

Deep Dive

अंदर से useRef कैसे काम करता है??

हालाँकि React दोनों useState और useRef उपलब्ध करता है, लेकिन प्रिंसिपल से useRef useState के ऊपर लागू किया जा सकता है। आप यह कल्पना कर सकते हैं कि React के अंदर, useRef इस तरह लागू होता है:

// Inside of React
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}

पहले रेंडर के दौरान, useRef { current: initialValue } रिटर्न करता है। यह ऑब्जेक्ट React द्वारा स्टोर किया जाता है, ताकि अगले रेंडर के दौरान वही ऑब्जेक्ट रिटर्न किया जा सके। इस उदाहरण में state सेटर इस्तेमाल नहीं होता है। यह अनावश्यक है क्योंकि useRef हमेशा एक ही ऑब्जेक्ट रिटर्न करता है!

React में useRef का एक built-in वर्शन उपलब्ध होता है क्योंकि इसका इस्तेमाल वास्तविकता में बहुत आम है। लेकिन आप इसे एक साधारण state वेरिएबल की तरह भी समझ सकते हैं जिसमें सेटर नहीं होता है। यदि आप ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग से फेमिलिअर हैं तो ref आपको इंस्टेंस फ़ील्ड की याद दिला सकता हैं—लेकिन इसमें this.something की बजाय somethingRef.current लिखा जाता है।

ref का इस्तेमाल कब करें

आम तौर पर, आप एक ref का इस्तेमाल तभी करेंगे जब आपके कौम्पोनॅन्ट को React से “बाहर निकल के” बाहरी APIs के साथ संवाद करने की जरूरत होगी-अक्सर एक ब्राउज़र API जो कम्पोनेंट की परफ़ॉर्मेंस को इम्पैक्ट नहीं करता। यह कुछ ऐसी रेयर परिस्थितियां हैं:

  • Timeout IDs को स्टोर करना।
  • DOM elements को स्टोर करना और मैनिपुलेट करना।, जिसे हम अगले पेज पर कवर करेंगे।
  • JSX को कैलकुलेट करने के लिए आवश्यक न होने वाले अन्य ऑब्जेक्ट्स को स्टोर करना।

यदि आपके कौम्पोनॅन्ट को कुछ वैल्यू स्टोर करने की जरुरत है, लेकिन यह रेंडरिंग लॉजिक पर असर नहीं डालता है, तो ref का इस्तेमाल करें।

ref के लिए बेस्ट प्रैक्टिस

इन प्रिंसिपल का पालन करने से आपके कॉम्पोनेन्ट ज्यादा प्रेडिक्टेबल हो जाएँगे:

  • ref को एक एस्केप हैच के रूप में इस्तेमाल करें। जब आप एक्सटर्नल सिस्टम या ब्राउज़र APIs के साथ काम करते हैं तब ref बहुत काम आता हैं। यदि आपके एप्लिकेशन लॉजिक और डेटा फ्लो ref पर बहुत निर्भर करते हैं, तो आपको अपने एप्रोच को बदलने की ज़रूरत है।

  • रेंडरिंग के दौरान ref.current को न रीड करें और न ही राइट करें। अगर कुछ जानकारी की रेंडरिंग के दौरान ज़रूरत पड़ती है तो, state का इस्तेमाल करें। क्योंकि React को पता नहीं होता कब ref.current बदलता है, यहाँ तक कि रेंडरिंग के दौरान इसे रीड करने से आपके कौम्पोनॅन्ट के बिहेवियर को प्रेडिक्ट करना मुश्किल हो जाता है। (इसका एक ही एक्सेप्शन है जो आप if (!ref.current) ref.current = new Thing() ऐसे कोड का इस्तेमाल करके पहले रेंडर के दौरान रेफरेंस को सेट कर सकते हैं।)

React state की सीमाएँ ref के लिए लागू नहीं होती हैं। उदाहरण के लिए, state हर रेंडर के लिए एक स्नैपशॉट की तरह काम करता है और सिंक्रोनोसली से अपडेट नहीं होता है। लेकिन जब आप ref के करंट वैल्यू को म्यूटेट करते हैं, तो वाह तुरंत बदल जाता है।

ref.current = 5;
console.log(ref.current); // 5

यह इसलिए होता है क्योंकि ref खुद एक साधारण जावास्क्रिप्ट ऑब्जेक्ट है और इसलिए यह उसके जैसे काम करता है।

जब आप ref के साथ काम करते हैं तो म्यूटेशन से बचने की चिंता करने की ज़रुरत नहीं है। जब तक आप म्युटेट कर रहे ऑब्जेक्ट को रेंडरिंग के लिए नहीं इस्तेमाल कर रहे हैं, React को कोई फर्क नहीं पड़ता कि आप ref या उसकी कंटेंट्स के साथ क्या कर रहे हैं।

ref और DOM

आप ref को किसी भी वैल्यू पर पॉइंट कर सकते हैं। हालांकि, एक ref का सबसे आम काम एक DOM एलिमेंट तक पहुंचने का होता है। उदाहरण के लिए, अगर आप किसी input को प्रोग्रामेटिकली focus करना चाहते है। जब आप JSX में ref एट्रिब्यूट में एक ref को पास करते हैं, जैसे <div ref={myRef}>, तो React उस संबंधित DOM एलिमेंट को myRef.current में रखता हैं। एलिमेंट के DOM से रिमूव होने के बाद React myRef.current को null सेट कर देता है। आप इसके बारे में अधिक जानकारी DOM को Refs के साथ मैनिपुलेट करना में पढ़ सकते हैं।

Recap

  • Ref रेंडर करने के लिए इस्तेमाल नहीं होने वाले वैल्यूज को पकड़ने के लिए एक एस्केप हैच हैं। आपको इनकी अक्सर जरूरत नहीं होगी।
  • Ref एक सादा जावास्क्रिप्ट ऑब्जेक्ट होता है जिसमें एक ही प्रॉपर्टी होती है जो current नाम से होती है और जिसे आप पढ़ सकते हैं या सेट कर सकते हैं।
  • आप useRef हुक को कॉल करके React से एक Ref मांग सकते हैं।
  • state की तरह, ref आपको कौम्पोनॅन्ट के री-रेंडर के बीच जानकारी रखने की अनुमति देते हैं।
  • state के विपरीत, Ref के current वैल्यू को सेट करने से फिर से रेंडर ट्रिगर नहीं होता हैं।
  • रेंडरिंग के दौरान ref.current को न रीड करें और न ही राइट करें। यह आपके कौम्पोनॅन्ट को प्रेडिक्ट करना मुश्किल कर देता है।

Challenge 1 of 4:
ब्रोकन चैट इनपुट को ठीक करें

एक मैसेज टाइप करें और “Send” पर क्लिक करें। आपको “Sent!” अलर्ट दिखाई देने से पहले तीन सेकंड कि देरी नोटिस होगी। इस देरी के दौरान, आप “Undo” बटन देख सकते हैं। उस पर क्लिक करें। यह “Undo” बटन “Sent!” मैसेज को रोकने के लिए होता है। यह handleSend के दौरान सेव की गई timeout ID के लिए clearTimeout को कॉल करता है। हालांकि, “Undo” पर क्लिक करने के बाद भी, “Sent!” मैसेज दिखाई दे रहा है। इसका कारण खोजें और उसे ठीक करें।

import { useState } from 'react';

export default function Chat() {
  const [text, setText] = useState('');
  const [isSending, setIsSending] = useState(false);
  let timeoutID = null;

  function handleSend() {
    setIsSending(true);
    timeoutID = setTimeout(() => {
      alert('Sent!');
      setIsSending(false);
    }, 3000);
  }

  function handleUndo() {
    setIsSending(false);
    clearTimeout(timeoutID);
  }

  return (
    <>
      <input
        disabled={isSending}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button
        disabled={isSending}
        onClick={handleSend}>
        {isSending ? 'Sending...' : 'Send'}
      </button>
      {isSending &&
        <button onClick={handleUndo}>
          Undo
        </button>
      }
    </>
  );
}