React wil dat je componenten voorspelbaar zijn. Geef het dezelfde state en props, en het zou elke keer dezelfde UI moeten renderen. Maar de meeste echte applicaties kunnen niet overleven binnen die bubbel. Ze moeten naar buiten reiken. Een dashboard heeft verse cijfers van een server nodig. Een chatwidget moet luisteren naar berichten. Een timer moet tikken. Deze operaties zijn side effects, en ze bevinden zich buiten de render-cyclus van React. De useEffect hook is de plek waar je dat rommelige, onvoorspelbare werk onderbrengt, zodat je component zelf betrouwbaar blijft.
Side Effects: Wat hoort binnen useEffect
Een side effect is alles wat de wereld raakt buiten het retourneren van JSX om. De render-fase van React moet puur zijn. Wanneer je begint met het ophalen van data, het schrijven naar globale variabelen of het koppelen van listeners aan de DOM, heb je het pure territorium verlaten.
Veelvoorkomende voorbeelden zijn:
- Data ophalen van een API
- Timers of intervallen instellen
- Event listeners toevoegen aan
windowofdocument - De titel van het browsertabblad bijwerken
- Verbinding maken met WebSockets
Deze taken hebben één gemeenschappelijk kenmerk: ze horen niet in de return-statement van je component of tijdens de hoofd-renderlogica. Het direct aanroepen van een browser API zoals setInterval binnen de render-body zal bij elke render worden uitgevoerd, wat zorgt voor dubbele timers en verwarrend gedrag. useEffect bestaat precies om dit werk te isoleren en op het juiste moment uit te voeren.
Hoe de dependency array de timing regelt
Het tweede argument van useEffect is de dependency array, en dit is de grootste bron van verwarring voor ontwikkelaars die overstappen van class components. Zie het als een set variabelen die React in de gaten houdt om te beslissen of je effect na de huidige render moet worden overgeslagen of uitgevoerd.
Er zijn drie patronen die je herhaaldelijk zult gebruiken.
Geen dependency array. Als je de array volledig weglaat, gaat React ervan uit dat je wilt dat het effect na elke render wordt uitgevoerd, inclusief de eerste. Dit is zelden wat je nodig hebt. Als je effect een netwerkverzoek of een zware DOM-operatie uitvoert, zal het uitvoeren ervan bij elke toetsaanslag of state-wijziging de prestaties kelderen. Gebruik dit patroon alleen wanneer je echt iets opnieuw moet uitvoeren omdat elke prop of state gewijzigd zou kunnen zijn en je niet kunt specificeren welke.
Een lege array []. Dit vertelt React om het effect één keer uit te voeren, direct nadat de component is gemount en de DOM gereed is. Dit is de juiste plek voor het initiële ophalen van data. Als je component bijvoorbeeld gebruikersprofielgegevens laadt, wil je dat dat verzoek precies één keer wordt uitgevoerd wanneer de profielpagina verschijnt, en niet elke keer dat de gebruiker interactie heeft met een formulier lager op de pagina.
Een array met specifieke variabelen [count]. Dit is het precisie-instrument. React vergelijkt de huidige waarden van deze dependencies met hun waarden tijdens de laatste render. Als een van deze waarden is gewijzigd, wordt het effect uitgevoerd. Als er niets in de lijst is gewijzigd, slaat React het effect volledig over.
Als je de titel van het browsertabblad synchroniseert met een state-variabele, plaats je die variabele in de dependency array. React zal de titel dan alleen bijwerken wanneer die waarde verandert. Laat je hem weg, dan blijft de titel verouderd. Voeg niet-gerelateerde state-variabelen toe, en je verspilt rekenkracht aan het bijwerken van de titel voor wijzigingen die er niet toe doen.
Cleanup is niet optioneel
Sommige effects laten sporen achter. Een timer blijft tellen. Een event listener blijft afgaan. Een WebSocket blijft openstaan. Wanneer je component unmount, of zelfs wanneer een effect opnieuw wordt uitgevoerd omdat de dependencies zijn gewijzigd, ruimt React de resten van het vorige effect niet automatisch op. Dat is jouw taak.
Je maakt een cleanup-functie door een functie te retourneren vanuit de useEffect. React roept deze cleanup aan voordat het volgende effect wordt toegepast, en nogmaals wanneer de component van het scherm verdwijnt.
Je moet cleanup gebruiken voor:
- Het wissen van intervallen of timeouts met
clearIntervalofclearTimeout - Het verwijderen van event listeners die zijn toegevoegd aan
window,documentof externe nodes - Het uitschrijven (unsubscribing) van datastromen of services
Verwaarloos dit en je krijgt memory leaks. Een component wordt gemount, koppelt een scroll-listener, unmount, en de listener blijft bestaan. De browser houdt de callback en de DOM-nodes waarnaar deze verwijst vast. Na verloop van tijd, vooral in single-page applicaties met veel navigatie, stapelen deze 'geesten' zich op en vertragen ze het tabblad. De oplossing is meestal slechts een paar regels: retourneer een functie die verwijdert wat je hebt toegevoegd.
Veelvoorkomende fouten die in productie belanden
Zelfs ervaren ontwikkelaars grijpen naar useEffect wanneer er een eenvoudigere optie bestaat. Hier zijn drie patronen die een rode vlag moeten zijn tijdens een code review.
Oneindige loops. Update nooit een state-variabele binnen useEffect als diezelfde variabele in je dependency array staat, tenzij je een voorwaarde hebt die de cyclus doorbreekt. Als je count uitleest, deze verhoogt en count als dependency vermeldt, ziet React de verandering, voert een re-render uit, draait de effect opnieuw, verhoogt deze opnieuw en blokkeert de browser.
Onnodige effects. Gebruik useEffect niet om een waarde te berekenen op basis van bestaande props of state. Als je deze direct tijdens de render kunt afleiden, doe dat dan gewoon. Afgeleide waarden horen in de body van de component of in een gememoriseerde berekening met useMemo. Door ze naar een effect te verplaatsen, splits je de logica over de render- en effectfases zonder dat dit voordeel oplevert, wat de code moeilijker te volgen maakt.
Het verkeerde hulpmiddel voor gebruikersacties. `use
