useLayoutEffect

Pitfall

useLayoutEffect performance-ஐ பாதிக்கக்கூடும். சாத்தியமான போது useEffect-ஐ முன்னுரிமை அளியுங்கள்.

useLayoutEffect என்பது browser screen-ஐ repaint செய்வதற்கு முன் fire ஆகும் useEffect-ன் ஒரு version.

useLayoutEffect(setup, dependencies?)

Reference

useLayoutEffect(setup, dependencies?)

Browser screen-ஐ repaint செய்வதற்கு முன் layout measurements செய்ய useLayoutEffect-ஐ call செய்யுங்கள்:

import { useState, useRef, useLayoutEffect } from 'react';

function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);

useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height);
}, []);
// ...

மேலும் எடுத்துக்காட்டுகளை கீழே பாருங்கள்.

Parameters

  • setup: உங்கள் Effect-ன் logic கொண்ட function. உங்கள் setup function விருப்பமாக cleanup function ஒன்றையும் return செய்யலாம். உங்கள் component commit ஆகும் முன், React உங்கள் setup function-ஐ run செய்யும். Dependencies மாறிய ஒவ்வொரு commit-க்கும் பிறகு, React முதலில் பழைய values உடன் cleanup function-ஐ (நீங்கள் வழங்கியிருந்தால்) run செய்து, பின்னர் புதிய values உடன் setup function-ஐ run செய்யும். உங்கள் component DOM-இலிருந்து remove செய்யப்படுவதற்கு முன், React உங்கள் cleanup function-ஐ run செய்யும்.

  • optional dependencies: setup code-க்குள் referenced செய்யப்படும் அனைத்து reactive values-ன் list. Reactive values-ல் props, state, மேலும் உங்கள் component body-க்குள் நேரடியாக declared செய்யப்பட்ட அனைத்து variables மற்றும் functions அடங்கும். உங்கள் linter React-க்காக configured செய்யப்பட்டிருந்தால், ஒவ்வொரு reactive value-மும் dependency ஆக சரியாக குறிப்பிடப்பட்டுள்ளதா என்பதை அது verify செய்யும். Dependencies list நிலையான எண்ணிக்கையிலான items கொண்டதாகவும் [dep1, dep2, dep3] போல inline ஆக எழுதப்பட்டதாகவும் இருக்க வேண்டும். React ஒவ்வொரு dependency-யையும் அதன் முந்தைய value-உடன் Object.is comparison மூலம் compare செய்யும். இந்த argument-ஐ omit செய்தால், component-ன் ஒவ்வொரு commit-க்கும் பிறகு உங்கள் Effect மீண்டும் run ஆகும்.

Returns

useLayoutEffect undefined-ஐ return செய்கிறது.

Caveats

  • useLayoutEffect ஒரு Hook; எனவே அதை உங்கள் component-ன் top level-இல் அல்லது உங்கள் சொந்த Hooks-இல் மட்டுமே call செய்யலாம். Loops அல்லது conditions-க்குள் அதை call செய்ய முடியாது. அது தேவைப்பட்டால், component ஒன்றை extract செய்து Effect-ஐ அங்கே நகர்த்துங்கள்.

  • Strict Mode on ஆக இருக்கும்போது, முதல் உண்மையான setup-க்கு முன் React development-only setup+cleanup cycle ஒன்றை கூடுதலாக run செய்யும். இது உங்கள் cleanup logic உங்கள் setup logic-ஐ “mirrors” செய்கிறது என்பதையும், setup செய்யும் எதையும் அது stop அல்லது undo செய்கிறது என்பதையும் உறுதி செய்யும் stress-test. இது பிரச்சினை ஏற்படுத்தினால், cleanup function-ஐ implement செய்யுங்கள்.

  • உங்கள் dependencies-இல் சில component-க்குள் defined செய்யப்பட்ட objects அல்லது functions ஆக இருந்தால், அவை Effect தேவைக்கு அதிகமாக re-run ஆகும் அபாயம் உள்ளது. இதைச் சரிசெய்ய, தேவையற்ற object மற்றும் function dependencies-ஐ remove செய்யுங்கள். உங்கள் Effect-க்கு வெளியே state updates மற்றும் non-reactive logic-ஐயும் extract செய்யலாம்.

  • Effects client-இல் மட்டுமே run ஆகும். Server rendering போது அவை run ஆகாது.

  • useLayoutEffect-க்குள் உள்ள code மற்றும் அதிலிருந்து schedule செய்யப்படும் அனைத்து state updates-மும் browser screen-ஐ repaint செய்வதை block செய்கின்றன. அதிகமாக பயன்படுத்தினால், இது உங்கள் app-ஐ slow ஆக்கும். சாத்தியமானபோது useEffect-ஐ முன்னுரிமை அளியுங்கள்.

  • useLayoutEffect-க்குள் state update ஒன்றை trigger செய்தால், useEffect உட்பட மீதமுள்ள அனைத்து Effects-ஐயும் React உடனே execute செய்யும்.


Usage

Browser screen-ஐ repaint செய்வதற்கு முன் layout-ஐ measure செய்தல்

பெரும்பாலான components என்ன render செய்ய வேண்டும் என்பதை முடிவு செய்ய screen-இல் தங்களின் position மற்றும் size-ஐ அறிய தேவையில்லை. அவை சில JSX-ஐ மட்டும் return செய்கின்றன. பின்னர் browser அவற்றின் layout (position மற்றும் size)-ஐ calculate செய்து screen-ஐ repaint செய்கிறது.

சில நேரங்களில் அது போதாது. Hover செய்யும்போது ஒரு element-க்கு அருகில் தோன்றும் tooltip ஒன்றைக் கற்பனை செய்யுங்கள். போதுமான இடம் இருந்தால், tooltip element-க்கு மேல் தோன்ற வேண்டும்; பொருந்தவில்லை என்றால் கீழே தோன்ற வேண்டும். Tooltip-ஐ சரியான இறுதி position-இல் render செய்ய, அதன் height-ஐ தெரிந்திருக்க வேண்டும் (அதாவது அது மேலே பொருந்துமா என்பதை).

இதற்கு, இரண்டு passes-இல் render செய்ய வேண்டும்:

  1. Tooltip-ஐ எங்காவது render செய்யுங்கள் (தவறான position உடனாக இருந்தாலும்).
  2. அதன் height-ஐ measure செய்து tooltip-ஐ எங்கு வைக்க வேண்டும் என்பதை முடிவு செய்யுங்கள்.
  3. Tooltip-ஐ சரியான இடத்தில் மீண்டும் render செய்யுங்கள்.

இவை அனைத்தும் browser screen-ஐ repaint செய்வதற்கு முன் நடக்க வேண்டும். Tooltip நகர்வதை user பார்க்க நீங்கள் விரும்பமாட்டீர்கள். Browser screen-ஐ repaint செய்வதற்கு முன் layout measurements செய்ய useLayoutEffect-ஐ call செய்யுங்கள்:

function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // உண்மையான height இன்னும் தெரியாது

useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // உண்மையான height தெரிந்ததால் இப்போது re-render செய்
}, []);

// ...use tooltipHeight in the rendering logic below...
}

இது படிப்படியாக எப்படி வேலை செய்கிறது:

  1. Tooltip ஆரம்ப tooltipHeight = 0 உடன் render ஆகிறது (எனவே tooltip தவறாக positioned ஆக இருக்கலாம்).
  2. React அதை DOM-இல் வைத்து, useLayoutEffect-க்குள் உள்ள code-ஐ run செய்கிறது.
  3. உங்கள் useLayoutEffect tooltip content-ன் height-ஐ measure செய்து உடனடி re-render ஒன்றை trigger செய்கிறது.
  4. Tooltip உண்மையான tooltipHeight உடன் மீண்டும் render ஆகிறது (எனவே tooltip சரியாக positioned ஆகிறது).
  5. React அதை DOM-இல் update செய்கிறது, இறுதியாக browser tooltip-ஐ display செய்கிறது.

கீழே உள்ள buttons மீது hover செய்து, அது பொருந்துகிறதா என்பதைப் பொறுத்து tooltip எப்படி தனது position-ஐ adjust செய்கிறது என்பதைப் பாருங்கள்:

import { useRef, useLayoutEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import TooltipContainer from './TooltipContainer.js';

export default function Tooltip({ children, targetRect }) {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height);
    console.log('அளவிடப்பட்ட tooltip height: ' + height);
  }, []);

  let tooltipX = 0;
  let tooltipY = 0;
  if (targetRect !== null) {
    tooltipX = targetRect.left;
    tooltipY = targetRect.top - tooltipHeight;
    if (tooltipY < 0) {
      // It doesn't fit above, so place below.
      tooltipY = targetRect.bottom;
    }
  }

  return createPortal(
    <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}>
      {children}
    </TooltipContainer>,
    document.body
  );
}

Tooltip component இரண்டு passes-இல் render செய்ய வேண்டியிருந்தாலும் (முதலில் tooltipHeight 0 ஆக initialized ஆக, பின்னர் உண்மையாக measured height உடன்), நீங்கள் இறுதி result-ஐ மட்டுமே பார்க்கிறீர்கள் என்பதை கவனியுங்கள். இந்த example-க்கு useEffect-க்கு பதிலாக useLayoutEffect தேவைப்படுவதற்கான காரணம் இதுவே. கீழே வேறுபாட்டைப் பார்க்கலாம்.

useLayoutEffect vs useEffect

Example 1 of 2:
useLayoutEffect browser repaint செய்வதை block செய்கிறது

useLayoutEffect-க்குள் உள்ள code மற்றும் அதற்குள் schedule செய்யப்படும் எந்த state updates-மும் browser screen-ஐ repaint செய்வதற்கு முன் process செய்யப்படும் என்று React guarantee செய்கிறது. இதனால் tooltip-ஐ render செய்து, measure செய்து, user முதல் கூடுதல் render-ஐ கவனிக்காமல் மீண்டும் render செய்யலாம். வேறு வார்த்தைகளில், useLayoutEffect browser painting-ஐ block செய்கிறது.

import { useRef, useLayoutEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import TooltipContainer from './TooltipContainer.js';

export default function Tooltip({ children, targetRect }) {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height);
  }, []);

  let tooltipX = 0;
  let tooltipY = 0;
  if (targetRect !== null) {
    tooltipX = targetRect.left;
    tooltipY = targetRect.top - tooltipHeight;
    if (tooltipY < 0) {
      // It doesn't fit above, so place below.
      tooltipY = targetRect.bottom;
    }
  }

  return createPortal(
    <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}>
      {children}
    </TooltipContainer>,
    document.body
  );
}

Note

இரண்டு passes-இல் render செய்வதும் browser-ஐ block செய்வதும் performance-ஐ பாதிக்கும். முடிந்தவரை இதைத் தவிர்க்க முயலுங்கள்.


Troubleshooting

எனக்கு error வருகிறது: “useLayoutEffect server-இல் எதுவும் செய்யாது”

useLayoutEffect-ன் நோக்கம் உங்கள் component rendering-க்கு layout information பயன்படுத்த அனுமதிப்பதே:

  1. Initial content-ஐ render செய்யுங்கள்.
  2. Browser screen-ஐ repaint செய்வதற்கு முன் layout-ஐ measure செய்யுங்கள்.
  3. நீங்கள் வாசித்த layout information-ஐ பயன்படுத்தி final content-ஐ render செய்யுங்கள்.

நீங்கள் அல்லது உங்கள் framework server rendering-ஐ பயன்படுத்தும்போது, initial render-க்காக உங்கள் React app server-இல் HTML ஆக render ஆகிறது. இதனால் JavaScript code load ஆகும் முன் initial HTML-ஐ காட்டலாம்.

பிரச்சினை என்னவெனில் server-இல் layout information இல்லை.

முந்தைய example-இல், Tooltip component-இல் உள்ள useLayoutEffect call, content height-ஐப் பொறுத்து அது தன்னைத்தானே சரியாக position செய்ய (content-க்கு மேலோ கீழோ) அனுமதிக்கிறது. Initial server HTML-ன் பகுதியாக Tooltip-ஐ render செய்ய முயன்றால், இதை தீர்மானிப்பது சாத்தியமில்லை. Server-இல் layout இன்னும் இல்லை! ஆகவே, server-இல் அதை render செய்தாலும், JavaScript load ஆகி run ஆன பிறகு அதன் position client-இல் “jump” ஆகும்.

வழக்கமாக, layout information-ஐ சார்ந்த components server-இல் render ஆக தேவையில்லை. உதாரணமாக, initial render போது Tooltip காட்டுவது பெரும்பாலும் பொருள் இல்லாதது. அது client interaction மூலம் trigger செய்யப்படுகிறது.

ஆனால், இந்த பிரச்சினையை நீங்கள் சந்தித்தால், சில வேறு options உங்களிடம் உள்ளன:

  • useLayoutEffect-ஐ useEffect-ஆல் replace செய்யுங்கள். இது original HTML உங்கள் Effect run ஆகும் முன் visible ஆகிவிடுவதால், paint-ஐ block செய்யாமல் initial render result-ஐ display செய்வது பரவாயில்லை என்று React-க்கு சொல்கிறது.

  • மாற்றாக, உங்கள் component-ஐ client-only ஆக mark செய்யுங்கள். Server rendering போது, closest <Suspense> boundary வரை அதன் content-ஐ loading fallback (உதாரணமாக spinner அல்லது glimmer) ஒன்றால் replace செய்ய React-க்கு இது சொல்கிறது.

  • மாற்றாக, hydration-க்கு பிறகே useLayoutEffect உள்ள component-ஐ render செய்யலாம். false ஆக initialized செய்யப்படும் boolean isMounted state ஒன்றை வைத்திருந்து, useEffect call-க்குள் அதை true ஆக set செய்யுங்கள். பின்னர் உங்கள் rendering logic return isMounted ? <RealContent /> : <FallbackContent /> போல இருக்கலாம். Server-இலும் hydration நேரத்திலும், user FallbackContent-ஐப் பார்ப்பார்; அது useLayoutEffect-ஐ call செய்யக்கூடாது. பின்னர் React அதை client-இல் மட்டும் run ஆகும் மற்றும் useLayoutEffect calls சேர்க்கக்கூடிய RealContent-ஆல் replace செய்யும்.

  • உங்கள் component-ஐ external data store-உடன் synchronize செய்து, layout measure செய்வதைத் தவிர வேறு காரணங்களுக்காக useLayoutEffect-ஐ சார்ந்திருந்தால், அதற்குப் பதிலாக useSyncExternalStore-ஐ பரிசீலிக்கவும்; அது server rendering-ஐ support செய்கிறது.