React.memo — component ni keshlash, qachon kerak

~15 daq · React · Performance

Muammodan boshlaylik

Mahsulotlar sahifasi: tepada qidiruv maydoni, pastda 500 ta mahsulot kartochkasi.

function Katalog({ mahsulotlar }) {
  const [qidiruv, setQidiruv] = useState('');

  return (
    <>
      <input value={qidiruv} onChange={(e) => setQidiruv(e.target.value)} />
      <MahsulotRoyxati mahsulotlar={mahsulotlar} />
    </>
  );
}

Har bir harf yozilganda Katalog render bo'ladi → MahsulotRoyxati va uning 500 ta kartochkasi ham (67-dars). Telefonda yozish sezilarli sekinlashdi: harflar kechikib paydo bo'lyapti.

mahsulotlar o'zgarmagan — ro'yxatni qayta render qilishning ma'nosi yo'q. React ga buni aytish kerak.


React.memo

import { memo } from 'react';

const MahsulotRoyxati = memo(function MahsulotRoyxati({ mahsulotlar }) {
  return (
    <ul>
      {mahsulotlar.map((m) => <MahsulotKartochka key={m.id} mahsulot={m} />)}
    </ul>
  );
});

memo komponentni o'raydi va React ga aytadi: "ota-ona render bo'lganda, avval props larni solishtir. Hammasi oldingisi bilan bir xil bo'lsa — meni render qilma, oldingi natijani ishlat."

Endi qidiruvda yozilganda MahsulotRoyxati render bo'lmaydi — mahsulotlar o'sha massiv.

Qanday solishtiradi

Har bir prop Object.is bilan (deyarli ===) — sayoz (shallow) solishtiriladi:

Object.is(5, 5)                     // true
Object.is('a', 'a')                 // true
Object.is([1, 2], [1, 2])           // false — har xil massivlar
Object.is({ a: 1 }, { a: 1 })       // false — har xil obyektlar
Object.is(() => {}, () => {})       // false — har xil funksiyalar

Oddiy qiymatlar (son, string, boolean) qiymat bo'yicha; obyekt, massiv va funksiyalar — havola bo'yicha.


memo ni buzadigan narsalar

Bu — memo ishlatishdagi eng katta tuzoq:

function Katalog({ mahsulotlar }) {
  const [qidiruv, setQidiruv] = useState('');

  return (
    <MahsulotRoyxati
      mahsulotlar={mahsulotlar}
      filtr={{ kategoriya: 'kitob' }}           // ❌ har renderda yangi obyekt
      onTanlash={(id) => console.log(id)}       // ❌ har renderda yangi funksiya
      ustunlar={['nomi', 'narx']}               // ❌ har renderda yangi massiv
    />
  );
}

Har renderda yangi obyekt, funksiya va massiv → Object.is → false → memo har safar "props o'zgardi" deydi → har safar render. memo foydasiz bo'ldi, va qo'shimcha solishtirish xarajati qo'shildi.

Yechimlar

// 1. O'zgarmas qiymatlarni komponentdan tashqariga
const FILTR = { kategoriya: 'kitob' };
const USTUNLAR = ['nomi', 'narx'];

function Katalog({ mahsulotlar }) {
  const [qidiruv, setQidiruv] = useState('');

  // 2. Funksiyalar — useCallback (16-dars)
  const onTanlash = useCallback((id) => {
    console.log(id);
  }, []);

  // 3. Hisoblangan obyektlar — useMemo (15-dars)
  const saralangan = useMemo(
    () => [...mahsulotlar].sort((a, b) => a.narx - b.narx),
    [mahsulotlar]
  );

  return (
    <MahsulotRoyxati
      mahsulotlar={saralangan}
      filtr={FILTR}
      ustunlar={USTUNLAR}
      onTanlash={onTanlash}
    />
  );
}

memo, useMemo va useCallback — birgalikda ishlaydigan vositalar. useCallback ning asosiy vazifasi — aynan memo qilingan bolaga barqaror funksiya berish.

children ham prop

<MemoKomponent>
  <p>Matn</p>          {/* har renderda yangi JSX obyekti → memo ishlamaydi */}
</MemoKomponent>

JSX — har renderda yangi obyekt. children qabul qiladigan komponentga memo deyarli foyda bermaydi.


Kartochkalar darajasida

Ro'yxat o'zgaradigan bo'lsa (masalan, filtrlash natijasida), har bir kartochkani memo qilish foydali:

const MahsulotKartochka = memo(function MahsulotKartochka({ mahsulot, onSavatga }) {
  return (
    <article>
      <h3>{mahsulot.nomi}</h3>
      <p>{mahsulot.narx.toLocaleString()} so'm</p>
      <button onClick={() => onSavatga(mahsulot.id)}>Savatga</button>
    </article>
  );
});

Bitta mahsulot "Savatga" qo'shilib, uning obyekti yangilansa — faqat o'sha kartochka render bo'ladi, qolgan 499 tasi emas. Buning uchun onSavatga useCallback bilan, va mahsulot obyektlari o'zgarmas tarzda yangilanishi kerak (o'zgarmagan mahsulotlar — o'sha obyektlar).


O'z solishtirish funksiyangiz

const Grafik = memo(
  function Grafik({ malumot, sarlavha }) { /* ... */ },
  (oldingi, keyingi) =>
    oldingi.malumot.length === keyingi.malumot.length &&
    oldingi.sarlavha === keyingi.sarlavha
);

Ikkinchi argument — true qaytarsa render qilinmaydi. Juda kamdan-kam kerak va xavfli: biror prop ni solishtirishni unutsangiz, komponent eskirgan ma'lumotni ko'rsatadi. Chuqur solishtirish (JSON.stringify, isEqual) esa ko'pincha render dan ham qimmatroq.


Qachon memo kerak

memo — bepul emas: har renderda props larni solishtirish va eski props larni xotirada saqlash.

✅ Foydali:

  • Komponent tez-tez bir xil props bilan render bo'ladi
  • Uning render i sezilarli qimmat (katta ro'yxat, grafik, murakkab hisoblash)
  • Props lar oddiy qiymatlar yoki barqaror havolalar (yoki ularni barqaror qilish oson)

❌ Foydasiz yoki zararli:

  • Kichik, arzon komponentlar — tugma, ikonka, matn
  • Props deyarli har doim o'zgaradi
  • Props ga har renderda yangi obyekt/funksiya beriladi va siz buni tuzatmaysiz
  • children qabul qiladi

[!TIP] Tartib: avval o'lchang (71-dars, React Profiler), keyin tuzilmani yaxshilang (67-dars: state ni pastga tushirish, children), va faqat shundan keyin memo. "Har ehtimolga qarshi" hamma joyga memo qo'yish kodni murakkablashtiradi va ko'pincha hech narsa bermaydi. Agar loyihada React Compiler yoqilgan bo'lsa, u bularning ko'pini avtomatik qiladi.


Muammoga qaytib — yanada yaxshi yechim

Darsning boshidagi qidiruv uchun memo yagona yechim emas:

function Katalog({ mahsulotlar }) {
  return (
    <>
      <Qidiruv />                                  {/* state shu yerda */}
      <MahsulotRoyxati mahsulotlar={mahsulotlar} />  {/* ta'sirlanmaydi */}
    </>
  );
}

Agar qidiruv ro'yxatni filtrlashi kerak bo'lsa (ya'ni ro'yxat qidiruvga bog'liq bo'lsa), memo yordam bermaydi — ro'yxat haqiqatan o'zgaryapti. U holda useDeferredValue (82-dars) yoki virtualizatsiya (70-dars) kerak.


Nima uchun shunday?

Nima uchun memo faqat sayoz solishtiradi — chuqur solishtirish ishonchliroq emasmi?

Chuqur solishtirish ishonchliroq, lekin oldindan aytib bo'lmaydigan darajada qimmat. 10 000 elementli massivni har renderda element-ma-element tekshirish komponentni render qilishdan ham sekinroq bo'lishi mumkin — optimizatsiya teskari natija beradi. Sayoz solishtirish esa doim tez: har bir prop uchun bitta ===. Bu yondashuv React ning boshqa qoidasi bilan uyg'unlashadi: ma'lumotlar o'zgarmas tarzda yangilanadi (9-dars). Agar massiv o'zgargan bo'lsa — u yangi massiv; o'zgarmagan bo'lsa — o'sha massiv. Shu kelishuv bajarilsa, havolani solishtirish mazmunni solishtirish bilan bir xil natija beradi — lekin bir zumda.


Xulosa

Eslab qoling:

  • memo(Komponent) — props o'zgarmasa, ota-ona render bo'lganda ham render qilmaydi
  • Solishtirish — sayoz (Object.is): obyekt, massiv, funksiya — havola bo'yicha
  • Har renderda yangi {}, [], () => {} — memo ni buzadi
  • Yechimlar: konstantalarni tashqariga, useCallback (funksiyalar), useMemo (hisoblangan qiymatlar)
  • children li komponentlarga memo deyarli foydasiz
  • O'z solishtirish funksiyasi — kamdan-kam va ehtiyotkorlik bilan
  • Avval o'lchang, keyin tuzilmani yaxshilang, oxirida memo

Keyingi dars

69-dars: Code Splitting — dynamic import, lazy, Suspense