तुम्ही कधी असा टूलटिप (tooltip) बनवला आहे का जो वरच्या-डाव्या कोपऱ्यातून त्याच्या योग्य जागी झटकन सरकतो? किंवा एखादे मॉडेल (modal) जे योग्य आकारात येण्यापूर्वी चुकीच्या आकारात चमकते? तो क्षणभराचा दोष म्हणजे 'लेआउट फ्लिकर' (layout flicker). हे तेव्हा घडते जेव्हा React DOM वाचते, सुधारणांची गणना करते आणि स्टेट (state) अपडेट करते, परंतु ब्राउझर आधीच स्क्रीनवर पिक्सेल्स दाखवण्यास सुरुवात करतो. याचे नेहमीचे उपाय म्हणजे useEffect ऐवजी useLayoutEffect वापरणे. हे बदल काम करते, पण फक्त तुम्हाला प्रत्येक हुक (hook) ब्राउझरच्या पाइपलाइनमध्ये नेमका कधी कार्यान्वित होतो हे समजले तरच.

ब्राउझर पाइपलाइन: Render, Commit, Paint

React एका घटकाला (component) तीन वेगवेगळ्या टप्प्यांत अपडेट करते. render फेजमध्ये, React Virtual DOM तयार करते—किंवा पुन्हा तयार करते—आणि 'डिफ' (diff) मोजते. अजून कोणतेही प्रत्यक्ष पिक्सेल बदल होत नाहीत; ही केवळ मेमरीमध्ये होणारी गणना आहे. त्यानंतर commit फेज येतो, जिथे React ते बदल प्रत्यक्ष DOM नोड्सवर लागू करते. स्टाइल्स अपडेट होतात, नोड्स समाविष्ट किंवा काढून टाकले जातात आणि मजकूर बदलतो.

त्यानंतर ब्राउझरची भूमिका सुरू होते. paint फेजमध्ये, ब्राउझरचे रेंडरिंग इंजिन लेआउट भूमिती (layout geometry) मोजते आणि स्क्रीनवर पिक्सेल्स काढते. ही प्रक्रिया अत्यंत शिस्तबद्ध आहे. ब्राउझरला पेंट करण्यापूर्वी लेआउट पूर्ण करावा लागतो, आणि वापरकर्त्याला काहीही नवीन दिसण्यापूर्वी पेंटिंग पूर्ण झाले पाहिजे. commit आणि paint मधील अंतर मिलिसेकंदात मोजले जाते, परंतु ते वास्तव आहे, आणि हीच ती खिडकी आहे जिथे useEffect आणि useLayoutEffect वेगळे होतात.

useEffect मुळे फ्लिकर का होतो?

useEffect असिंक्रोनसली (asynchronously) चालते, ब्राउझरने स्क्रीन पेंट केल्यानंतर ते कार्यान्वित करण्यासाठी शेड्यूल केलेले असते. DOM अपडेट होतो, पिक्सेल्स काढले जातात आणि त्यानंतर React तुमचा इफेक्ट (effect) चालवण्यासाठी पुन्हा सक्रिय होते.

कल्पना करा की तुम्ही एका बटणाखाली ड्रॉपडाउन मेनू रेंडर करत आहात. useEffect मध्ये, तुम्ही buttonRef.current.getBoundingClientRect() कॉल करता, योग्य 'top' आणि 'left' कोऑर्डिनेट्स मोजता आणि ते स्टेटमध्ये साठवता. useEffect पेंटिंगनंतर चालत असल्यामुळे, ब्राउझरने ड्रॉपडाउन आधीच त्याच्या डीफॉल्ट पोझिशनवर, कदाचित top: 0, left: 0 वर काढलेला असतो. त्या पेंटिंगनंतरच तुमचा इफेक्ट स्टेट अपडेट करतो. React सुधारित कोऑर्डिनेट्स कमिट करते आणि ब्राउझर पुन्हा पेंट करतो. वापरकर्त्याला दोन फ्रेम्स दिसतात: आधी चुकीची पोझिशन आणि नंतर योग्य पोझिशन. तो दृश्य बदल (visual snap) म्हणजे फ्लिकर, जो टाळण्याचा प्रत्येकजण प्रयत्न करतो.

डेटा फेचिंग (data fetching), API कॉल्स, ॲनालिटिक्स ट्रॅकिंग किंवा इव्हेंट लिसनर्स सेट करण्यासाठी, या विलंबाचा फरक पडत नाही. पेंटिंगनंतर काही मिलिसेकंद उशिरा ॲनालिटिक्स बीकन (analytics beacon) फायर झाले तरी वापरकर्त्याला फरक पडत नाही. खरं तर, नॉन-व्हिज्युअल कामे पेंटिंगनंतरपर्यंत पुढे ढकलल्यामुळे सुरुवातीचे रेंडरिंग रिस्पॉन्सिव्ह राहते. परंतु लेआउट-आधारित सुधारणांसाठी, useEffect खूप उशिरा येतो.

useLayoutEffect पेंटिंगला कसे रोखते (Blocks)

useLayoutEffect सिंक्रोनसली (synchronously) चालते, React ने DOM मध्ये बदल केल्याबरोबर आणि ब्राउझरला लेआउट मोजण्याची किंवा पिक्सेल्स पेंट करण्याची संधी मिळण्यापूर्वीच ते कार्यान्वित होते. हे पूर्ण पेंट पाइपलाइनला रोखते.

जर तुम्ही useLayoutEffect मध्ये तेच ड्रॉपडाउन मोजण्याचे काम केले, तर क्रम बदलतो. React सुरुवातीचा DOM अपडेट कमिट करते, तुमचा लेआउट इफेक्ट चालवते आणि तुमचा स्टेट अपडेट एक सिंक्रोनस री-रेंडर (re-render) ट्रिगर करतो. React सुधारित कोऑर्डिनेट्स कमिट करते आणि त्यानंतरच ब्राउझर पेंट करतो. वापरकर्त्याला एकच फ्रेम दिसते, जी आधीच योग्य असते.

हे ब्लॉकिंग वर्तन हे वैशिष्ट्य आणि जोखीम दोन्ही आहे. useLayoutEffect जोपर्यंत काम पूर्ण होत नाही तोपर्यंत ब्राउझरला पेंट करण्यापासून रोखते, त्यामुळे त्यामध्ये केलेले कोणतेही जड काम (heavy computation) UI फ्रीझ करू शकते. काही डझन्स मिलिसेकंदचा ब्लॉक झालेला पेंट देखील वापरकर्त्याला 'जँक' (jank) सारखा वाटू शकतो. म्हणूनच React डॉक्युमेंटेशनमध्ये स्पष्टपणे सांगितले आहे की useEffect ने सुरुवात करा आणि जेव्हा तुम्हाला खरोखरच असा फ्लिकर दिसेल जो तुम्ही सहन करू शकत नाही, तेव्हाच useLayoutEffect कडे वळा.

प्रत्येक हुक कधी वापरावा

तुमचे बहुतेक लॉजिक useEffect मध्ये असावे. याचा वापर खालील गोष्टींसाठी करा:

  • API मधून डेटा फेच करणे
  • सबस्क्रिप्शन किंवा इव्हेंट लिसनर्स सेट करणे
  • ॲनालिटिक्स इव्हेंट्स पाठवणे
  • कोणताही असा साइड इफेक्ट (side effect) जो लगेच लेआउट वाचत नाही किंवा बदलत नाही

useLayoutEffect अशा कामांसाठी राखून ठेवा ज्यामध्ये वापरकर्त्याला फ्रेम दिसण्यापूर्वी DOM वाचणे आणि पुन्हा लिहिणे आवश्यक आहे:

  • घटकाचे परिमाण (dimensions) मोजणे, जसे की रुंदी (width), उंची (height), किंवा स्क्रोल पोझिशन
  • टूलटिप्स, पॉपओव्हर्स किंवा कॉन्टेक्स्ट मेन्यूसाठी कोऑर्डिनेट्स मोजणे
  • दृश्य पोझिशन रेंडर केलेल्या भूमितीवर अवलंबून असल्यास दिसणारे लेआउट शिफ्ट्स रोखणे

जर तुम्हाला निवडण्यात शंका असेल, तर useEffect निवडा. जेव्हा तुम्हाला दृश्य अस्थिरता (visual instability) जाणवेल तेव्हाच useLayoutEffect कडे वळा. केवळ या नियमामुळे बहुतांश React ॲप्लिकेशन्स सुरळीत चालतील.

सर्व्हर-साइड रेंडरिंगमधील अडचण (The Server-Side Rendering Gotcha)

जर तुम्ही Next.js, Remix, किंवा सर्व्हरवर React रेंडर करणारा कोणताही फ्रेमवर्क वापरत असाल, तर तुम्हाला useLayoutEffect सोबत एक वॉर्निंग (warning) येईल. सर्व्हरकडे DOM नसल्यामुळे, या हुकला (hook) मोजण्यासाठी काहीही नसते. React तुम्हाला सूचित करते की त्याला ब्राउझर एन्व्हायरमेंटची (environment) अपेक्षा होती पण ते मिळाले नाही. हायड्रेशनच्या (hydration) दरम्यान, या विसंगतीमुळे (mismatch) सूक्ष्म बग्स (bugs) येऊ शकतात, कारण सर्व्हर-रेंडर केलेले मार्कअप (markup) आणि क्लायंटचे पहिले नियोजित रेंडरिंग वेगवेगळे असू शकते.

याचे प्रमाणित निराकरण म्हणजे एक isomorphic hook वापरणे, जे एन्व्हायरमेंटनुसार योग्य इफेक्ट निवडते:

const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect;

ज्या कोणत्याही कंपोनंटमध्ये DOM नोड्स मोजणे आवश्यक आहे परंतु जे सर्व्हर रेंडरिंग दरम्यान कार्यान्वित (execute) होऊ शकतात, तिथे हा रॅपर (wrapper) वापरा. यामुळे वॉर्निंग थांबते आणि तुमचे सर्व्हर आउटपुट सुसंगत (consistent) राहते.

परफॉर्मन्स आणि सर्वोत्तम पद्धती (Best Practices)

useLayoutEffect पेंटिंगला (painting) रोखत असल्याने, हुकचा बॉडी (body) शक्य तितका हलका (light) ठेवा. लेआउट व्हॅल्यू वाचा, सुधारणा (correction) मोजा आणि ती पुन्हा लिहा. यामध्ये डेटा फेच (fetch) करू नका, मोठे ऑब्जेक्ट्स पार्स (parse) करू नका किंवा महागडे अल्गोरिदम (expensive algorithms) चालवू नका. येथे जड कोड असल्यास मेन थ्रेड (main thread) थांबेल आणि तुमचा इंटरफेस फ्रीज (frozen) झाल्यासारखा वाटेल.

जेव्हा तुम्ही एलिमेंट्स मोजता, तेव्हा document.getElementById ऐवजी React refs वापरा. Refs तुमच्या कंपोनंट इन्स्टन्सशी (instance) जोडलेले असतात, क्वेरी ट्रिक्सशिवाय री-रेंडर्समध्ये टिकून राहतात आणि पोर्टल्स (portals) किंवा कंडिशनल रेंडरिंगसोबत (conditional rendering) विश्वासार्हपणे काम करतात. ग्लोबल ID लुकअपमुळे कंपोनंट एन्कॅप्स्युलेशन (encapsulation) बिघडते आणि ज्या क्षणी तुम्हाला त्यांची गरज असते, त्याच वेळी ते null रिटर्न करू शकतात.

जवळपास प्रत्येक साइड इफेक्टसाठी (side effect) useEffect हा योग्य डिफॉल्ट पर्याय आहे. हे ब्राउझरला विनाअडथळा पेंटिंग करू देते आणि डेटा, इव्हेंट्स आणि एक्सटर्नल सिंक्रोनाइझेशन (external synchronization) व्यवस्थित हाताळते. useLayoutEffect हे एका विशिष्ट समस्येसाठी असलेले विशेष साधन आहे: पेंटिंग होण्यापूर्वी लेआउट वाचणे आणि तो पुन्हा लिहिणे. त्यांच्यामधील वेळेतील (timing) फरक समजून घ्या, म्हणजे तुम्ही फ्लिकर्सचा (flickers) पाठलाग करणे थांबवाल आणि ते रोखण्यास सुरुवात कराल.

खरे महत्त्वाचे: प्रत्येक गोष्टीसाठी useEffect ने सुरुवात करा. ज्या क्षणी तुम्हाला एखादा टूलटिप (tooltip) किंवा मॉडेल (modal) स्वतःला सुधारण्यापूर्वी चुकीच्या ठिकाणी चमकताना (blink) दिसेल, तो तुमचा संकेत आहे. useLayoutEffect कडे वळा, DOM मोजा, तुमचा लेआउट ॲडजस्ट करा आणि ब्राउझरला एकदाच—योग्यरित्या—पेंट करू द्या.