Mundarija (24)
- Bu darsda
- 1. Nega bu kerak?
- 2. localStorage ning cheklovlari
- 2.1 Faqat matn: JSON nimani yo'qotadi
- 2.2 Rasm: base64 va +33%
- 2.3 Sinxronlik — kuchi ham, zaifligi ham
- 2.4 sessionStorage va boshqa tab
- 3. idb-keyval: kalit va qiymat
- 3.1 Eng kichik IndexedDB o'rami
- 3.2 React'ga ulash: kanondagi profil rasmi
- 4. Dexie: jadvallar, so'rovlar, migratsiya
- 4.1 Jadval va indeks
- 4.2 Versiya va migratsiya
- 4.3 useLiveQuery — baza o'zgarsa, ekran yangilanadi
- 5. Kanon o'lchovi: qaysi ma'lumot qayerda
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): qayerga?
- 2-mashq (o'rta): useLocalStorage hook
- 3-mashq (qiyin): o'z migratsiyangiz
- 4-mashq (tahlil): idb-keyval yoki Dexie?
- 8. Real ishda
- Xulosa
- Manbalar
IndexedDB React'da: idb-keyval va Dexie bilan brauzerda ma'lumot saqlash
Qisqacha:
localStorage— sinxron, faqat matn, ~5 MB: kichik sozlamalar va qoralamalar uchun. Rasm (Blob), minglab yozuv yoki qidiriladigan ma'lumot — IndexedDB ga. Uning xom API'si noqulay, shuning uchun ikki kutubxona: idb-keyval — "kalit → qiymat" (get/set), Dexie — jadvallar, indekslar, so'rovlar, versiya migratsiyasi va React uchunuseLiveQuery. Kanon o'lchovi (10 000 vazifa, Chrome 155):localStorageo'qish 1.7 ms, idb-keyval 6.5 ms, DexietoArray135.8 ms. Shuning uchunvazifalarro'yxatilocalStorageda qoladi, IndexedDB — faqat profil rasmi uchun.
Bu darsda
localStoragening to'rt cheklovini ko'rasiz: matn, JSON yo'qotishlari, kvota va sinxronlik.- idb-keyval bilan Blob,
DatevaMapni saqlaysiz va React'gauseSyncExternalStorebilan ulaysiz. - Dexie'da jadval va indeks e'lon qilasiz,
wherebilan so'raysiz,useLiveQuerybilan ekranni avtomatik yangilaysiz. - Sxema versiyasini oshirib, eski yozuvlarni migratsiya qilasiz.
- Boshqa tabdagi o'zgarishni (
storagehodisasi) ushlaysiz. - Qaysi ma'lumot qayerda saqlanishini o'lchov bilan tanlaysiz.
Oldin bilishingiz kerak: localStorage va sessionStorage, IndexedDB va brauzer xotira kvotasi, Zustand II, useSyncExternalStore.
1. Nega bu kerak?
Jasur aka «Bahor» ofitsiantlari uchun planshet ilovasini xohlaydi. Zalda Wi-Fi ba'zan uziladi. Ofitsiant buyurtmani baribir qabul qilishi kerak — buyurtmalar brauzerda saqlanib, internet qaytganda serverga ketsin. Har buyurtmaga stol rasmi ham biriktiriladi.
Sardor hammasini localStorage ga yozmoqchi edi. Birinchi kunning o'zida uch muammo chiqdi: rasm localStorage ga sig'madi; buyurtma vaqti (Date) qayta o'qilganda oddiy satrga aylandi; 2 000 ta buyurtmadan keyin har saqlashda ekran bir lahza "qotadigan" bo'ldi.
localStorage va IndexedDB darslarida ikkala saqlash joyining xom API'sini o'rgandik. Bugun savol boshqa: React ilovada ularni qanday ishlatish, qaysi kutubxona bilan va qachon. Hayotiy o'xshatish: cho'ntak daftarcha va arxiv shkafi. Daftarcha (localStorage) doim qo'l ostida — ochasiz-u, o'qiysiz; lekin unga faqat yozuv sig'adi va varaqlari kam. Arxiv (IndexedDB) — hamma narsa sig'adi, papkalar va ko'rsatkichlar bor; lekin kerakli hujjatni so'rab, bir oz kutasiz.
2. localStorage ning cheklovlari
2.1 Faqat matn: JSON nimani yo'qotadi
localStorage faqat satr saqlaydi. Obyektni JSON.stringify bilan matnga aylantiramiz — va ba'zi narsalar yo'lda yo'qoladi. Ishga tushiring:
const order = {
createdAt: new Date(2026, 9, 14, 12, 30),
items: new Map([["osh", 2]]),
note: undefined,
table: 4,
};
const restored = JSON.parse(JSON.stringify(order));
console.log(typeof restored.createdAt); // string
console.log(restored.items); // {}
console.log("note" in restored); // false
console.log(restored.table); // 4Sana — satrga, Map — bo'sh obyektga aylandi, undefined maydon butunlay yo'qoldi. Faqat oddiy son va satrlar sog' qaytdi. Structured clone (Web Worker darsida ko'rgan algoritm) esa bularning hammasini saqlaydi — IndexedDB aynan shu algoritm bilan yozadi:
const order = {
createdAt: new Date(2026, 9, 14, 12, 30),
items: new Map([["osh", 2]]),
};
const copy = structuredClone(order);
console.log(copy.createdAt instanceof Date); // true
console.log(copy.items.get("osh")); // 22.2 Rasm: base64 va +33%
Rasm — ikkilik ma'lumot (Blob). localStorage ga uni faqat base64 satrga aylantirib qo'yish mumkin. Base64 har 3 baytni 4 ta belgi bilan yozadi:
const bytes = new Uint8Array(300 * 1024); // 300 KB "rasm"
let binary = "";
for (const b of bytes) binary += String.fromCharCode(b);
const base64 = btoa(binary);
console.log(bytes.length); // 307200
console.log(base64.length); // 409600
console.log((base64.length / bytes.length).toFixed(2)); // 1.33300 KB rasm — 400 KB matn. Chrome'da bitta sayt uchun localStorage ~5 MB (aniqrog'i, kalit va qiymatlar uzunligi bo'yicha), ya'ni 12 ta shunday rasm — va joy tugadi. Tugaganda nima bo'lishini Chrome 155 da sinadik (6 MB satr yozildi):
QuotaExceededError
Failed to execute 'setItem' on 'Storage': Setting the value of 'katta' exceeded the quota.Tarjimasi: "Storage da setItem bajarilmadi: katta qiymati kvotadan oshdi." Bu istisno: try/catch siz setItem ilovani yiqitadi. localStorage ga har yozuv try ichida bo'lishi kerak (localStorage darsidagi qoida).
2.3 Sinxronlik — kuchi ham, zaifligi ham
localStorage.getItem sinxron: natija shu qatorning o'zida, await siz. Bu — ham yutuq, ham xavf:
- Yutuq: ilova birinchi chizishdan oldin saqlangan holatni o'qiy oladi. Zustand II dagi
persistaynan shuning uchun birinchi kadrdayoq to'g'ri sozlama beradi. - Xavf: katta ma'lumotda o'qish ham, yozish ham asosiy oqimni band qiladi — shu vaqt ichida bosish, yozish, animatsiya to'xtaydi.
Qanchalik katta? Raqamlar — «Kanon o'lchovi» bo'limida.
2.4 sessionStorage va boshqa tab
sessionStorage — xuddi shunday API, lekin tab yopilguncha yashaydi va har tabning o'zi uchun alohida. To'lov oynasidagi vaqtinchalik qadam, "bu tabda ko'rsatilgan e'lonni qayta ko'rsatma" kabi narsalar uchun. Kanonda u vazifalar:tarix (Orqaga tugmasi uchun tarix chuqurligi, React Router III) da ishlatiladi.
localStorage esa saytning hamma tablari uchun umumiy. Bir tab yozsa, boshqalariga storage hodisasi keladi (yozgan tabning o'ziga — kelmaydi). Zustand II da kanon shu hodisa bilan sozlamalarni qayta o'qiydi. Kutubxonasiz React hook'i qanday bo'lishini 2-mashqda yozasiz.
Tekshirib ko'ring: Ofitsiant planshetida «oxirgi tanlangan zal» (bir so'z), «yozilayotgan buyurtma qoralamasi» (shu tab uchun) va «stol rasmlari» saqlanadi. Har biri qayerga?
Javob
Zal — localStorage (kichik matn, keyingi safar ham kerak, birinchi kadrda o'qiladi). Qoralama — sessionStorage (faqat shu tab va shu sessiya; ikkinchi tabda boshqa buyurtma yozilishi mumkin). Rasmlar — IndexedDB (Blob, katta, base64'siz).
3. idb-keyval: kalit va qiymat
3.1 Eng kichik IndexedDB o'rami
IndexedDB darsida xom API'ni ko'rdik: open, upgradeneeded, tranzaksiya, onsuccess. Sizga faqat "kalit bo'yicha qiymat saqlash" kerak bo'lsa — bularning hammasi ortiqcha. idb-keyval — Jake Archibald yozgan kichik kutubxona: localStorage ga o'xshash API, lekin IndexedDB ustida va async. Kanondagi versiya — 6.3.0.
Saytdagi natija oynasida IndexedDB ishlamaydi (sandbox'li iframe — shaffof origin). Quyidagi kodni idb-keyval 6.3.0 bilan Chrome 155 da ishga tushirdik:
import { get, keys, set } from "idb-keyval";
async function main() {
const photo = new Blob([new Uint8Array(300 * 1024)], {
type: "image/webp",
});
await set("bahor:rasm", photo);
await set("bahor:buyurtma", {
createdAt: new Date(2026, 9, 14, 12, 30),
items: new Map([["osh", 2]]),
});
const saved = await get("bahor:rasm");
console.log(saved instanceof Blob, saved.type, saved.size);
const order = await get("bahor:buyurtma");
const isDate = order.createdAt instanceof Date;
console.log(isDate, order.items.get("osh"));
console.log(JSON.stringify(await keys()));
try {
await set("bahor:funksiya", { pay: () => "to'landi" });
} catch (error) {
console.log(error.name);
}
}
main();true image/webp 307200
true 2
["bahor:buyurtma","bahor:rasm"]
DataCloneError- Blob o'zi saqlandi: turi va hajmi (307 200 bayt) bilan. Base64 yo'q, +33% yo'q.
Date—Datebo'lib,Map—Mapbo'lib qaytdi (structured clone).keys()— barcha kalitlar.- Funksiyali obyekt —
DataCloneError: structured clone funksiyani nusxalay olmaydi. JSON jim tashlab yuborardi, IndexedDB esa ochiq xato beradi.
API: get, set, del, update(kalit, (eski) => yangi) (o'qish va yozish bitta tranzaksiyada), keys, values, entries, clear, createStore (alohida baza / object store nomi bilan). Hammasi promise qaytaradi.
Zustand persist ni IndexedDB'ga ulash. Zustand II da createJSONStorage(() => sessionStorage) ni ko'rgan edik. U getItem/setItem/removeItem li har qanday saqlash joyini (storage) oladi — async bo'lsa ham. idb-keyval bilan (Zustand hujjatidagi "custom storage" namunasi asosida):
import { del, get, set } from "idb-keyval";
import { createJSONStorage } from "zustand/middleware";
export const idbStorage = createJSONStorage(() => ({
getItem: async (name) => (await get(name)) ?? null,
setItem: (name, value) => set(name, value),
removeItem: (name) => del(name),
}));
// persist(..., { name: "bahor:savat", storage: idbStorage })Narxi — async tiklanish: store birinchi kadrda boshlang'ich qiymat bilan chiziladi, saqlangan holat esa bir lahzadan keyin keladi. localStorage dagi sinxron tiklanish (birinchi kadrdayoq to'g'ri qiymat) yo'qoladi. Shuning uchun kanonda sozlamalar localStorage da qoldi.
3.2 React'ga ulash: kanondagi profil rasmi
idb-keyval'ning React hook'i yo'q va kerak ham emas: IndexedDB — tashqi manba, ulash usuli bizga useSyncExternalStore dan tanish. Kanonda profil rasmi aynan shunday ishlaydi (profil-rasmi.ts, holat 36 — fayl yuklash UI darsida to'liq ko'ramiz, Fayl yuklash UI). Bugungi mavzuga tegishli qismi (kanon, izohlar qisqartirilgan):
import { useSyncExternalStore } from "react";
import { del, get, set } from "idb-keyval";
const KALIT = "vazifalar:profil-rasmi";
let joriyUrl: string | null = null;
let yuklandi = false;
const tinglovchilar = new Set<() => void>();
function almashtir(blob: Blob | undefined): void {
if (joriyUrl !== null) {
URL.revokeObjectURL(joriyUrl); // eski Blob xotiradan bo'shasin
}
joriyUrl = blob === undefined ? null : URL.createObjectURL(blob);
tinglovchilar.forEach((t) => t());
}
function obuna(tinglovchi: () => void): () => void {
tinglovchilar.add(tinglovchi);
if (!yuklandi) {
yuklandi = true;
// Birinchi obunachi — IndexedDB'dan (async; yo'q yoki xato —
// rasmsiz). Promise.resolve().then: IndexedDB umuman yo'q
// bo'lsa get() SINXRON tashlaydi — u ham rad etilgan Promise
// bo'lsin, menyu yiqilmasin (Vitest/jsdom topdi)
Promise.resolve()
.then(() => get<Blob>(KALIT))
.then(almashtir, (xato: unknown) => {
console.warn(`Profil rasmi o'qilmadi: ${String(xato)}`);
});
}
return () => tinglovchilar.delete(tinglovchi);
}
/** blob: URL yoki null (rasm yo'q) */
export function useProfilRasmi(): string | null {
return useSyncExternalStore(
obuna,
() => joriyUrl,
() => null,
);
}
export async function rasmniSaqla(blob: Blob): Promise<void> {
await set(KALIT, blob);
almashtir(blob);
}Qatorma-qator nimani o'rgatadi:
- Dangasa o'qish. IndexedDB birinchi obunachi paydo bo'lganda o'qiladi (
yuklandi), modul yuklanishida emas. Rasm kerak bo'lmagan sahifa bazani ochmaydi. - Async — demak birinchi kadrda rasm yo'q.
getSnapshotboshidanullberadi, rasm o'qilgach —blob:URL. Profil rasmi uchun bu maqbul (avatar bir lahzadan keyin paydo bo'ladi). Ro'yxat uchun esa — yo'q: bo'sh ro'yxat → keyin to'la ro'yxat — miltillash va CLS. URL.createObjectURL— Blob'gablob:manzil beradi,<img src>ga qo'yiladi. Eski URLrevokeObjectURLbilan bo'shatiladi, aks holda Blob xotirada qoladi. CSP'gaimg-src 'self' blob:qo'shilgani shundan — sababini Fayl yuklash UI darsida batafsil ko'ramiz.- Haqiqiy topilma. Vitest muhitida (jsdom)
indexedDBumuman yo'q. Shundaget()promise qaytarish o'rniga sinxron tashlar edi va butun foydalanuvchi menyusi yiqilardi.Promise.resolve().then(() => get(…))sinxron xatoni ham rad etilgan promise'ga aylantiradi. Xuddi shu holat haqiqiy hayotda ham bor: Firefox'ning ba'zi maxfiy rejimlari va o'chirilgan sayt ma'lumotlari.
Tekshirib ko'ring: Nega
getServerSnapshot(uchinchi argument)() => null?
Javob
Serverda (SSR) IndexedDB ham, Blob URL ham yo'q. Server rasmsiz chizadi. Jonlantirishda React null bilan solishtiradi — mos keladi. Keyin klient rasm o'qilgach yangilaydi. vazifalar da SSR yo'q, lekin hook to'g'ri yozilgan: kelajakda Next.js'ga ko'chsa, hydration xatosi bermaydi (Zustand II dagi getServerSnapshot mantig'i).
4. Dexie: jadvallar, so'rovlar, migratsiya
4.1 Jadval va indeks
Ofitsiant planshetiga "arzon taomlar", "7-stolning buyurtmalari", "to'lanmaganlar" kabi so'rovlar kerak. idb-keyval'da buning uchun hammasini o'qib, JavaScript'da filter qilasiz. Dexie — IndexedDB ustidagi to'liq qatlam: jadvallar (object store), indekslar va so'rovlar. Tajribadagi versiya — dexie 4.4.6, React hook'lari — dexie-react-hooks 4.4.0.
Sxema bitta satr bilan e'lon qilinadi: "++id, price" — birinchisi asosiy kalit (++ — avtomatik o'suvchi), qolganlari — indekslangan maydonlar. Indekslanmagan maydonlar ham saqlanadi, lekin ular bo'yicha where qilib bo'lmaydi.
4.2 Versiya va migratsiya
Ilova o'sadi, sxema o'zgaradi. Zustand II da persist ning version + migrate ini ko'rdik; IndexedDB'da g'oya o'sha, faqat brauzer darajasida: baza versiyasi oshsa, upgradeneeded (IndexedDB) ishga tushadi. Dexie uni version(n).stores(...).upgrade(fn) ga o'raydi. Quyidagi kod ikki "relizni" ketma-ket taqlid qiladi — dexie 4.4.6, Chrome 155:
import Dexie from "dexie";
async function main() {
// 1-versiya: taomlar jadvali, indeks — narx
const v1 = new Dexie("bahor");
v1.version(1).stores({ dishes: "++id, price" });
await v1.dishes.bulkAdd([
{ name: "Osh", price: 35000 },
{ name: "Manti", price: 30000 },
{ name: "Ko'k choy", price: 5000 },
]);
const cheap = await v1.dishes.where("price").below(31000).toArray();
console.log("arzon:", cheap.map((d) => d.name).join(", "));
v1.close();
// 2-versiya (keyingi reliz): yangi indeks, eskilarini ko'chirish
const v2 = new Dexie("bahor");
v2.version(1).stores({ dishes: "++id, price" });
v2.version(2)
.stores({ dishes: "++id, price, category" })
.upgrade(async (tx) => {
const count = await tx
.table("dishes")
.toCollection()
.modify((dish) => {
dish.category = dish.price > 10000 ? "taom" : "ichimlik";
});
console.log("migratsiya: ko'chirildi", count);
});
const drinks = await v2.dishes
.where("category")
.equals("ichimlik")
.toArray();
console.log("ichimliklar:", drinks.map((d) => d.name).join(", "));
console.log("versiya:", v2.verno);
}
main();arzon: Ko'k choy, Manti
migratsiya: ko'chirildi 3
ichimliklar: Ko'k choy
versiya: 2where("price").below(31000)— indeks bo'yicha so'rov: baza hamma yozuvni o'qimaydi, indeksdan kerakli oraliqni oladi. Natija indeks tartibida (narx o'sishi bo'yicha).- 2-versiyada
categoryindeksi qo'shildi vaupgradeeski uchta yozuvga kategoriya yozdi. Bu funksiya faqat bir marta — eski versiyali baza birinchi ochilganda — ishlaydi. version(1)2-versiyali kodda ham qoladi: Dexie versiyalar tarixini ko'radi (eski versiyalarni o'chirish mumkin, agar ular faqatstoresbo'lsa,upgradesiz).
Qoida o'sha: sxema o'zgardi — versiya oshdi — eski ma'lumot ko'chirildi. Ko'chirish unutilsa, eski foydalanuvchida yangi indeks bo'yicha so'rov bo'sh qaytadi (eski yozuvlarda category yo'q).
4.3 useLiveQuery — baza o'zgarsa, ekran yangilanadi
Dexie'ning React uchun eng kuchli imkoniyati — jonli so'rov: useLiveQuery(() => db.items.toArray()). Komponent so'rov natijasini oladi va so'rovga tegishli jadval har qanday joydan o'zgarsa (boshqa komponent, boshqa tab, servis ishchisi), qayta chiziladi. Chrome 155, dexie-react-hooks 4.4.0 — «Osh» ikki marta bosildi, keyin sahifa yangilandi:
import Dexie from "dexie";
import { useLiveQuery } from "dexie-react-hooks";
const db = new Dexie("bahor-savat");
db.version(1).stores({ items: "++id, name" });
function Cart() {
const items = useLiveQuery(() => db.items.toArray());
if (items === undefined) {
console.log("Cart: yuklanmoqda");
return <p>Yuklanmoqda…</p>;
}
console.log("Cart:", items.length);
const names = items.map((item) => item.name).join(", ");
return <p>Savat: {names || "bo'sh"}</p>;
}
function AddButton() {
// komponent store'ni bilmaydi: bazaga yozadi, xolos
return (
<button onClick={() => db.items.add({ name: "Osh" })}>Osh</button>
);
}
export default function App() {
return (
<>
<Cart />
<AddButton />
</>
);
}Cart: yuklanmoqda
Cart: 0
Cart: 1
Cart: 2
--- (sahifa qayta yuklandi)
Cart: yuklanmoqda
Cart: 2- Birinchi chizishda —
undefined("yuklanmoqda"): so'rov async. Har sahifa ochilishida shunday bo'ladi — oxirgi ikki qatorga qarang. AddButtonfaqat bazaga yozdi. Hech qanday store,setState,invalidateyo'q —Carto'zi yangilandi: Dexie qaysi jadval o'zgarganini kuzatadi.- Ma'lumot sahifa yangilangandan keyin ham joyida — ikki osh.
Bu — Jotai, Valtio, MobX darsidagi "qiymat o'quvchilarini o'zi biladi" g'oyasining baza darajasidagi ko'rinishi. Boshqa tabdagi o'zgarish ham yetib keladi: Dexie tablar orasida xabar almashadi (Oyna va tablar orasida aloqa darsidagi kanallar g'oyasi).
Tekshirib ko'ring:
useLiveQuery(() => db.items.where("name").equals("Osh").count())— bazaga «Manti» qo'shilsa, komponent qayta chiziladimi?
Javob
Dexie 4 so'rov qaysi indeks oralig'ini o'qiganini kuzatadi: name = "Osh". «Manti» bu oraliqqa tushmaydi — so'rov qayta bajarilmaydi, komponent chizilmaydi. «Osh» qo'shilsa — yangilanadi. Indekssiz, butun jadvalni o'qiydigan so'rov (toArray()) esa har o'zgarishda qayta bajariladi.
5. Kanon o'lchovi: qaysi ma'lumot qayerda
Bu darsda vazifalar ga yangi kod yo'q. Lekin "ro'yxatni IndexedDB'ga ko'chiramizmi?" savolini o'lchov bilan hal qildik (natijalar/tajriba/27-indexeddb.txt, Chrome 155, har usul 7 marta, mediana). 10 000 vazifa (JSON 830 KB):
- localStorage o'qishsinxron ≤ 2.4 ms1,7 ms
- idb-keyval getasync6,5 ms
- idb-keyval setasync7,4 ms
- localStorage yozishsinxron ≤ 11 ms9,4 ms
- Dexie toArrayhar vazifa — yozuv135,8 ms
- Dexie bulkPutasosiy oqim ≤ 103 ms band309,6 ms
Manba: IlmHamroh kurs tajribasi (Chrome 155.0.8059.40, idb-keyval 6.3.0, dexie 4.4.6)
Xom fayldagi "bitta vazifani yangilash" qatorlari:
Dexie bitta vazifani yangilash (update) 1.9 ms sinxron ≤ 1.2 ms
idb-keyval bitta vazifani yangilash (butun massiv qayta) 21.5 ms sinxron ≤ 0.2 msNimani ko'ramiz:
- O'qish:
localStorageeng tez (1.7 ms) va sinxron. 830 KB JSON niJSON.parseqilish ham shu ichida. - idb-keyval butun massivni bitta kalitda saqlaydi — o'qish 6.5 ms, lekin async: birinchi chizishga ulgurmaydi.
- Dexie har vazifani alohida yozuv qiladi: hammasini o'qish (135.8 ms) va yozish (309.6 ms) ancha sekin,
bulkPutasosiy oqimni 103 ms gacha band qildi. Lekin bitta vazifani yangilash — 1.9 ms, idb-keyval'da esa butun massivni qayta yozish kerak — 21.5 ms.
Kanon qarori: vazifalar ro'yxati localStorage da qoladi — birinchi kadr sinxron o'qishga tayanadi va 10 000 vazifada ham 1.7 ms. IndexedDB (idb-keyval) — faqat profil rasmi uchun (Blob; base64 +33% va kvota). Dexie kanonga kirmadi: so'rov va indekslar kerak emas (qidiruv — xotirada), butun ro'yxatni yuklash esa sekinroq.
flowchart TD
A["Nima saqlanadi?"] --> B{"Rasm, fayl, Blob?"}
B -- ha --> I["IndexedDB: idb-keyval"]
B -- yo'q --> C{"Kichik va birinchi kadrda kerak?"}
C -- ha --> L["localStorage (shu tab uchun — sessionStorage)"]
C -- yo'q --> D{"Ko'p yozuv, so'rov, bittalab yangilash?"}
D -- ha --> X["IndexedDB: Dexie"]
D -- yo'q --> IQachon Dexie — to'g'ri tanlov: oflayn ishlaydigan ilovalar (ofitsiant plansheti, kuryer ilovasi) — minglab yozuv, indeks bo'yicha so'rov, bittalab yangilash, tablararo jonli yangilanish. Bunday ilovada sinxron birinchi kadr o'rniga "yuklanmoqda" holati qabul qilinadi.
6. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
QuotaExceededError va ilova yiqildi |
localStorage ~5 MB to'ldi (ko'pincha base64 rasm) |
Blob — IndexedDB'ga; setItem — try ichida |
saqlangan sana — satr, Map — {} |
JSON structured clone emas | IndexedDB yoki o'qishda qayta tiklash |
DataCloneError |
IndexedDB'ga funksiya yoki DOM element | faqat ma'lumot saqlang |
ReferenceError: indexedDB is not defined (test) |
jsdom'da IndexedDB yo'q, get() sinxron tashladi |
Promise.resolve().then(() => get(…)) yoki fake-indexeddb |
| yangi indeks bo'yicha so'rov bo'sh | versiya oshdi, eski yozuvlar ko'chirilmadi | .upgrade(tx => …modify(…)) |
useLiveQuery natijasi undefined |
birinchi chizish — so'rov hali tugamagan | "yuklanmoqda" holatini chizing |
| boshqa tabdagi o'zgarish ko'rinmaydi | storage hodisasi tinglanmagan |
addEventListener("storage", …) yoki Dexie liveQuery |
blob: rasm ko'rinmaydi |
CSP img-src da blob: yo'q |
img-src 'self' blob: |
7. Mashqlar
Mashqlar kurs/mashqlar/19/27-indexeddb/ da. 2- va 3-mashq saytda ishlaydi (natija oynasida localStorage o'rinbosari bor).
1-mashq (oson): qayerga?
Har ma'lumotga saqlash joyi tanlang — localStorage, sessionStorage yoki IndexedDB:
- tema ("qora"/"oq") →
- to'lov oynasidagi joriy qadam (faqat shu tab) →
- 300 KB lik taom rasmi →
- 5 000 ta oflayn buyurtma, stol bo'yicha qidiriladi →
Yechim
Tema — kichik matn, birinchi kadrda kerak (aks holda oq ekran → qora ekran). To'lov qadami — faqat shu tab, tab yopilsa kerak emas. Rasm — Blob, base64 +33% va kvota. Buyurtmalar — ko'p yozuv va indeks bo'yicha so'rov: Dexie. Rasm uchun esa idb-keyval yetarli — so'rov kerak emas.
2-mashq (o'rta): useLocalStorage hook
localStorage dagi qiymatni o'qiydigan va yozadigan hook yozing: const [value, setValue] = useLocalStorage(kalit, boshlang'ich).
- Qiymat JSON bo'lib saqlanadi; buzuq JSON yoki yo'q kalit — boshlang'ich qiymat.
- Bir xil kalitli ikki komponent bir-birini ko'rsin: biri yozsa, ikkinchisi yangilansin.
- Boshqa tabdan kelgan
storagehodisasi ham yangilasin.
Ishora: useSyncExternalStore — getSnapshot satrni qaytarsin (satrlar qiymat bo'yicha solishtiriladi), JSON.parse — undan keyin. O'z tabidagi yozuvni xabar qilish uchun o'zingizning Set ingiz.
Yechim
import { useSyncExternalStore } from "react";
const listeners = new Set();
function subscribe(listener) {
listeners.add(listener);
// boshqa tablar
addEventListener("storage", listener);
return () => {
listeners.delete(listener);
removeEventListener("storage", listener);
};
}
function useLocalStorage(key, initial) {
const raw = useSyncExternalStore(subscribe, () =>
localStorage.getItem(key),
);
let value = initial;
try {
if (raw !== null) value = JSON.parse(raw);
} catch {
value = initial;
}
const setValue = (next) => {
try {
localStorage.setItem(key, JSON.stringify(next));
} catch (error) {
console.log("saqlanmadi:", error.name);
}
listeners.forEach((listener) => listener()); // shu tab
};
return [value, setValue];
}
function Toggle() {
const [compact, setCompact] = useLocalStorage(
"bahor:ixcham",
false,
);
return (
<button onClick={() => setCompact(!compact)}>Ixcham</button>
);
}
function Status() {
const [compact] = useLocalStorage("bahor:ixcham", false);
return <p>Menyu: {compact ? "ixcham" : "oddiy"}</p>;
}
localStorage.setItem("bahor:buzuq", "{osh");
function Broken() {
const [value] = useLocalStorage("bahor:buzuq", "standart");
return <p>Buzuq kalit: {value}</p>;
}
export default function App() {
return (
<>
<Toggle />
<Status />
<Broken />
</>
);
}Ekranda:
Ixcham
Menyu: ixcham
Buzuq kalit: standartToggle yozdi, Status — boshqa komponent — yangilandi: ikkalasi bitta tashqi manbaga obuna. getSnapshot satr qaytaradi: agar JSON.parse natijasini (obyektni) qaytarganida, har chaqiruvda yangi obyekt — Zustand I dagi cheksiz sikl. Buzuq JSON ilovani yiqitmadi.
3-mashq (qiyin): o'z migratsiyangiz
Savat localStorage da { version, items } shaklida saqlanadi. Tarixi:
- 1-versiya:
items— nomlar:["Osh"]. - 2-versiya:
items—{ name, qty }. - 3-versiya (hozirgi):
items—{ name, qty, price }; eski yozuvlarda narx yo'q —PRICESjadvalidan oling.
loadCart() yozing: istalgan eski versiyani 3-versiyaga zinapoya bilan ko'chirsin va natijani qayta saqlasin. Boshlang'ich kodda brauzerda 1-versiyali yozuv bor.
localStorage.setItem(
"bahor:savat",
JSON.stringify({ version: 1, items: ["Osh", "Manti"] }),
);
const PRICES = { Osh: 35000, Manti: 30000 };
const VERSION = 3;
function loadCart() {
const saved = JSON.parse(localStorage.getItem("bahor:savat"));
// shu yerga: 1 → 2 → 3
return saved.items;
}
export default function App() {
const items = loadCart();
return <p>Savatda: {items.length} xil</p>;
}Ekranda:
Savatda: 2 xilYechim
localStorage.setItem(
"bahor:savat",
JSON.stringify({ version: 1, items: ["Osh", "Manti"] }),
);
const PRICES = { Osh: 35000, Manti: 30000 };
const VERSION = 3;
function loadCart() {
let saved = JSON.parse(localStorage.getItem("bahor:savat"));
if (saved.version < 2) {
saved = {
version: 2,
items: saved.items.map((name) => ({ name, qty: 1 })),
};
}
if (saved.version < 3) {
saved = {
version: 3,
items: saved.items.map((item) => ({
...item,
price: PRICES[item.name] ?? 0,
})),
};
}
if (saved.version !== VERSION) throw new Error("Noma'lum versiya");
localStorage.setItem("bahor:savat", JSON.stringify(saved));
return saved.items;
}
// bir marta, modul yuklanganda (renderda emas)
const items = loadCart();
console.log(localStorage.getItem("bahor:savat"));
export default function App() {
const total = items.reduce((s, i) => s + i.qty * i.price, 0);
return <p>Savatda: {items.length} xil, {total} so'm</p>;
}Konsolda:
{"version":3,"items":[{"name":"Osh","qty":1,"price":35000},{"name":"Manti","qty":1,"price":30000}]}Ekranda:
Savatda: 2 xil, 65000 so'mZinapoya if (version < N) — else if emas: 1-versiyali yozuv ikkala qadamdan o'tadi, 2-versiyali — faqat ikkinchisidan. Dexie'ning version(n).upgrade() lari va IndexedDB'ning oldVersion mantig'i — aynan shu. loadCart modul darajasida bir marta chaqirildi: renderda setItem — yon ta'sir bo'lardi (va StrictMode'da ikki marta).
4-mashq (tahlil): idb-keyval yoki Dexie?
O'lchov faylidagi ikki qatorga qarang: Dexie bitta vazifani yangilash — 1.9 ms, idb-keyval — 21.5 ms. Nega bunday farq va bu vazifalar uchun nega hal qiluvchi emas?
Yechim
idb-keyval ro'yxatni bitta kalitda saqlaydi: bitta vazifani o'zgartirish uchun butun 830 KB massivni o'qib, qayta yozish kerak. Dexie'da har vazifa — alohida yozuv: update(5000, …) faqat bittasiga tegadi. vazifalar da esa ro'yxat IndexedDB'da umuman yo'q — localStorage da, va u yerda ham butun massiv yoziladi (10 000 da 9.4 ms). Haqiqiy foydalanuvchida vazifalar yuzlab, minglab emas; 1 000 vazifada localStorage yozish 0.9 ms. Dexie'ning ustunligi faqat ko'p yozuvli va bittalab tez-tez yangilanadigan ma'lumotda seziladi.
8. Real ishda
- Oflayn ilovalar (PWA): buyurtmalar, qoralamalar, keshlangan katalog — IndexedDB (ko'pincha Dexie), serverga sinxronlash navbati bilan. Servis ishchisi ham shu bazani o'qiydi.
- Media: foydalanuvchi tanlagan rasm, ovozli xabar, eksport fayli — Blob sifatida idb-keyval'da.
- TanStack Query keshini ham saqlash mumkin:
persistQueryClient+ IndexedDB'dagi saqlash joyi (ZustandpersistningcreateJSONStoragega o'xshash). Server state oflaynda ham ko'rinadi. - Xavfsizlik: IndexedDB ham,
localStorageham shu saytdagi har skriptga ochiq — token va maxfiy ma'lumot u yerda emas (Frontend xavfsizligi). - Intervyu savollari: "localStorage va IndexedDB farqi?", "nega localStorage'ga rasm saqlamaysiz?", "IndexedDB sxemasini qanday o'zgartirasiz?", "boshqa tabdagi o'zgarishni qanday bilasiz?".
Xulosa
localStorage— sinxron, faqat matn, ~5 MB: JSONDate,Map,undefinedni yo'qotadi, rasm base64 bilan +33%, to'lgandaQuotaExceededError.sessionStorage— shu tab uchun;storagehodisasi — boshqa tablargalocalStorageo'zgarishi haqida.- idb-keyval — IndexedDB'da kalit/qiymat, structured clone (Blob,
Date,Map), React'gauseSyncExternalStorebilan. - Dexie — jadvallar, indekslar,
whereso'rovlar,version().upgrade()migratsiya,useLiveQuery— baza o'zgarsa ekran o'zi yangilanadi. - Sxema o'zgarsa — versiya oshadi va eski yozuvlar zinapoya bilan ko'chiriladi.
- O'lchov (10 000 vazifa):
localStorageo'qish 1.7 ms, idb-keyval 6.5, Dexie 135.8 — kanonda ro'yxatlocalStorageda, IndexedDB faqat profil rasmi uchun.
Keyingi dars: Holat mashinalari: XState va murakkab oqimlar — imkonsiz holatlarsiz ko'p bosqichli oqim: statechart, createMachine, useMachine, @xstate/store va vizualizator.
Manbalar
- idb-keyval — github.com/jakearchibald/idb-keyval (v6.3)
- Dexie.js: Version.upgrade(), WhereClause, useLiveQuery() — dexie.org/docs (v4, 2026-10 holatiga)
- MDN: Storage quotas and eviction criteria; Structured clone algorithm;
StorageEvent— developer.mozilla.org - web.dev: Storage for the web — web.dev/articles/storage-for-the-web
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!