Hopp til hovedinnhold

<ForrigeUke uke="40" år=2025" />

Publisert:7. oktober
Skrevet av:Marcus Haaland

Raskere fanevisning, mindre ignorering i useEffect og en detaljrik debugging — i denne ukas ForrigeUke.

Dette var uka for ny kaptein på dekk og ho vi alle kjenner til — og 3110 ting som skjedde i frontendverdenen!

«<ForrigeUke /> er en artikkelserie som oppsummerer hva som skjedde i frontend-verden i uka som var.»

Idet vi går inn i en skummel måned 🎃, slipper React sin tredje lansering det siste året. I lanseringsposten er det mye å grave i, men denne gang er det tre funksjonaliteter jeg har trukket frem, og som jeg gleder meg til å ta i bruk:

  • Den nye Activity-komponenten, som gjør betinget visning kjappere
  • useEffectEvent, som kan virkelig rydde opp i useEffecter, og
  • Performance Tracks, som gir enda mer detaljrike devtools

Så la oss gå gjennom steg for steg hva dette betyr.

<Activity />

Activity- komponenten er kanskje den som har fått mest oppmerksomhet i denne lanseringen. Overfladisk gjør denne komponenten det samme som en vanlig betinget visning, hvor du viser noe innhold og skjuler noe annet. Forskjellen er at med Activity bevarer React tilstanden når noe skjules, i stedet for å fjerne det helt fra DOM-en.

Her er et eksempel hvor vi betinget viser en teller med en betinget visning, uten Activity:

export function WithConditional() {
  const [showCounter, setShowCounter] = useState(false);
  return (
    <div>
      <button onClick={() => setShowCounter(!showCounter)}>
        Toggle counter
      </button>

      // 👇 Betinget viser en komponent
      {showCounter ? <Counter /> : null}
      <MainContent />
    </div>
  );
}

Om vi inkrementerer telleren opp til 3:

Om counteren er vanlig betinget vist, vil telleren nullstilles når toggle lukkes.

Men så toggler vi telleren vekk, og åpner telleren igjen... Da har 3 endret seg til 0:

Etter lukking og åpning av telleren, vil tilstanden bli nullstilt.

Årsaken er at når vi bruker vanlig betinget visning, unmountes komponenten når den ikke vises. Dette betyr at arbeid må gjøres på nytt neste gang komponenten vises, og brukeren må eventuelt fylle inn data på nytt.

Activity lar deg skjule komponenter uten å miste intern tilstand, ved å styre synlighet med mode-prop-en:

export function WithActivity() {
  const [showCounter, setShowCounter] = useState(false);
  return (
    <div>
      <button onClick={() => setShowCounter(!showCounter)}>
        Toggle counter
      </button>

      // 👇 mode styrer om barn-komponentene blir vist
      <Activity mode={showCounter ? "visible" : "hidden"}>
        <Counter />
      </Activity>
      <MainContent />
    </div>
  );
}

Her blir komponenten visuelt skjult, men React bevarer tilstanden slik at brukeren ser samme verdier når komponenten vises igjen — altså ville tilstanden til 3 vært bevart.

I tillegg vil også komponentene inni Activity fortsatt re-rendres, selv når modusen er satt til "hidden". Det fører til at innholdet er raskere klart når det vises igjen. Samtidig skjer denne re-rendringen med lavere prioritet, slik at det ikke gjør resten av appen tregere.

Du kan tenke at komponentene i Activity oppfører seg som om de blir unmountet, siden useEffect-cleanups faktisk kjøres når modusen endres til "hidden". Men her ligger også en liten gotcha: de er ikke helt unmounted. Elementet fjernes ikke fra DOM-en (React bruker faktisk display: none). Det betyr at hvis du for eksempel spiller av en video i en komponent, vil ikke videoen stoppe automatisk når innholdet skjules. Slike sideeffekter må du rydde opp i selv — for eksempel med en cleanup-funksjon i useEffect:

useEffect(() => {
  const videoRef = ref.current;

  return () => {
    videoRef.pause()
  }
}, []);

Så når skal du bruke Activity?

Activity- komponenten er ikke bare for å bevare tilstand du ikke ønsker å nullstille. Den lar deg også holde deler av appen aktive i bakgrunnen, slik at de raskt kan vises igjen uten ny oppstart. Det gjør den perfekt for faner og sidepaneler, hvor brukeren veksler mellom ulikt innhold.

Et annet interessant usecase er å bruke Activity som alternativ til Suspense — ved visse oppsett. Siden Activity deler opp komponentene, impliserer dette til React at det synlige skal prioriteres først.

Activity er altså et verktøy for å øke ytelsen og responsiviteten i applikasjonen. Men som med all optimalisering, er det en balanse: hvis du bruker den overalt, kan du ende opp med mye bakgrunnsarbeid som totalt sett gjør appen tregere. Derfor bør du bruke Activity når du trenger betinget visning eller har behov for å bevare tilstand — også bør du følge med på ytelsen også forbedres — noe vi snart skal se på hvordan du kan gjøre.

Men først til en forbedring i hvordan vi bruker useEffect.

useEffectEvent

En useEffect lar deg kjøre en funksjon når variabler endres. Men det er ikke alltid du ønsker at alle variablene du bruker, også skal fyre igang funksjonen igjen. Som i dette eksempelet, hvor vi vil logge url og handlekurv, men vil kun kjøre det når URL-en endres:

export function WithUseEffect({ url }: Props) {
  const { items } = useShoppingCart();

  useEffect(() => {
    logVisit(url, items);
    /* 
      👇 Her vil linter klage. 
      ⚠️ React Hook useEffect has a missing dependency: 'items'.
      Vi kan unngå det med å legge til 
      // eslint-disable-next-line react-hooks/exhaustive-deps
    */
  }, [url]); 

  return <div>Page</div>;
}

En “fiks” er å skru av eslint. Men når vi senere endrer innholdet i useEffecten, kan vi ved mangel på varsler glemme å oppdatere verdier vi ønsker skal føre til ny kjøring av funksjonen — som kan lede til bugs. Derfor er ikke ignorering av regler, overraskende nok, en reliabel tilnærming.

For å fikse dette scenarioet har React-teamet introdusert useEffectEvent. Her kan vi pakke inn deler av funksjonen, så det ikke introduseres nye avhengigheter i useEffect-en. For eksempelet over, kan det se slik ut:

export function WithUseEffectEvent({ url }: Props) {
  const { items } = useShoppingCart();

  /*
    👇 Flytter variabelen items, som tidligere førte til kjøring, 
       inn i en useEffectEvent
  */
  const onNavigate = useEffectEvent((visitedUrl: string) => {
    logVisit(visitedUrl, items);
  });

  useEffect(() => {
    // 👇 Kaller på useEffectEvent i en useEffect
    onNavigate(url);
  }, [url]);

  return <div>Page</div>;
}

Nå har vi stykket opp innholdet i useEffecten til det som skal føre til ny kjøring (altså url) og det som ikke skal føre til ny kjøring (altså items).

Eksempelet over er ganske grunnleggende. Men det fins mer kompliserte eksempler som utviklere har måttet jobbe seg rundt, som har inneholdt en spaghetti av useRefs og betingelser. Med introduksjonen av useEffectEvent blir koden betydelig mer forståelig — og vil forhåpentligvis gi færre overraskelser ved endringer.

Performance Tracks

Jeg skal innrømme at mine debuggings-skills i frontend ofte utarter seg i enkle console.logs. Med introduseringen av performance tracks har debugging blitt enda mer detaljert, som kanskje blir påskuddet for meg å oppdatere verktøykassa.

Debuggingverktøy for React er ikke noe nytt i seg selv, men med performance tracks får vi verktøy uten å trenge å legge til en nettleser-utvidelse. React har nemlig lagt til underfanen "Scheduler" og "Components" i Performance-fanen, som du finner når du åpner utviklerverktøyet i nettleseren. Hver av disse forteller oss hvordan React fungerer:

Performance-fanen i nettleseren har nå flere detaljer. Bilde fra Hanlon: https://bsky.app/profile/ricky.fm/post/3m26ald62g227
  • Scheduler, som navnet hinter om, viser oss hvordan React prioriterer oppgaver. Her kan vi se de synkrone oppdateringene, som brukerinput, også kan vi se bakgrunnsarbeid, som transitions eller andre oppgaver med lavere prioritet.
  • Components viser oss livssyklusene til komponentene. Interessant nok ser vi også et nytt event i render-fasen etter introduksjonen av Activity, nemlig reconnect og disconnect. Disse tilsvarerer mount og unmount, men med unntak av at vi husker tilstanden.

Det jeg synes er enda mer spennende enn debugging, er hvordan dette verktøyet kan gjøre React enda lettere å forstå i en pedagogisk sammenheng, et poeng jeg så først påpekt av nuqs-utvikler Francois Best. Opp gjennom studietiden og i jobbhverdagen har jeg flere ganger lært, og lært bort, React. Så å kunne krydre eksempler med å vise hvordan React egentlig fungerer, steg for steg, er noe jeg er veldig nysgjerrig på å teste ut.

Best peker også på en post av Ricky Hanlon som har gjort nettopp det. Nå kan vi i detalj se hva som skjer i React fra øyeblikket en bruker trykker på en knapp, noen oppdateringer blir prioritert, noen spinnere dukker opp, en transition vises, og til slutt får brukeren gjennomført handlingen. Det er absolutt verdt å ta en titt, så du forstår hvordan du kan bruke performance tracks.

Vent! Det er mer 🍿

Om denne posten ga mersmak, kan jeg også tipse om at årets React Conf har på agendaen å dykke dypere i disse temaene. På tirsdag (6. oktober), 19:10 norsk tid, går Chance Strickland på scenen for å snakke om Activity (og View transitions). Og rett etterpå skal Ruslan Lesiutin snakke om performance tracks. Opptak dukker opp på streamen: https://www.youtube.com/watch?v=zyVRg2QR6LA.

Med noen timer snacks underholdning i påvente, runder jeg av for denne gang. Ha en riktig god uke 👋

Skrevet av