React teljesítmény optimalizálás: A felesleges re-render elkerülése

A React alapvetően rendkívül gyors a Virtuális DOM (Virtual DOM) architektúrájának köszönhetően. Azonban ahogy egy alkalmazás egyre nagyobbra és komplexebbé nő, a fejlesztők gyakran szembesülnek azzal a problémával, hogy az alkalmazás belassul, a gépelés akad a beviteli mezőkben, vagy a gombokra kattintás késik. Ennek a leggyakoribb oka a felesleges újra-renderelések (unnecessary re-renders) sorozata.

Hogyan működik a React renderelési ciklusa?

Amikor egy React komponens állapota (state) vagy a szülőtől kapott tulajdonságai (props) megváltoznak, a React újra lefutatja a komponenst, hogy kiszámolja az új UI-t. A probléma ott kezdődik, hogy alapértelmezés szerint egy szülő komponens újra-renderelése automatikusan újra-rendereli az összes gyermek komponensét is, függetlenül attól, hogy a gyermek adatai változtak-e vagy sem.

1. A React.memo() használata

A legegyszerűbb védekezés a felesleges renderelések ellen a React.memo. Ez egy magasabb rendű komponens (Higher Order Component - HOC), ami "megjegyzi" a komponens utolsó renderelt állapotát. Ha a komponens props-ai nem változtak az előző renderelés óta, a React egyszerűen újra felhasználja az előző eredményt, megspórolva a számítási időt.

import React, { memo } from 'react';

const NehezKomponens = memo(({ adat }) => {
    console.log("Nehéz komponens renderelése...");
    return <div>Feldolgozott adat: {adat}</div>;
});

export default NehezKomponens;

2. A useMemo Hook alkalmazása

Gyakran előfordul, hogy egy komponensen belül egy számításigényes műveletet (például egy nagy tömb szűrését vagy rendezését) kell elvégezni. Ha a komponenst újra-renderelik (például egy egyszerű input mező változása miatt), a számításigényes feladat újra lefut.

A useMemo hook segítségével eltárolhatjuk (memoizálhatjuk) egy számítás eredményét, és csak akkor számoljuk újra, ha a függőségei (dependencies) megváltoznak.

import React, { useState, useMemo } from 'react';

function TermekLista({ termekek }) {
    const [keresoSzo, setKeresoSzo] = useState('');

    // Ez a drága szűrés csak akkor fut le, ha a 'termekek' vagy a 'keresoSzo' változik!
    const szurtTermekek = useMemo(() => {
        console.log("Drága szűrés folyamatban...");
        return termekek.filter(t => t.nev.includes(keresoSzo));
    }, [termekek, keresoSzo]);

    return (
        <div>
            <input onChange={(e) => setKeresoSzo(e.target.value)} />
            {szurtTermekek.map(t => <div key={t.id}>{t.nev}</div>)}
        </div>
    );
}

3. A useCallback Hook fontossága

A JavaScriptben két függvény (még ha a kódjuk teljesen ugyanaz is) nem egyenlő egymással memóriacím alapján (===). Amikor egy szülő komponens újra-renderelődik, az összes benne definiált függvény újra létrejön a memóriában. Ha ezeket a függvényeket átadjuk egy React.memo-val védett gyermek komponensnek props-ként, a gyermek látni fogja a változást (mert új a függvény referenciája), és ő is újra-renderelődik, tönkretéve a memo hatását.

A useCallback megőrzi a függvény memóriareferenciáját a renderelések között:

import React, { useState, useCallback } from 'react';
import NehezGomb from './NehezGomb';

function SzuloKomponens() {
    const [szamlalo, setSzamlalo] = useState(0);

    // A függvény referenciája stabil marad, amíg a függőségek nem változnak
    const kattintasKezelo = useCallback(() => {
        console.log("Gombra kattintottak!");
    }, []);

    return (
        <div>
            <p>{szamlalo}</p>
            <button onClick={() => setSzamlalo(s => s + 1)}>Növelés</button>
            <NehezGomb onClick={kattintasKezelo} />
        </div>
    );
}

Összegzés

A React alkalmazások teljesítményének javítása nem a framework hibáinak javítása, hanem annak megértése, hogyan hoz döntéseket a React. Bár a memo, useMemo és useCallback rendkívül hasznosak, ne használjuk őket mindenhol vakon! Maga a memoizáció is memóriába kerül és futásidejű költsége van. Csak akkor alkalmazzuk ezeket, ha valós teljesítményproblémát tapasztalunk, vagy bizonyítottan nehéz számításokat végzünk.

Gyakran Ismételt Kérdések (GYIK)

Minden komponenst érdemes React.memo-ba csomagolni?

Nem. Ha egy komponens props-ai szinte minden renderelésnél amúgy is megváltoznak (pl. folyamatosan frissülő adatok), akkor a memo összehasonlító logikája csak plusz terhet jelent a böngészőnek anélkül, hogy megakadályozná a re-rendert.


Ezek is érdekelhetnek: