يتناول هذا الدليل أساسيات
IndexedDB API.
نحن نستخدم مكتبة IndexedDB Promised التي أنشأها "جيك أرشيبالد"، وهي مشابهة جدًا لواجهة برمجة التطبيقات IndexedDB API، ولكنّها تستخدم الوعود التي يمكنك await للحصول على بنية أكثر إيجازًا. يؤدي ذلك إلى تبسيط واجهة برمجة التطبيقات
مع الحفاظ على بنيتها.
ما هي IndexedDB؟
IndexedDB هو نظام تخزين NoSQL واسع النطاق يتيح تخزين أي شيء تقريبًا في متصفّح المستخدم. بالإضافة إلى إجراءات البحث والحصول والوضع المعتادة، تتيح IndexedDB أيضًا المعاملات، وهي مناسبة تمامًا لتخزين كميات كبيرة من البيانات المنظَّمة.
تكون كل قاعدة بيانات IndexedDB فريدة من نوعها بالنسبة إلى مصدر معيّن (عادةً ما يكون نطاق الموقع الإلكتروني أو نطاقه الفرعي)، ما يعني أنّه لا يمكنها الوصول إلى أي مصدر آخر أو الوصول إليها من خلاله. حدود تخزين البيانات كبيرة عادةً، إن وُجدت، ولكن تتعامل المتصفحات المختلفة مع الحدود وإخلاء البيانات بشكل مختلف. لمزيد من المعلومات، راجِع قسم مراجع إضافية.
بنود IndexedDB
- قاعدة البيانات
- أعلى مستوى من IndexedDB وهي تحتوي على مخازن العناصر، والتي بدورها تحتوي على البيانات التي تريد الاحتفاظ بها. يمكنك إنشاء قواعد بيانات متعددة بأي أسماء تختارها.
- مساحة تخزين العناصر
- مجموعة بيانات فردية لتخزين البيانات، تشبه الجداول في قواعد البيانات الارتباطية.
عادةً ما يكون هناك مخزن عناصر واحد لكل نوع (وليس نوع بيانات JavaScript) من البيانات التي يتم تخزينها. على عكس جداول قواعد البيانات، لا يلزم أن تكون أنواع بيانات JavaScript في المخزن متسقة. على سبيل المثال، إذا كان التطبيق يحتوي على مخزن عناصر
peopleيتضمّن معلومات عن ثلاثة أشخاص، يمكن أن تكون سمات عمر هؤلاء الأشخاص هي53و'twenty-five'وunknown. - Index
- هو نوع من مخازن العناصر لتنظيم البيانات في مخزن عناصر آخر (يُسمى مخزن العناصر المرجعي) حسب سمة فردية للبيانات. يُستخدم الفهرس لاسترداد السجلات في مخزن العناصر حسب هذه السمة. على سبيل المثال، إذا كنت تخزّن بيانات أشخاص، قد تحتاج إلى جلبها لاحقًا حسب الاسم أو العمر أو الحيوان المفضّل.
- العملية
- تفاعل مع قاعدة البيانات
- Transaction
- هي برنامج تضمين لعملية أو مجموعة من العمليات التي تضمن سلامة قاعدة البيانات. إذا فشل أحد الإجراءات في معاملة ما، فلن يتم تطبيق أي منها وتعود قاعدة البيانات إلى الحالة التي كانت عليها قبل بدء المعاملة. يجب أن تكون جميع عمليات القراءة أو الكتابة في IndexedDB جزءًا من معاملة. يتيح ذلك إجراء عمليات القراءة والتعديل والكتابة الذرية بدون خطر حدوث تعارضات مع سلاسل المحادثات الأخرى التي تعمل على قاعدة البيانات في الوقت نفسه.
- Cursor
- هي آلية لتكرار سجلات متعددة في قاعدة بيانات.
كيفية التحقّق من توافق المتصفّح مع IndexedDB
تتوفّر IndexedDB على جميع الأجهزة تقريبًا.
ومع ذلك، إذا كنت تستخدم متصفّحات قديمة، من الأفضل
التحقّق من توفّر الميزة تحسّبًا لأي مشكلة. أسهل طريقة هي التحقّق من الكائن window:
function indexedDBStuff () {
// Check for IndexedDB support:
if (!('indexedDB' in window)) {
// Can't use IndexedDB
console.log("This browser doesn't support IndexedDB");
return;
} else {
// Do IndexedDB stuff here:
// ...
}
}
// Run IndexedDB code:
indexedDBStuff();
كيفية فتح قاعدة بيانات
باستخدام IndexedDB، يمكنك إنشاء قواعد بيانات متعددة بأي أسماء تختارها. إذا لم تكن قاعدة البيانات موجودة عند محاولة فتحها، يتم إنشاؤها تلقائيًا.
لفتح قاعدة بيانات، استخدِم طريقة openDB() من مكتبة idb:
import {openDB} from 'idb';
async function useDB () {
// Returns a promise, which makes `idb` usable with async-await.
const dbPromise = await openDB('example-database', version, events);
}
useDB();
تعرض هذه الطريقة وعدًا يتم حله إلى عنصر قاعدة بيانات. عند استخدام طريقة openDB()، يجب تقديم اسم ورقم إصدار وعنصر أحداث لإعداد قاعدة البيانات.
إليك مثال على طريقة openDB() في السياق:
import {openDB} from 'idb';
async function useDB () {
// Opens the first version of the 'test-db1' database.
// If the database does not exist, it will be created.
const dbPromise = await openDB('test-db1', 1);
}
useDB();
ضع التحقّق من دعم IndexedDB في أعلى الدالة المجهولة. يؤدي هذا إلى الخروج من الدالة إذا كان المتصفح لا يدعم IndexedDB. إذا كان بإمكان الدالة المتابعة، فإنها تستدعي طريقة openDB() لفتح قاعدة بيانات باسم 'test-db1'.
في هذا المثال، تم استبعاد كائن الأحداث الاختياري لتبسيط الأمور، ولكن عليك تحديده لإجراء أي عمل مفيد باستخدام IndexedDB.
كيفية استخدام مخازن العناصر
تحتوي قاعدة بيانات IndexedDB على مخزن عناصر واحد أو أكثر، ولكل منها عمود لمفتاح، وعمود آخر للبيانات المرتبطة بهذا المفتاح.
إنشاء مخازن عناصر
يجب أن تحتوي قاعدة بيانات IndexedDB المنظَّمة بشكل جيد على مخزن عناصر واحد لكل نوع
من البيانات التي يجب الاحتفاظ بها. على سبيل المثال، قد يحتوي الموقع الإلكتروني الذي يحتفظ بملفات المستخدمين الشخصية وملاحظاتهم على مخزن عناصر people يحتوي على عناصر person، ومخزن عناصر notes يحتوي على عناصر note.
لضمان سلامة قاعدة البيانات، يمكنك فقط إنشاء مخازن العناصر أو إزالتها في
عنصر الأحداث في طلب openDB(). يعرض عنصر الأحداث طريقة upgrade()
تتيح لك إنشاء مخازن عناصر. استدعِ الطريقة
createObjectStore()
داخل الطريقة upgrade() لإنشاء مخزن العناصر:
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('example-database', 1, {
upgrade (db) {
// Creates an object store:
db.createObjectStore('storeName', options);
}
});
}
createStoreInDB();
تأخذ هذه الطريقة اسم مخزن العناصر وعنصر إعدادات اختياري يتيح لك تحديد سمات مختلفة لمخزن العناصر.
في ما يلي مثال على كيفية استخدام createObjectStore():
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('test-db1', 1, {
upgrade (db) {
console.log('Creating a new object store...');
// Checks if the object store exists:
if (!db.objectStoreNames.contains('people')) {
// If the object store does not exist, create it:
db.createObjectStore('people');
}
}
});
}
createStoreInDB();
في هذا المثال، يتم تمرير كائن أحداث إلى طريقة openDB() لإنشاء
مخزن الكائنات، وكما سبق، يتم تنفيذ عمل إنشاء مخزن الكائنات
في طريقة upgrade() لكائن الحدث. ومع ذلك، نظرًا لأن المتصفح يطرح
خطأ إذا حاولت إنشاء مخزن كائنات موجود بالفعل، فإننا نوصي
بتغليف طريقة createObjectStore() في عبارة if التي تتحقق
مما إذا كان مخزن الكائنات موجودًا. داخل كتلة if، استدعِ
createObjectStore() لإنشاء مخزن عناصر باسم 'firstOS'.
كيفية تحديد المفاتيح الأساسية
عند تحديد مخازن العناصر، يمكنك تحديد كيفية تحديد البيانات بشكل فريد في المخزن باستخدام مفتاح أساسي. يمكنك تحديد مفتاح أساسي إما من خلال تحديد مسار مفتاح أو باستخدام منشئ مفاتيح.
مسار المفتاح هو سمة تتوفّر دائمًا وتحتوي على قيمة فريدة. على سبيل المثال، في حال استخدام مخزن عناصر people، يمكنك اختيار عنوان البريد الإلكتروني كمسار المفتاح:
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('test-db2', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('people')) {
db.createObjectStore('people', { keyPath: 'email' });
}
}
});
}
createStoreInDB();
ينشئ هذا المثال مخزن عناصر باسم 'people' ويحدّد السمة email كمفتاح أساسي في الخيار keyPath.
يمكنك أيضًا استخدام أداة إنشاء مفاتيح، مثل autoIncrement. ينشئ منشئ المفاتيح قيمة فريدة لكل كائن تتم إضافته إلى مخزن الكائنات. إذا لم تحدّد مفتاحًا، تنشئ IndexedDB مفتاحًا وتخزّنه بشكل منفصل عن البيانات تلقائيًا.
ينشئ المثال التالي مخزن عناصر باسم 'notes' ويضبط
المفتاح الأساسي ليتم تعيينه تلقائيًا كرقم يتزايد تلقائيًا:
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('test-db2', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('notes')) {
db.createObjectStore('notes', { autoIncrement: true });
}
}
});
}
createStoreInDB();
يشبه المثال التالي المثال السابق، ولكن هذه المرة يتم تعيين قيمة الزيادة التلقائية بشكل صريح لسمة باسم 'id'.
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('test-db2', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('logs')) {
db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
}
}
});
}
createStoreInDB();
يعتمد اختيار الطريقة التي يجب استخدامها لتحديد المفتاح على بياناتك. إذا كانت بياناتك تحتوي على خاصية فريدة دائمًا، فيمكنك جعلها keyPath لفرض هذا التفرد. وإلا، استخدِم قيمة تزيد تلقائيًا.
ينشئ الرمز التالي ثلاثة مخازن عناصر توضّح الطرق المختلفة لتحديد المفاتيح الأساسية في مخازن العناصر:
import {openDB} from 'idb';
async function createStoresInDB () {
const dbPromise = await openDB('test-db2', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('people')) {
db.createObjectStore('people', { keyPath: 'email' });
}
if (!db.objectStoreNames.contains('notes')) {
db.createObjectStore('notes', { autoIncrement: true });
}
if (!db.objectStoreNames.contains('logs')) {
db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
}
}
});
}
createStoresInDB();
كيفية تحديد الفهارس
الفهارس هي نوع من مخازن العناصر التي تُستخدم لاسترداد البيانات من مخزن العناصر المرجعي حسب سمة محددة. يتوفّر الفهرس داخل مخزن العناصر المرجعية ويحتوي على البيانات نفسها، ولكنه يستخدم السمة المحدّدة كمسار مفتاح بدلاً من المفتاح الأساسي للمخزن المرجعي. يجب إنشاء الفهارس عند إنشاء مخازن العناصر، ويمكن استخدامها لتحديد قيد فريد على بياناتك.
لإنشاء فهرس، استدعِ طريقة createIndex() في مثيل مخزن العناصر:
import {openDB} from 'idb';
async function createIndexInStore() {
const dbPromise = await openDB('storeName', 1, {
upgrade (db) {
const objectStore = db.createObjectStore('storeName');
objectStore.createIndex('indexName', 'property', options);
}
});
}
createIndexInStore();
تنشئ هذه الطريقة كائن فهرس وتعرضه. تأخذ طريقة createIndex() في مثيل مخزن العناصر اسم الفهرس الجديد كوسيطة أولى، وتشير الوسيطة الثانية إلى الخاصية الموجودة في البيانات التي تريد فهرستها. تتيح لك الوسيطة الأخيرة تحديد خيارَين يحدّدان كيفية عمل الفهرس: unique وmultiEntry. إذا تم ضبط unique على true، لن يسمح الفهرس بالقيم المكرّرة لمفتاح واحد. بعد ذلك، يحدّد multiEntry كيفية تصرّف createIndex() عندما تكون السمة المفهرَسة مصفوفة. إذا تم ضبطها على true، يضيف createIndex() إدخالاً في الفهرس لكل عنصر في الصفيفة. وإلا، فإنه يضيف إدخالاً واحدًا يحتوي على المصفوفة.
وفي ما يلي مثال لذلك:
import {openDB} from 'idb';
async function createIndexesInStores () {
const dbPromise = await openDB('test-db3', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('people')) {
const peopleObjectStore = db.createObjectStore('people', { keyPath: 'email' });
peopleObjectStore.createIndex('gender', 'gender', { unique: false });
peopleObjectStore.createIndex('ssn', 'ssn', { unique: true });
}
if (!db.objectStoreNames.contains('notes')) {
const notesObjectStore = db.createObjectStore('notes', { autoIncrement: true });
notesObjectStore.createIndex('title', 'title', { unique: false });
}
if (!db.objectStoreNames.contains('logs')) {
const logsObjectStore = db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
}
}
});
}
createIndexesInStores();
في هذا المثال، يحتوي مخزنا العناصر 'people' و'notes' على فهارس. لإنشاء الفهارس، عليك أولاً تعيين نتيجة createObjectStore() (عنصر مخزن العناصر) لمتغير حتى تتمكن من استدعاء createIndex() عليه.
كيفية العمل مع البيانات
يوضّح هذا القسم كيفية إنشاء البيانات وقراءتها وتعديلها وحذفها. هذه
العمليات كلها غير متزامنة، وتستخدم الوعود حيث تستخدم واجهة برمجة تطبيقات IndexedDB
الطلبات. يؤدي ذلك إلى تبسيط واجهة برمجة التطبيقات. بدلاً من الاستماع إلى الأحداث التي يتم تشغيلها بواسطة
الطلب، يمكنك استدعاء .then() على كائن قاعدة البيانات الذي تم إرجاعه من
طريقة openDB() لبدء التفاعلات مع قاعدة البيانات، أو await إنشائها.
يتم تنفيذ جميع عمليات البيانات في IndexedDB داخل معاملة. تأخذ كل عملية الشكل التالي:
- الحصول على عنصر قاعدة البيانات
- فتح معاملة في قاعدة البيانات
- فتح مساحة تخزين العناصر في المعاملة
- تنفيذ عملية على مساحة تخزين العناصر
يمكن اعتبار المعاملة بمثابة غلاف آمن حول عملية أو مجموعة من العمليات. إذا تعذّر تنفيذ أحد الإجراءات ضمن معاملة معيّنة، يتم التراجع عن جميع الإجراءات. تكون المعاملات خاصة بمخزن عناصر واحد أو أكثر، والذي تحدده عند فتح المعاملة. ويمكن أن تكون للقراءة فقط أو للقراءة والكتابة. يشير ذلك إلى ما إذا كانت العمليات داخل المعاملة تقرأ البيانات أو تُجري تغييرًا على قاعدة البيانات.
إنشاء البيانات
لإنشاء البيانات، يمكنك استدعاء الطريقة add() في مثيل قاعدة البيانات وتمرير البيانات التي تريد إضافتها. الوسيطة الأولى للدالة add() هي مخزن العناصر الذي تريد إضافة البيانات إليه، والوسيطة الثانية هي عنصر يحتوي على الحقول والبيانات المرتبطة التي تريد إضافتها. في ما يلي أبسط مثال، حيث تتم إضافة صف واحد من البيانات:
import {openDB} from 'idb';
async function addItemToStore () {
const db = await openDB('example-database', 1);
await db.add('storeName', {
field: 'data'
});
}
addItemToStore();
يتم إجراء كل طلب add() ضمن معاملة، لذا حتى إذا تم حلّ الوعد
بنجاح، لا يعني ذلك بالضرورة أنّ العملية نجحت. للتأكّد من تنفيذ عملية الإضافة، عليك التحقّق مما إذا كانت المعاملة بأكملها قد اكتملت باستخدام طريقة transaction.done(). هذا هو
وعد يتم حله عند اكتمال المعاملة نفسها، ويتم رفضه إذا
حدثت أخطاء في المعاملة. يجب إجراء هذا التحقّق لجميع عمليات "الكتابة"، لأنّها الطريقة الوحيدة لمعرفة أنّ التغييرات في قاعدة البيانات قد حدثت بالفعل.
يوضّح الرمز التالي استخدام طريقة add() داخل معاملة:
import {openDB} from 'idb';
async function addItemsToStore () {
const db = await openDB('test-db4', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('foods')) {
db.createObjectStore('foods', { keyPath: 'name' });
}
}
});
// Create a transaction on the 'foods' store in read/write mode:
const tx = db.transaction('foods', 'readwrite');
// Add multiple items to the 'foods' store in a single transaction:
await Promise.all([
tx.store.add({
name: 'Sandwich',
price: 4.99,
description: 'A very tasty sandwich!',
created: new Date().getTime(),
}),
tx.store.add({
name: 'Eggs',
price: 2.99,
description: 'Some nice eggs you can cook up!',
created: new Date().getTime(),
}),
tx.done
]);
}
addItemsToStore();
بعد فتح قاعدة البيانات (وإنشاء مخزن عناصر إذا لزم الأمر)، عليك
فتح معاملة من خلال استدعاء طريقة transaction() عليها. تأخذ هذه الطريقة وسيطة للمتجر الذي تريد إجراء المعاملة فيه، بالإضافة إلى الوضع.
في هذه الحالة، نريد الكتابة إلى المتجر، لذا يحدّد هذا المثال 'readwrite'.
الخطوة التالية هي البدء في إضافة عناصر إلى المتجر كجزء من المعاملة.
في المثال السابق، نتعامل مع ثلاث عمليات في متجر 'foods'، وكل عملية تُرجع وعدًا:
- جارٍ إضافة سجلّ لشطيرة لذيذة.
- جارٍ إضافة سجلّ لبعض البيض.
- الإشارة إلى اكتمال المعاملة (
tx.done)
بما أنّ كل هذه الإجراءات تستند إلى الوعود، علينا الانتظار حتى تنتهي جميعها. إنّ تمرير هذه الوعود إلى
Promise.all
هو طريقة جيدة ومريحة لإنجاز ذلك. تقبل الدالة Promise.all مصفوفة من الوعود وتنتهي عندما يتم حل جميع الوعود التي تم تمريرها إليها.
بالنسبة إلى السجلَّين اللذين تتم إضافتهما، تستدعي واجهة store لمثيل المعاملة add() وتمرّر البيانات إليها. يمكنك await طلب Promise.all
ليتم إنهاؤه عند اكتمال المعاملة.
قراءة البيانات
لقراءة البيانات، استخدِم طريقة get() في مثيل قاعدة البيانات الذي تسترجعه باستخدام طريقة openDB().
تأخذ get() اسم المتجر وقيمة المفتاح الأساسي للعنصر الذي تريد استرداده. إليك مثال أساسي:
import {openDB} from 'idb';
async function getItemFromStore () {
const db = await openDB('example-database', 1);
// Get a value from the object store by its primary key value:
const value = await db.get('storeName', 'unique-primary-key-value');
}
getItemFromStore();
كما هو الحال مع add()، تعرض طريقة get() وعدًا، لذا يمكنك await إذا كنت تفضّل ذلك، أو استخدام دالة الاستدعاء .then() للوعد.
يستخدم المثال التالي طريقة get() في مخزن عناصر 'foods' لقاعدة بيانات 'test-db4' للحصول على صف واحد من خلال المفتاح الأساسي 'name':
import {openDB} from 'idb';
async function getItemFromStore () {
const db = await openDB('test-db4', 1);
const value = await db.get('foods', 'Sandwich');
console.dir(value);
}
getItemFromStore();
إنّ استرداد صف واحد من قاعدة البيانات أمر بسيط إلى حد ما: افتح
قاعدة البيانات وحدِّد مخزن العناصر وقيمة المفتاح الأساسي للصف الذي
تريد الحصول على البيانات منه. بما أنّ طريقة get() تعرض وعدًا، يمكنك استخدام await.
تعديل البيانات
لتعديل البيانات، يمكنك استدعاء طريقة put() في مخزن العناصر. تتشابه طريقة put() مع طريقة add()
ويمكن استخدامها أيضًا بدلاً من add() لإنشاء البيانات. في ما يلي مثال أساسي على استخدام put() لتعديل صف في مخزن عناصر حسب قيمة المفتاح الأساسي:
import {openDB} from 'idb';
async function updateItemInStore () {
const db = await openDB('example-database', 1);
// Update a value from in an object store with an inline key:
await db.put('storeName', { inlineKeyName: 'newValue' });
// Update a value from in an object store with an out-of-line key.
// In this case, the out-of-line key value is 1, which is the
// auto-incremented value.
await db.put('otherStoreName', { field: 'value' }, 1);
}
updateItemInStore();
مثل الطرق الأخرى، تُرجع هذه الطريقة وعدًا. يمكنك أيضًا استخدام put()
كجزء من معاملة. إليك مثال باستخدام متجر 'foods' من وقت سابق
يحدّث سعر الشطيرة والبيض:
import {openDB} from 'idb';
async function updateItemsInStore () {
const db = await openDB('test-db4', 1);
// Create a transaction on the 'foods' store in read/write mode:
const tx = db.transaction('foods', 'readwrite');
// Update multiple items in the 'foods' store in a single transaction:
await Promise.all([
tx.store.put({
name: 'Sandwich',
price: 5.99,
description: 'A MORE tasty sandwich!',
updated: new Date().getTime() // This creates a new field
}),
tx.store.put({
name: 'Eggs',
price: 3.99,
description: 'Some even NICER eggs you can cook up!',
updated: new Date().getTime() // This creates a new field
}),
tx.done
]);
}
updateItemsInStore();
تعتمد طريقة تعديل العناصر على كيفية ضبط المفتاح. إذا ضبطت keyPath،
يرتبط كل صف في مخزن العناصر بمفتاح مضمّن. يحدّث المثال السابق الصفوف استنادًا إلى هذا المفتاح، وعند تحديث الصفوف في هذه الحالة، ستحتاج إلى تحديد هذا المفتاح لتحديث العنصر المناسب في مخزن العناصر. يمكنك أيضًا إنشاء مفتاح غير مضمّن من خلال ضبط
autoIncrement كمفتاح أساسي.
حذف البيانات
لحذف البيانات، استخدِم طريقة delete()
في مساحة تخزين العناصر:
import {openDB} from 'idb';
async function deleteItemFromStore () {
const db = await openDB('example-database', 1);
// Delete a value
await db.delete('storeName', 'primary-key-value');
}
deleteItemFromStore();
مثل add() وput()، يمكنك استخدام هذا كجزء من معاملة:
import {openDB} from 'idb';
async function deleteItemsFromStore () {
const db = await openDB('test-db4', 1);
// Create a transaction on the 'foods' store in read/write mode:
const tx = db.transaction('foods', 'readwrite');
// Delete multiple items from the 'foods' store in a single transaction:
await Promise.all([
tx.store.delete('Sandwich'),
tx.store.delete('Eggs'),
tx.done
]);
}
deleteItemsFromStore();
بنية التفاعل مع قاعدة البيانات هي نفسها بالنسبة إلى العمليات الأخرى. تذكَّر التحقّق من اكتمال المعاملة بالكامل من خلال
تضمين طريقة tx.done في الصفيف الذي يتم تمريره إلى Promise.all.
الحصول على جميع البيانات
حتى الآن، لم تسترد سوى كائنات من المتجر واحدًا تلو الآخر. يمكنك أيضًا
استرداد جميع البيانات أو مجموعة فرعية منها من مخزن عناصر أو فهرس باستخدام
إما طريقة getAll() أو المؤشرات.
طريقة getAll()
أبسط طريقة لاسترداد جميع بيانات مخزن العناصر هي استدعاء getAll()
على مخزن العناصر أو الفهرس، مثل هذا:
import {openDB} from 'idb';
async function getAllItemsFromStore () {
const db = await openDB('test-db4', 1);
// Get all values from the designated object store:
const allValues = await db.getAll('storeName');
console.dir(allValues);
}
getAllItemsFromStore();
تعرض هذه الطريقة جميع العناصر في مخزن العناصر، بدون أي قيود على الإطلاق. إنّها الطريقة الأكثر مباشرةً للحصول على جميع القيم من مخزن عناصر، ولكنّها أيضًا الأقل مرونة.
import {openDB} from 'idb';
async function getAllItemsFromStore () {
const db = await openDB('test-db4', 1);
// Get all values from the designated object store:
const allValues = await db.getAll('foods');
console.dir(allValues);
}
getAllItemsFromStore();
يستدعي هذا المثال getAll() في مخزن العناصر 'foods'. يؤدي ذلك إلى عرض جميع العناصر من 'foods'، مرتّبة حسب المفتاح الأساسي.
كيفية استخدام المؤشرات
تُعدّ المؤشرات طريقة أكثر مرونة لاسترداد عناصر متعددة. يختار المؤشر كل عنصر في مخزن العناصر أو الفهرس واحدًا تلو الآخر، ما يتيح لك تنفيذ إجراء ما على البيانات عند اختيارها. تعمل المؤشرات، مثل عمليات قاعدة البيانات الأخرى، في المعاملات.
لإنشاء مؤشر، يمكنك استدعاء openCursor()
في مخزن العناصر كجزء من معاملة. باستخدام مخزن 'foods' من الأمثلة السابقة، إليك كيفية تقديم المؤشر عبر جميع صفوف البيانات في مخزن العناصر:
import {openDB} from 'idb';
async function getAllItemsFromStoreWithCursor () {
const db = await openDB('test-db4', 1);
const tx = await db.transaction('foods', 'readonly');
// Open a cursor on the designated object store:
let cursor = await tx.store.openCursor();
// Iterate on the cursor, row by row:
while (cursor) {
// Show the data in the row at the current cursor position:
console.log(cursor.key, cursor.value);
// Advance the cursor to the next row:
cursor = await cursor.continue();
}
}
getAllItemsFromStoreWithCursor();
يتم فتح المعاملة في هذه الحالة في وضع 'readonly'، ويتم استدعاء طريقة openCursor الخاصة بها. في حلقة while اللاحقة، يمكن قراءة السمتَين key وvalue للصف في الموضع الحالي للمؤشر، ويمكنك إجراء عمليات على هذه القيم بأي طريقة تناسب تطبيقك. عندما تكون مستعدًا، يمكنك استدعاء طريقة continue() لكائن cursor للانتقال إلى الصف التالي، وتنتهي حلقة while عندما يصل المؤشر إلى نهاية مجموعة البيانات.
استخدام المؤشرات مع النطاقات والفهارس
تتيح لك الفهارس جلب البيانات في مساحة تخزين العناصر حسب سمة أخرى غير
المفتاح الأساسي. يمكنك إنشاء فهرس على أي سمة، والتي تصبح keyPath
للفهرس، وتحديد نطاق على تلك السمة، والحصول على البيانات ضمن
النطاق باستخدام getAll() أو مؤشر.
حدِّد النطاق باستخدام الكائن IDBKeyRange وأي من الطرق التالية:
upperBound().lowerBound().bound()(الذي يجمع بين الاثنين).only().includes().
تحدّد الطريقتان upperBound() وlowerBound() الحدّين الأعلى والأدنى للنطاق.
IDBKeyRange.lowerBound(indexKey);
أو:
IDBKeyRange.upperBound(indexKey);
تأخذ كل دالة وسيطة واحدة: قيمة keyPath للفهرس للعنصر الذي تريد
تحديده كحد أعلى أو أدنى.
تحدّد طريقة bound() حدًّا أقصى وحدًّا أدنى:
IDBKeyRange.bound(lowerIndexKey, upperIndexKey);
يكون نطاق هذه الدوال شاملاً بشكل تلقائي، ما يعني أنّه يتضمّن البيانات المحدّدة كحدود للنطاق. لاستبعاد هذه القيم،
حدِّد النطاق على أنّه حصري من خلال تمرير true كوسيطة ثانية لـ
lowerBound() أو upperBound()، أو كوسيطة ثالثة ورابعة لـ
bound()، للحدَّين الأدنى والأقصى على التوالي.
يستخدم المثال التالي فهرسًا في السمة 'price' في مخزن العناصر 'foods'. يحتوي المتجر الآن أيضًا على نموذج مرفق به مع مدخلين للحدود
العليا والدنيا للنطاق. استخدم الكود التالي للعثور على الأطعمة ذات
الأسعار بين تلك الحدود:
import {openDB} from 'idb';
async function searchItems (lower, upper) {
if (!lower === '' && upper === '') {
return;
}
let range;
if (lower !== '' && upper !== '') {
range = IDBKeyRange.bound(lower, upper);
} else if (lower === '') {
range = IDBKeyRange.upperBound(upper);
} else {
range = IDBKeyRange.lowerBound(lower);
}
const db = await openDB('test-db4', 1);
const tx = await db.transaction('foods', 'readonly');
const index = tx.store.index('price');
// Open a cursor on the designated object store:
let cursor = await index.openCursor(range);
if (!cursor) {
return;
}
// Iterate on the cursor, row by row:
while (cursor) {
// Show the data in the row at the current cursor position:
console.log(cursor.key, cursor.value);
// Advance the cursor to the next row:
cursor = await cursor.continue();
}
}
// Get items priced between one and four dollars:
searchItems(1.00, 4.00);
يحصل نموذج الرمز أولاً على قيم الحدود ويتحقق مما إذا كانت الحدود
موجودة. يحدّد الجزء التالي من الرمز الطريقة التي يجب استخدامها لتحديد النطاق
استنادًا إلى القيم. في تفاعل قاعدة البيانات، افتح مخزن العناصر في المعاملة كالمعتاد، ثم افتح فهرس 'price' في مخزن العناصر. يتيح لك الفهرس 'price' البحث عن السلع حسب السعر.
بعد ذلك، يفتح الرمز مؤشرًا على الفهرس ويمرّر النطاق. يعرض المؤشر
وعدًا يمثّل العنصر الأول في النطاق، أو undefined في حال
عدم توفّر بيانات ضمن النطاق. تعرض الطريقة cursor.continue() مؤشرًا يمثّل العنصر التالي، وتستمر في التكرار حتى تصل إلى نهاية النطاق.
إصدارات قواعد البيانات
عند استدعاء طريقة openDB()، يمكنك تحديد رقم إصدار قاعدة البيانات
في المَعلمة الثانية. في جميع الأمثلة الواردة في هذا الدليل، تم ضبط الإصدار على 1، ولكن يمكن ترقية قاعدة البيانات إلى إصدار جديد إذا كنت بحاجة إلى تعديلها بطريقة ما. إذا كان الإصدار المحدّد أكبر من إصدار قاعدة البيانات الحالية، يتم تنفيذ دالة الاستدعاء upgrade في عنصر الحدث، ما يسمح لك بإضافة مخازن عناصر وفهارس جديدة إلى قاعدة البيانات.
يحتوي العنصر db في دالة ردّ الاتصال upgrade على السمة الخاصة oldVersion،
التي تشير إلى رقم إصدار قاعدة البيانات التي يمكن للمتصفّح الوصول إليها.
يمكنك تمرير رقم الإصدار هذا إلى عبارة switch لتنفيذ مجموعات من الرموز البرمجية داخل دالة الاستدعاء upgrade استنادًا إلى رقم إصدار قاعدة البيانات الحالي. وفي ما يلي مثال لذلك:
import {openDB} from 'idb';
const db = await openDB('example-database', 2, {
upgrade (db, oldVersion) {
switch (oldVersion) {
case 0:
// Create first object store:
db.createObjectStore('store', { keyPath: 'name' });
case 1:
// Get the original object store, and create an index on it:
const tx = await db.transaction('store', 'readwrite');
tx.store.createIndex('name', 'name');
}
}
});
يضبط هذا المثال أحدث إصدار من قاعدة البيانات على 2. عند تنفيذ هذا الرمز لأول مرة، لا تكون قاعدة البيانات موجودة بعد في المتصفّح، لذا تكون قيمة oldVersion هي 0، وتبدأ عبارة switch عند case 0. في المثال، يضيف هذا مخزن عناصر 'store' إلى قاعدة البيانات.
نقطة أساسية: في عبارات switch، عادةً ما يكون هناك break بعد كل كتلة case، ولكن لم يتم استخدام ذلك هنا عن قصد. بهذه الطريقة، إذا كانت قاعدة البيانات الحالية متأخرة ببضعة إصدارات، أو إذا لم تكن موجودة، فسيستمر الرمز خلال بقية كتل case حتى يتم تحديثه. في المثال، يواصل المتصفّح التنفيذ من خلال case 1، وينشئ فهرس name في مخزن عناصر store.
لإنشاء فهرس 'description' في مخزن العناصر 'store'، عليك تعديل رقم الإصدار وإضافة مجموعة بيانات case جديدة على النحو التالي:
import {openDB} from 'idb';
const db = await openDB('example-database', 3, {
upgrade (db, oldVersion) {
switch (oldVersion) {
case 0:
// Create first object store:
db.createObjectStore('store', { keyPath: 'name' });
case 1:
// Get the original object store, and create an index on it:
const tx = await db.transaction('store', 'readwrite');
tx.store.createIndex('name', 'name');
case 2:
const tx = await db.transaction('store', 'readwrite');
tx.store.createIndex('description', 'description');
}
}
});
إذا كانت قاعدة البيانات التي أنشأتها في المثال السابق لا تزال موجودة في المتصفّح،
عند تنفيذ هذا الإجراء، ستكون قيمة oldVersion هي 2. يتخطّى المتصفّح case 0 وcase 1، وينفّذ الرمز في case 2، ما يؤدي إلى إنشاء فهرس description. بعد ذلك، يحتوي المتصفّح على قاعدة بيانات في الإصدار 3 تتضمّن مخزن عناصر store مع فهرسَي name وdescription.
محتوى إضافي للقراءة
تقدّم المراجع التالية مزيدًا من المعلومات والسياق حول استخدام IndexedDB.
مستندات IndexedDB
- مستودع GitHub
idb - استخدام IndexedDB
- المفاهيم الأساسية وراء IndexedDB
- مواصفات Indexed Database API 3.0