IlmHamroh
JavaScript Full-stack/18-qism. React chuqur20/42-dars21 daqiqa
Mundarija (35)

memo, useMemo, useCallback: qachon va qanday

Qisqacha: memo(Component) — props avvalgisi bilan bir xil bo'lsa, komponentni qayta chizmaydi (har props alohida, Object.is bilan). Hook useMemo(() => hisob, [a, b]) — a va b o'zgarmaguncha o'sha natijani (o'sha havolani) qaytaradi. useCallback(fn, [a]) — o'sha funksiyani. Uchalasi bitta muammoga xizmat qiladi: JavaScript'da {} !== {}, shuning uchun har renderda yasalgan obyekt va funksiya memo ni buzadi. Ular faqat o'lchangan muammoda kerak. vazifalar da bitta katak 1 000 vazifada 18 056 komponent render berardi. Ro'yxat o'sha obyektlarni qaytaradigan bo'ldi va qator memo ga o'raldi — 68 render, 48 → 32 ms.

Bu darsda

  • Havola tengligi (referential equality) nega memo ning asosiy dushmani ekanini ko'rasiz.
  • memo ni yozasiz va uni buzadigan uchta props turini taniysiz.
  • Custom compare funksiyasi nega xavfli ekanini eskirgan closure misolida ko'rasiz.
  • useMemo ni qimmat hisob va barqaror havola uchun, useCallback ni funksiya props uchun ishlatasiz.
  • vazifalar da qator memo sini kiritib, 18 056 → 68 render natijasini o'lchaysiz va commit qilasiz.

Oldin bilishingiz kerak: Kompozitsiya orqali optimallashtirish, Qayta render sabablari va miflar, useEffect II: dependency massivi, Tenglik va Object.is.

1. Nega bu kerak?

Kompozitsiya darsining oxirida bitta holat qoldi: ro'yxat. vazifalar dagi 1 000 qator bitta massivdan chiziladi. Bitta vazifa o'zgarsa massiv yangi bo'ladi, ro'yxat qayta chiziladi va har qator — "ota chizildi" sababi bilan — u ham. 999 ta o'zgarmagan qatorni ro'yxatdan tashqariga chiqarib bo'lmaydi: ular ro'yxatning o'zi.

Bu yerda kompozitsiya ojiz, lekin memo aynan shu uchun yaratilgan: "props o'sha bo'lsa — chizma". Uni useTransition va useDeferredValue darslarida bir gap bilan ishlatgan edik va to'liq tushuntirishni va'da qilgan edik. Tashxis esa Qayta render sabablari darsida qo'yildi: memo(VazifaQatori) qo'shilsa ham yetmasdi, chunki har renderda yangi Vazifa obyektlari yasalardi. Bugun ikkalasini tuzatamiz.

Hayotiy o'xshatish: Jasur aka har kuni ertalab menyu taxtasini qayta yozadi. Aqlli yondashuv — kechagi taxtaga qarash: "taom va narx o'sha bo'lsa — o'chirmayman". Lekin bitta shart bor: solishtirish "narx yozilgan qog'oz o'sha qog'ozmi?" bo'yicha ketadi, "raqam o'shami?" bo'yicha emas. Har kuni yangi qog'ozga o'sha raqamni yozsangiz — u baribir "yangi" hisoblanadi. memo ning qoidasi ham shu.

2. Havola tengligi

2.1 Primitiv va obyekt

memo, useMemo, useEffect massivi va context — hammasi Object.is bilan solishtiradi (Tenglik va Object.is):

js
console.log(Object.is(35000, 35000)); // true
console.log(Object.is("Osh", "Osh")); // true
console.log(Object.is({ dish: "Osh" }, { dish: "Osh" })); // false
console.log(Object.is([1, 2], [1, 2])); // false
const order = () => "Osh";
console.log(Object.is(order, order)); // true
console.log(Object.is(() => "Osh", () => "Osh")); // false

Son va satr qiymat bo'yicha solishtiriladi. Obyekt, massiv va funksiya esa havola bo'yicha: "bu xotiradagi o'sha bitta obyektmi?" (Havola semantikasi). Mazmuni bir xil ikki obyekt — ikki xil obyekt.

2.2 Render — yangi obyektlar zavodi

Komponent tanasidagi har {…}, […] va () => … — har renderda yangi. Bu JavaScript qoidasi, React'niki emas: funksiya har chaqirilganda ichidagi literallar qaytadan yasaladi.

jsx
function App() {
  const style = { color: "red" };      // har renderda yangi obyekt
  const items = menu.filter(isHot);    // har renderda yangi massiv
  const onPick = () => setDish("Osh"); // har renderda yangi funksiya
  …
}

Odatda bu zararsiz va arzon. Muammo faqat shu qiymat solishtiriladigan joyga tushganda boshlanadi: memo li bolaning props'i, effekt massivi, context qiymati.

3. memo

3.1 Asosiy ishlashi

memo — komponentni o'raydigan funksiya: ota chizilganda React bolaning yangi props'ini eskisi bilan har bir kalit bo'yicha Object.is da solishtiradi. Hammasi teng bo'lsa — bolani chizmaydi, eski natijani ishlatadi. Uchta qator, uchta xil props:

jsx
import { memo, useState } from "react";

const DishRow = memo(function DishRow({ dish, style, onPick }) {
  console.log("DishRow:", dish);
  return (
    <li style={style} onClick={onPick}>
      {dish}
    </li>
  );
});

function App() {
  const [count, setCount] = useState(0);
  console.log("App:", count);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Sanagich</button>
      <ul>
        <DishRow dish="Osh" />
        <DishRow dish="Manti" style={{ color: "red" }} />
        <DishRow dish="Somsa" onPick={() => console.log("Somsa")} />
      </ul>
    </div>
  );
}

Konsolda:

text
App: 0
DishRow: Osh
DishRow: Manti
DishRow: Somsa
App: 1
DishRow: Manti
DishRow: Somsa
Qator Props memo ishladimi Nega
Osh dish="Osh" ha satr — qiymat bo'yicha teng
Manti style={{…}} yo'q har renderda yangi obyekt
Somsa onPick={() => …} yo'q har renderda yangi funksiya

Uchala qator ham memo da. Lekin faqat birinchisi himoyalandi. Ikkinchi va uchinchi qatorda memo foydasiz — undan ham yomoni: React har safar props'ni solishtiradi (bekor ish) va baribir chizadi.

3.2 Uch tuzatish

Muammo Tuzatish
doimiy obyekt (style={{ color: "red" }}) komponentdan tashqarida konstanta: const RED = { color: "red" }
hisoblangan obyekt yoki massiv useMemo
funksiya useCallback yoki funksiyani bolaga bermaslik

Birinchisi eng oddiy va eng ko'p unutiladigan: state'ga bog'liq bo'lmagan obyekt komponentdan tashqarida bir marta yasaladi. Qolgan ikkitasi — keyingi bo'limlarda.

3.3 Custom compare — ehtiyot bo'ling

memo ga ikkinchi argument berish mumkin — o'z solishtirish funksiyangiz: memo(Component, arePropsEqual). true qaytarsa — "teng, chizma". Vasvasa katta: "funksiya props'ni e'tiborsiz qoldiraman, shunda useCallback kerak emas". Natija:

jsx
import { memo, useState } from "react";

const DishButton = memo(
  function DishButton({ dish, onOrder }) {
    console.log("DishButton chizildi");
    return <button onClick={onOrder}>{dish}</button>;
  },
  (prev, next) => prev.dish === next.dish, // ❌ onOrder e'tiborsiz
);

function App() {
  const [discount, setDiscount] = useState(0);
  function order() {
    console.log("buyurtma, chegirma:", discount);
  }
  return (
    <div>
      <button onClick={() => setDiscount(10)}>Aksiya</button>
      <DishButton dish="Osh" onOrder={order} />
    </div>
  );
}

Konsolda:

text
DishButton chizildi
buyurtma, chegirma: 0

«Aksiya» bosildi — chegirma 10. Lekin buyurtma chegirma: 0 bilan ketdi. DishButton qayta chizilmadi, demak uning <button onClick> ida birinchi renderdagi order qoldi — u o'z snapshot'idagi discount = 0 ni ko'radi (State II: snapshot). Bu eskirgan closure (stale closure) — haqiqiy pul xatosi bo'lishi mumkin.

React hujjati (memo, "Specifying a custom comparison function") ham shuni ogohlantiradi: custom compare'da har props'ni, ayniqsa funksiyalarni solishtiring; chuqur tenglik esa sekin va ilovani qotirib qo'yishi mumkin. Amalda custom compare kam kerak — avval props'ni barqaror qiling.

Tekshirib ko'ring: memo li MenuCard ga price={35000} va tags={["achchiq"]} berilgan. Ota qayta chizilsa, MenuCard chiziladimi?

Javob

Chiziladi. price — son, teng. tags — har renderda yangi massiv, Object.is — false. Bitta teng bo'lmagan props yetarli. Tuzatish: tags doimiy bo'lsa — komponentdan tashqarida const SPICY = ["achchiq"]; hisoblangan bo'lsa — useMemo.

4. useMemo

4.1 Qimmat hisobni saqlash

useMemo(hisob, massiv) — birinchi renderda hisob() ni chaqiradi va natijani saqlaydi. Keyingi renderlarda massivdagi qiymatlar (Object.is bilan) o'zgarmagan bo'lsa — hisobni chaqirmaydi, o'sha natijani qaytaradi:

jsx
import { useMemo, useState } from "react";

const MENU = ["Osh", "Manti", "Somsa", "Lag'mon"];
let calls = 0;

function filterMenu(items, query) {
  calls += 1;
  console.log("filterMenu chaqirildi:", calls);
  return items.filter((d) => d.toLowerCase().includes(query));
}

function App() {
  const [query, setQuery] = useState("");
  const [tick, setTick] = useState(0);
  const found = useMemo(() => filterMenu(MENU, query), [query]);
  console.log("App:", tick, found.length);
  return (
    <div>
      <input
        placeholder="Qidiruv"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <button onClick={() => setTick(tick + 1)}>Yangila</button>
      <p>{found.join(", ")}</p>
    </div>
  );
}

Konsolda:

text
filterMenu chaqirildi: 1
App: 0 4
App: 1 4
filterMenu chaqirildi: 2
App: 1 3
App: 2 3

Ekranda:

text
Yangila
Osh, Somsa, Lag'mon

To'rt render, ikki hisob. «Yangila» — query o'zgarmadi, filterMenu chaqirilmadi. Qidiruvga "o" yozildi — query o'zgardi, hisob qayta bajarildi. Haqiqiy loyihada filterMenu 10 000 elementli ro'yxatni aylanib chiqsa, tejash sezilarli bo'ladi.

4.2 Barqaror havola

useMemo ning ikkinchi — va React'da ko'pincha asosiy — vazifasi: havolani barqaror qilish. found — massiv. Uni memo li bolaga bersangiz, useMemo siz u har renderda yangi bo'lardi va memo buzilardi. useMemo bilan esa query o'zgarmaguncha — o'sha massiv.

useDeferredValue darsidagi vazifalar kodida aynan shu sabab yozilgan edi: korinadigan — useMemo, chunki u memo(VazifaRoyxati) ga props bo'lib boradi. Hisobni tejash ikkinchi darajali edi.

4.3 Kafolat emas — optimallashtirish

React hujjati (useMemo, "Caveats") ikki narsani aytadi:

  1. Dev rejimda StrictMode hisob funksiyasini ikki marta chaqiradi — toza bo'lmasa, xato darhol ko'rinsin (StrictMode chuqur).
  2. React keshni ba'zi hollarda tashlab yuborishi mumkin va kelajakda tashlashi ham mumkin. Shuning uchun useMemo ni "faqat bir marta bajarilsin" kafolati sifatida ishlatmang. Bunday kerak bo'lsa — useState(() => …) yoki useRef.

Qoida: useMemo ni olib tashlasangiz, kod sekinlashishi mumkin, lekin buzilmasligi kerak.

Tekshirib ko'ring: const total = useMemo(() => price * qty, [price, qty]); — bu useMemo foydalimi?

Javob

Deyarli yo'q. Ko'paytirish — nanosekundlar, useMemo esa har renderda massivni solishtiradi va keshni saqlaydi: tejashdan ko'ra xarajati katta bo'lishi mumkin. Natija — son (primitiv), havola barqarorligi ham kerak emas. Shunchaki const total = price * qty;.

5. useCallback

5.1 Funksiya uchun useMemo

useCallback(fn, massiv) — massiv o'zgarmaguncha o'sha funksiyani qaytaradi. Aslida bu useMemo(() => fn, massiv) ning qisqa yozuvi. U funksiyani tezlashtirmaydi va "kamroq yaratmaydi" (inline funksiya baribir har renderda yasaladi, faqat qaytarilmaydi). Yagona foydasi — barqaror havola:

jsx
import { memo, useCallback, useState } from "react";

const DishButton = memo(function DishButton({ dish, onOrder }) {
  console.log("DishButton chizildi:", dish);
  return <button onClick={() => onOrder(dish)}>{dish}</button>;
});

function App() {
  const [count, setCount] = useState(0);
  const [orders, setOrders] = useState([]);
  const handleOrder = useCallback((dish) => {
    setOrders((prev) => [...prev, dish]);
  }, []);
  console.log("App:", count, "buyurtma:", orders.length);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Sanagich</button>
      <DishButton dish="Osh" onOrder={handleOrder} />
      <DishButton dish="Manti" onOrder={handleOrder} />
    </div>
  );
}

Konsolda:

text
App: 0 buyurtma: 0
DishButton chizildi: Osh
DishButton chizildi: Manti
App: 1 buyurtma: 0
App: 1 buyurtma: 1

«Sanagich» ham, «Osh» (buyurtma qo'shildi) ham App ni chizdi — lekin tugmalar chizilmadi. handleOrder — o'sha funksiya, dish — o'sha satr.

5.2 Bo'sh massiv va updater

Diqqat qiling: useCallback(…, []) — massiv bo'sh, lekin funksiya baribir eng so'nggi buyurtmalar ro'yxatiga qo'shadi. Sir — updater funksiya: setOrders((prev) => [...prev, dish]) (State II). Funksiya orders ni o'qimaydi — React eng so'nggisini prev qilib beradi. Agar setOrders([...orders, dish]) deb yozsangiz, orders massivga kirishi kerak bo'lardi va funksiya har buyurtmada yangilanardi.

set… funksiyalarining o'zi doim barqaror — ularni massivga yozish shart emas (State I). ESLint'ning react-hooks/exhaustive-deps qoidasi qolganini nazorat qiladi (useEffect II): massivdan biror narsani unutsangiz — eskirgan closure, ortiqcha qo'shsangiz — keraksiz yangilanish.

flowchart TD
  A{"Bola memo'dami?"} -->|"yo'q"| B["useCallback/useMemo foydasiz"]
  A -->|"ha"| C{"Props'da obyekt yoki funksiya?"}
  C -->|"yo'q"| D["memo o'zi yetadi"]
  C -->|"ha"| E{"State'ga bog'liqmi?"}
  E -->|"yo'q"| F["Komponentdan tashqariga"]
  E -->|"ha"| G["useMemo / useCallback"]

Diagrammaning birinchi tuguniga qarang: memo siz bolaga useCallback berish — eng ko'p uchraydigan foydasiz optimallashtirish. Bola baribir chiziladi ("ota chizildi").

6. Qachon kerak emas

6.1 Erta optimallashtirish

Uchala vosita ham bepul emas:

Narx Qayerda
props yoki massiv har renderda solishtiriladi memo, useMemo, useCallback
eski natija xotirada turadi kesh
kod o'qilishi qiyinlashadi, massivlar xatoga sabab eskirgan closure, unutilgan bog'liqlik
bitta yangi obyekt butun zanjirni buzadi ishonch noto'g'ri bo'ladi

Premature optimization (erta optimallashtirish) — o'lchanmagan muammoni "har ehtimolga qarshi" davolash. Natija: murakkab kod, sezilmaydigan foyda. Bu darsdagi barcha misollar console.log bilan o'lchangan; vazifalar dagi qaror esa Event Timing va render soni bilan.

6.2 Qachon kerak

React hujjati (useMemo, "Should you add useMemo everywhere?") va kanon tajribasi bo'yicha — uchta holat:

  1. Hisob haqiqatan qimmat — console.time yoki Profiler 1 ms dan ko'p ko'rsatsa (sekin telefonda bu yana bir necha barobar).
  2. Natija memo li bolaga props bo'lib boradi va u bola qimmat yoki ko'p.
  3. Natija boshqa hook'ning massivida (useEffect, boshqa useMemo) — har renderda yangi bo'lsa, effekt har renderda ishlaydi.

Qolgan hollarda — oddiy kod. Keyingi darsdagi React Compiler bu tanlovni avtomatlashtirishga urinadi; u bilan nima o'zgarishini o'lchaymiz.

7. Vazifalar qadami: qator memo si (holat 20)

7.1 Ikki sabab, ikki tuzatish

Yangi branch: perf/qator-memo. Qayta render sabablari darsidagi tashxis — ikki qism:

  1. VazifaQatori memo siz — "ota chizildi" bilan 1 000 qator chiziladi.
  2. memo qo'shilsa ham, korinadiganVazifalar() har chaqirilganda yangi Vazifa obyektlarini qaytaradi — vazifa props'i doim "yangi".

Ikkinchisi birinchi tuzatiladi — aks holda memo «Uchta qator, uchta xil props» dagi Manti qatoriga o'xshab foydasiz bo'ladi. Diff: 4 fayl, +50 −13.

7.2 holat.ts: o'sha obyektlar

tsx
// Filtr, keyin qidiruv: qidir tartibni saqlaydi.
// 18/#20: natija — ro'yxatdagi O'SHA obyektlar (Vazifa nusxalari
// emas): o'zgarmagan vazifa havolasi o'zgarmaydi — memo'li qator
// (VazifaQatori) uni Object.is bilan solishtirib, qayta chizilmaydi.
// Avval har renderda yangi Vazifa yasalardi va 1 000 qatorning
// hammasi qayta chizilardi (o'lchandi: bitta katak — 18 056
// komponent render, natijalar/18-*)
export function korinadiganVazifalar<T extends VazifaMalumoti>(
  vazifalar: readonly T[],
  filtr: Filtr,
  saralash: Saralash,
  qidiruv: string,
): T[] {
  const asl = new Map(vazifalar.map((v) => [v.id, v]));
  return qidir(
    VazifalarRoyxati.dan(vazifalar).korinadiganlar(filtr, saralash),
    qidiruv,
    // id lar bir xil — doim topiladi
  ).flatMap((v) => asl.get(v.id) ?? []);
}

Izoh qatorlari telefon uchun qayta bo'lindi (oxirgi qatordagi izoh uning ustiga ko'chdi), kod — aynan. Uch narsa:

  • Filtr, saralash va qidiruv mantig'i o'zgarmadi — VazifalarRoyxati klassi (vazifalar v2) ishlayveradi. Faqat oxirida: har natija id bo'yicha asl obyektga almashtiriladi.
  • new Map(…) — id → asl obyekt; qidirish O(1) (Map). flatMap(… ?? []) — topilmasa (bo'lmaydi, lekin TypeScript bilmaydi) — bo'sh massiv, ya'ni tashlab yuboriladi.
  • Generic <T extends VazifaMalumoti> (Generic cheklovlar): qaytgan tur — kirgan tur. Avval Vazifa[] qaytardi, endi — readonly T[] dagi T ning o'zi. Tur ham "o'sha obyekt" ekanini aytadi.

Test ham aynan shuni tekshiradi (tekshiruv/holat.test.js, +1):

js
  test("18/#20 korinadiganVazifalar — ro'yxatdagi O'SHA obyektlar (memo uchun)", () => {
    const natija = korinadiganVazifalar(
      boshlangichVazifalar,
      "hammasi",
      "standart",
      "",
    );
    assert.equal(
      natija.every((v) => boshlangichVazifalar.includes(v)),
      true,
    );
  });

includes — === bilan qidiradi: har natija asl massivdagi o'sha obyekt bo'lishi kerak. Mazmunan teng nusxa testdan o'tmaydi.

7.3 VazifaQatori: memo

tsx
// memo (18/#20): props — vazifa (ro'yxatdagi o'sha obyekt, #20),
// sorov (satr), tahrirda (boolean) — o'zgarmagan qator qayta
// chizilmaydi. Hodisalar context'dan — u barqaror (18/#02),
// memo'ni buzmaydi
export const VazifaQatori = memo(function VazifaQatori({
  vazifa,
  sorov,
  tahrirda,
}: Props) {

Izohda butun tekshiruv ro'yxati: uchta props, uchalasi barqaror. vazifa — o'sha obyekt (yuqoridagi tuzatish), sorov — satr, tahrirda — boolean. Qator hodisalari props'da emas, context'da — u esa #02 dan beri barqaror ("latest ref", useDeferredValue). Ya'ni «Uchta qator, uchta xil props» dagi uchala xavf ham yopilgan.

memo(function VazifaQatori(…)) — nomli funksiya: DevTools va xato izlarida "Anonymous" emas, VazifaQatori ko'rinadi.

7.4 App.tsx: hosilaviy qiymatlar useMemo da

tsx
  const royxat = useMemo(
    () => VazifalarRoyxati.dan(vazifalar),
    [vazifalar],
  );
  …
  // 18/#20: ro'yxatdan hisoblanadigan hamma narsa — faqat
  // ro'yxat o'zgarganda (qidiruvda har harfda emas). Teglar
  // Trie'si — O(T · l)
  const { qoldi, bajarildi, teglar, yopilgan, daraxt } = useMemo(() => {
    const teglar = royxat.tegHisobi();
    return {
      ...royxat.statistika(),
      teglar,
      yopilgan: royxat.yopilganTeglar(),
      daraxt: tegDaraxtiniQur(teglar),
    };
  }, [royxat]);

Bu — «Qimmat hisobni saqlash» holati. Statistika, teg hisobi va teglar daraxti (Trie, Trie) — 10 000 vazifada sezilarli ish. Ular faqat vazifalar ga bog'liq, lekin avval har renderda — qidiruvning har harfida ham — qayta hisoblanardi. Endi — faqat ro'yxat o'zgarganda. yopilgan={royxat.yopilganTeglar()} props'i ham shu obyektga ko'chdi.

7.5 O'lchov

O'sha olchov/renderlar.mjs, 1 000 vazifa (natijalar/20-renderlar.txt, holat 12 bilan bir xil sharoit):

Bitta katak, 1 000 vazifa: komponent renderlari
  • holat 12: memo faqat ro'yxatdaVazifaQatori×300018 056 render
  • holat 20: asl obyektlar + memo(VazifaQatori)VazifaQatori×068 render

Manba: O'lchandi: vazifalar holat 12 va 20, olchov/renderlar.mjs, Chrome headless, soxta server, 2026-10-13

Ssenariy Holat 12 Holat 20
katak 18 056 68
yangi vazifa qo'shish 18 068 68
qidiruv: "vazifa 12" (9 harf) 237 927
qidiruv: bitta harf "v" 6 035 6 035
e'lon o'chishi 17 17

Katakda 68 render qoldi — App, XatoChegarasi, StatistikaPaneli, forma, filtrlar va o'zgargan qatorning o'zi (AmalTugmasi×4). 999 qator — nol.

Qidiruvdagi o'sish (237 → 927) ham bor. Qatorlar soniga qarang: holat 12 da VazifaQatori×11, holat 20 da ×123. Ro'yxatdagi 1 000 vazifadan "vazifa 12" ga 11 tasi mos keladi, "vazifa 1" ga esa 112 tasi (1, 10–19, 100–199, 1000). 123 = 112 + 11 — juda mos keladi.

Bizning tushuntirishimiz shunday. Holat 20 dagi o'lchovda tez yozish paytida oraliq so'rov "vazifa 1" ham ekranga chiqdi. Holat 12 dagisida esa React uni tashlab, darhol yakuniy so'rovga o'tdi (useDeferredValue). Lekin bu — taxmin, isbot emas. Xom faylda faqat jami son bor, qaysi so'rov commit bo'lgani yozilmagan. Aniq bilish uchun har commit'dagi so'rovni ham yozib olish kerak edi. Shuning uchun bu raqamdan "memo qidiruvni sekinlashtirdi" degan xulosa chiqarmang. Qidiruvning eng og'ir holati ("v" — 6 035) esa o'zgarmadi: har qatorda belgilangan matn haqiqatan o'zgaradi, memo buni to'xtatmaydi va to'xtatmasligi kerak.

Vaqt (Event Timing, bosishdan kadrgacha, 20-oldin.txt → 20-keyin.txt):

Katak bosilishi: bosishdan keyingi kadrgacha (mediana)
  • 1 000 — holat 1248 ms
  • 1 000 — holat 2032 ms
  • 10 000 — holat 12264 ms
  • 10 000 — holat 20120 ms

Manba: O'lchandi: vazifalar holat 12 va 20, olchov/katak-tezligi.mjs (Event Timing), Chrome headless, 1 000 vazifada 18 bosish, 10 000 da 12 bosish, 2026-10-13

10 000 vazifada 264 → 120 ms: INP "yaxshi emas" dan (200 ms dan ko'p) "yaxshi" ga o'tdi. Qolgan 120 ms — 10 000 elementli DOM'ning o'zi va App dagi qolgan hisoblar; ular Virtualizatsiya darsida.

7.6 Tekshiruvlar va commit

Testlar: node 164 (+1 — «o'sha obyektlar» testi), Vitest 39; lint, format:check, tip — toza (natijalar/tekshiruv-20.txt). Brauzer: 159/159, e2e-18 36/36. Build: index JS 254.86 KB (gzip 80.71 KB) — memo va useMemo hajmga deyarli ta'sir qilmaydi.

text
perf: qator memo — ro'yxatdagi o'sha obyektlar, hosilaviy useMemo

Commit tanasida: korinadiganVazifalar — asl obyektlar (Map + flatMap, generic); memo(VazifaQatori); royxat va hosilaviy qiymatlar — useMemo([vazifalar]); o'lchov: katak 18 056 → 68 render, 1 000: 48 → 32 ms, 10 000: 264 → 120 ms.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
memo bor, bola baribir chiziladi props'da inline obyekt, massiv yoki funksiya konstanta, useMemo, useCallback
useCallback bor, foyda yo'q bola memo siz memo qo'shing yoki useCallback ni olib tashlang
tugma eski qiymat bilan ishlaydi custom compare funksiyani e'tiborsiz qoldirdi hamma props'ni solishtiring yoki compare'siz
useCallback har renderda yangilanadi massivda har renderda yangi qiymat updater funksiya, massivni qisqartirish
ro'yxat memo da, qatorlar baribir chiziladi map da yangi obyektlar ({ ...v }, klass nusxasi) asl obyektlarni bering
useMemo ichida fetch yoki setState hisob — toza bo'lishi kerak effekt yoki hodisa

9. Mashqlar

Mashqlar kurs/mashqlar/18/20-memo/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): teng yoki yo'q?

memo uchun props teng hisoblanadimi (ota qayta chizilganda)?

  • count={5} → count={5}:
  • user={{ name: "Ali" }} (JSX ichida) → o'sha yozuv:
  • onClick={handleClick} (handleClick — komponent ichida oddiy function):
  • items={MENU} (MENU — modul darajasidagi massiv):
Yechim

Son — qiymat bo'yicha teng. JSX ichidagi {{ … }} — har renderda yangi obyekt. Komponent ichidagi function handleClick ham har renderda yangidan e'lon qilinadi — yangi funksiya. Modul darajasidagi MENU esa bir marta yasalgan — o'sha havola.

2-mashq (o'rta): rangli qator

PriceTag — memo da, lekin «Sanagich» har bosilganda chizilyapti. Avval qaysi props "yangi" ekanini toping, keyin tuzating (useMemo siz, useCallback siz): «Sanagich» PriceTag ni chizmasin. Kutilgan konsol — pastda.

jsx
import { memo, useState } from "react";

const PriceTag = memo(function PriceTag({ price, style }) {
  console.log("PriceTag chizildi");
  return <p style={style}>{price} so'm</p>;
});

function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Sanagich</button>
      <PriceTag price={35000} style={{ fontWeight: "bold" }} />
    </div>
  );
}

Konsolda:

text
PriceTag chizildi
PriceTag chizildi
PriceTag chizildi
Yechim
jsx
import { memo, useState } from "react";

const BOLD = { fontWeight: "bold" };

const PriceTag = memo(function PriceTag({ price, style }) {
  console.log("PriceTag chizildi");
  return <p style={style}>{price} so'm</p>;
});

function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Sanagich</button>
      <PriceTag price={35000} style={BOLD} />
    </div>
  );
}

Konsolda:

text
PriceTag chizildi

Bitta joy yetdi: stil obyekti komponentdan tashqariga, BOLD konstantasiga chiqdi — u state'ga bog'liq emas. price={35000} — son, u allaqachon teng edi. memo ni ham, price ni ham o'zgartirish kerak emas. Doim avval qaysi props "yangi" ekanini aniqlang.

3-mashq (qiyin): savat va barqaror ishlovchi

CartItem — memo da. App da savat (items state'i) va remove(id) funksiyasi. Hozir bitta taom o'chirilganda qolgan hamma qatorlar qayta chiziladi. Shunday qiling: o'chirishda qolgan qatorlar chizilmasin, «Sanagich» da esa hech qaysi qator chizilmasin. Ishora: useCallback + updater funksiya («Bo'sh massiv va updater» bo'limi).

jsx
import { memo, useState } from "react";

const CartItem = memo(function CartItem({ item, onRemove }) {
  console.log("CartItem:", item.dish);
  return (
    <li>
      {item.dish}{" "}
      <button onClick={() => onRemove(item.id)}>×</button>
    </li>
  );
});

function App() {
  const [items, setItems] = useState([
    { id: 1, dish: "Osh" },
    { id: 2, dish: "Manti" },
    { id: 3, dish: "Somsa" },
  ]);
  const [count, setCount] = useState(0);
  function remove(id) {
    setItems(items.filter((i) => i.id !== id));
  }
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Sanagich</button>
      <ul>
        {items.map((item) => (
          <CartItem key={item.id} item={item} onRemove={remove} />
        ))}
      </ul>
    </div>
  );
}
Yechim
jsx
import { memo, useCallback, useState } from "react";

const CartItem = memo(function CartItem({ item, onRemove }) {
  console.log("CartItem:", item.dish);
  return (
    <li>
      {item.dish}{" "}
      <button onClick={() => onRemove(item.id)}>×</button>
    </li>
  );
});

function App() {
  const [items, setItems] = useState([
    { id: 1, dish: "Osh" },
    { id: 2, dish: "Manti" },
    { id: 3, dish: "Somsa" },
  ]);
  const [count, setCount] = useState(0);
  const remove = useCallback((id) => {
    setItems((prev) => prev.filter((i) => i.id !== id));
  }, []);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Sanagich</button>
      <ul>
        {items.map((item) => (
          <CartItem key={item.id} item={item} onRemove={remove} />
        ))}
      </ul>
    </div>
  );
}

Konsolda:

text
CartItem: Osh
CartItem: Manti
CartItem: Somsa

Ekranda:

text
Sanagich
Manti ×
Somsa ×

Birinchi chizishdan keyin — bironta ham CartItem logi yo'q: «Sanagich» da remove o'sha funksiya, item lar o'sha obyektlar. Birinchi «×» (Osh) bosilganda Osh o'chdi, Manti va Somsa chizilmadi: filter yangi massiv qaytaradi, lekin ichidagi qolgan obyektlar — o'sha. Bu vazifalar dagi «o'sha obyektlar» qoidasining kichik nusxasi. prev — eng so'nggi savat, shuning uchun massiv bo'sh va funksiya hech qachon eskirmaydi.

4-mashq: vazifalar qadami

perf/qator-memo branch'ida «Vazifalar qadami» dagi to'rt o'zgarishni kiriting. Ular: korinadiganVazifalar (asl obyektlar, generic), memo(VazifaQatori), royxat va hosilaviy qiymatlar useMemo da, holat.test.js dagi yangi test. npm test, npm run lint, npm run tip toza bo'lsin. React DevTools Profiler bilan bitta katakni yozib oling — commit va PR.

Yechim

Testlar: node 164, Vitest 39. Profiler'da katak bosilganda flamegraph'da VazifaRoyxati sariq (chizildi), uning ostidagi 1 000 qatordan faqat bittasi rangli, qolganlari kulrang ("Did not render"). Agar hammasi rangli bo'lsa — "Why did this render?" ni o'qing: "• Props changed:" va yonida vazifa — korinadiganVazifalar hali nusxa qaytaryapti; "• Context changed" — qator context'i barqaror emas (#02 dagi "latest ref" tekshiring). Commit xabari — «Tekshiruvlar va commit» bo'limida.

10. Real ishda

  • Jadvallar va ro'yxatlar: admin panel, CRM, chat — memo li qator + barqaror ma'lumot obyektlari — eng ko'p uchraydigan haqiqiy foydali memo.
  • State kutubxonalari: Redux va Zustand "o'zgarmagan qism — o'sha havola" qoidasiga qurilgan (immutable yangilash) — memo ular bilan tabiiy ishlaydi.
  • Kod ko'rigi: "bu useCallback qaysi memo li bolaga ketyapti?" — javob bo'lmasa, uni olib tashlash taklif qilinadi.
  • Intervyu savollari: "memo, useMemo, useCallback farqi?", "Nega memo ishlamay qoldi?", "useCallback funksiyani tezlashtiradimi?", "Hamma joyga memo qo'yish yaxshimi?".

Xulosa

  • Object.is: primitiv — qiymat bo'yicha, obyekt/massiv/funksiya — havola bo'yicha; render har safar yangi literallar yasaydi.
  • memo — props'ni kalitma-kalit solishtiradi; bitta inline obyekt yoki funksiya uni foydasiz qiladi. Custom compare funksiyalarni e'tiborsiz qoldirsa — eskirgan closure.
  • useMemo — qimmat hisob va barqaror havola uchun; kafolat emas, optimallashtirish. useCallback — funksiya uchun useMemo, faqat memo li bola yoki hook massivi uchun kerak.
  • Tartib: o'lchov → kompozitsiya → memo + barqaror props. Erta optimallashtirish — murakkablik, foydasiz.
  • vazifalar holat 20: asl obyektlar + memo(VazifaQatori) + hosilaviy useMemo — katak 18 056 → 68 render, 1 000: 48 → 32 ms, 10 000: 264 → 120 ms.

Keyingi dars: React Compiler: avtomatik memoizatsiya — kompilyator bularni o'zi qo'ysa-chi? vazifalar da o'lchab ko'ramiz va nega kanonga kirmaganini bilamiz.

Manbalar

  • React: memo — react.dev/reference/react/memo
  • React: useMemo — react.dev/reference/react/useMemo
  • React: useCallback — react.dev/reference/react/useCallback
  • Kanon: sinov/18/QADAMLAR.md (#20), natijalar/20-renderlar.txt, natijalar/20-oldin.txt, natijalar/20-keyin.txt, natijalar/tekshiruv-20.txt
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
memo, useMemo, useCallback: qachon va qanday — IlmHamroh