قراءة وكتابة البيانات على الويب

(اختياري) النموذج الأولي والاختبار باستخدام Firebase Local Emulator Suite

قبل الحديث عن كيفية قراءة تطبيقك من قاعدة بيانات Realtime والكتابة إليها، دعنا نقدم مجموعة من الأدوات التي يمكنك استخدامها لإنشاء نموذج أولي واختبار وظيفة قاعدة بيانات Realtime: Firebase Local Emulator Suite. إذا كنت تجرب نماذج بيانات مختلفة، أو تعمل على تحسين قواعد الأمان الخاصة بك، أو تعمل على إيجاد الطريقة الأكثر فعالية من حيث التكلفة للتفاعل مع الواجهة الخلفية، فقد تكون القدرة على العمل محليًا دون نشر الخدمات المباشرة فكرة رائعة.

يعد محاكي Realtime Database جزءًا من Local Emulator Suite، الذي يمكّن تطبيقك من التفاعل مع محتوى قاعدة البيانات التي تمت محاكاتها وتكوينها، بالإضافة إلى موارد المشروع التي تمت محاكاتها بشكل اختياري (الوظائف وقواعد البيانات الأخرى وقواعد الأمان).

يتضمن استخدام محاكي Realtime Database بضع خطوات فقط:

  1. إضافة سطر من التعليمات البرمجية إلى التكوين التجريبي لتطبيقك للاتصال بالمحاكي.
  2. من جذر دليل مشروعك المحلي، قم بتشغيل firebase emulators:start .
  3. إجراء مكالمات من الكود الأولي لتطبيقك باستخدام منصة Realtime Database SDK كالمعتاد، أو باستخدام Realtime Database REST API.

تتوفر إرشادات تفصيلية تتضمن قاعدة بيانات Realtime ووظائف السحابة . يجب عليك أيضًا إلقاء نظرة على مقدمة Local Emulator Suite .

الحصول على مرجع قاعدة البيانات

لقراءة البيانات أو كتابتها من قاعدة البيانات، تحتاج إلى مثيل لـ firebase.database.Reference :

Web modular API

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web namespaced API

var database = firebase.database();

كتابة البيانات

يغطي هذا المستند أساسيات استرداد البيانات وكيفية ترتيب بيانات Firebase وتصفيتها.

يتم استرداد بيانات Firebase عن طريق ربط مستمع غير متزامن بـ firebase.database.Reference . يتم تشغيل المستمع مرة واحدة للحالة الأولية للبيانات ومرة ​​أخرى في أي وقت تتغير فيه البيانات.

عمليات الكتابة الأساسية

بالنسبة لعمليات الكتابة الأساسية، يمكنك استخدام set() لحفظ البيانات في مرجع محدد، واستبدال أي بيانات موجودة في هذا المسار. على سبيل المثال، قد يضيف تطبيق التدوين الاجتماعي مستخدمًا باستخدام set() كما يلي:

Web modular API

import { getDatabase, ref, set } from "firebase/database";

function writeUserData(userId, name, email, imageUrl) {
  const db = getDatabase();
  set(ref(db, 'users/' + userId), {
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

Web namespaced API

function writeUserData(userId, name, email, imageUrl) {
  firebase.database().ref('users/' + userId).set({
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

يؤدي استخدام set() إلى استبدال البيانات في الموقع المحدد، بما في ذلك أي عقد فرعية.

إقرأ البيانات

استمع للأحداث القيمة

لقراءة البيانات في المسار والاستماع إلى التغييرات، استخدم onValue() لمراقبة الأحداث. يمكنك استخدام هذا الحدث لقراءة اللقطات الثابتة للمحتويات في مسار معين، كما كانت موجودة في وقت الحدث. يتم تشغيل هذه الطريقة مرة واحدة عند إرفاق المستمع ومرة ​​أخرى في كل مرة تتغير فيها البيانات، بما في ذلك البيانات الفرعية. يتم تمرير رد اتصال الحدث لقطة تحتوي على كافة البيانات الموجودة في ذلك الموقع، بما في ذلك البيانات الفرعية. إذا لم تكن هناك بيانات، فسوف تُرجع اللقطة false عند استدعاء exists() null عند استدعاء val() عليها.

يوضح المثال التالي تطبيق تدوين اجتماعي يقوم باسترداد عدد النجوم لمنشور من قاعدة البيانات:

Web modular API

import { getDatabase, ref, onValue } from "firebase/database";

const db = getDatabase();
const starCountRef = ref(db, 'posts/' + postId + '/starCount');
onValue(starCountRef, (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

Web namespaced API

var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

يتلقى المستمع snapshot تحتوي على البيانات في الموقع المحدد في قاعدة البيانات في وقت الحدث. يمكنك استرداد البيانات الموجودة في snapshot باستخدام طريقة val() .

قراءة البيانات مرة واحدة

قراءة البيانات مرة واحدة باستخدام get()

تم تصميم SDK لإدارة التفاعلات مع خوادم قاعدة البيانات سواء كان تطبيقك متصلاً بالإنترنت أو غير متصل بالإنترنت.

بشكل عام، يجب عليك استخدام تقنيات حدث القيمة الموضحة أعلاه لقراءة البيانات لتلقي إشعارات بتحديثات البيانات من الواجهة الخلفية. تقلل تقنيات المستمع من استخدامك وفواتيرك، ويتم تحسينها لمنح المستخدمين أفضل تجربة أثناء اتصالهم بالإنترنت وغير متصلين بالإنترنت.

إذا كنت بحاجة إلى البيانات مرة واحدة فقط، فيمكنك استخدام get() للحصول على لقطة من البيانات من قاعدة البيانات. إذا لم يتمكن get() لأي سبب من الأسباب من إرجاع قيمة الخادم، فسيقوم العميل بفحص ذاكرة التخزين المؤقت المحلية وإرجاع خطأ إذا لم يتم العثور على القيمة بعد.

يمكن أن يؤدي الاستخدام غير الضروري لـ get() إلى زيادة استخدام النطاق الترددي ويؤدي إلى فقدان الأداء، وهو ما يمكن منعه باستخدام المستمع في الوقت الفعلي كما هو موضح أعلاه.

Web modular API

import { getDatabase, ref, child, get } from "firebase/database";

const dbRef = ref(getDatabase());
get(child(dbRef, `users/${userId}`)).then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

Web namespaced API

const dbRef = firebase.database().ref();
dbRef.child("users").child(userId).get().then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

قراءة البيانات مرة واحدة مع المراقب

في بعض الحالات، قد ترغب في إرجاع القيمة من ذاكرة التخزين المؤقت المحلية على الفور، بدلاً من التحقق من وجود قيمة محدثة على الخادم. في هذه الحالات، يمكنك استخدام once() للحصول على البيانات من ذاكرة التخزين المؤقت على القرص المحلي على الفور.

يعد هذا مفيدًا للبيانات التي يجب تحميلها مرة واحدة فقط ومن غير المتوقع أن تتغير بشكل متكرر أو تتطلب الاستماع النشط. على سبيل المثال، يستخدم تطبيق التدوين في الأمثلة السابقة هذه الطريقة لتحميل الملف الشخصي للمستخدم عندما يبدأ في تأليف منشور جديد:

Web modular API

import { getDatabase, ref, onValue } from "firebase/database";
import { getAuth } from "firebase/auth";

const db = getDatabase();
const auth = getAuth();

const userId = auth.currentUser.uid;
return onValue(ref(db, '/users/' + userId), (snapshot) => {
  const username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
}, {
  onlyOnce: true
});

Web namespaced API

var userId = firebase.auth().currentUser.uid;
return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => {
  var username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
});

تحديث أو حذف البيانات

تحديث حقول محددة

للكتابة بشكل متزامن إلى أبناء محددين لعقدة دون الكتابة فوق العقد التابعة الأخرى، استخدم طريقة update() .

عند استدعاء update() ، يمكنك تحديث القيم الفرعية ذات المستوى الأدنى عن طريق تحديد مسار للمفتاح. إذا تم تخزين البيانات في مواقع متعددة لتوسيع نطاقها بشكل أفضل، فيمكنك تحديث جميع مثيلات تلك البيانات باستخدام توزيع البيانات .

على سبيل المثال، قد يقوم تطبيق التدوين الاجتماعي بإنشاء منشور وتحديثه في نفس الوقت إلى خلاصة الأنشطة الأخيرة وخلاصة نشاط المستخدم المنشور باستخدام رمز مثل هذا:

Web modular API

import { getDatabase, ref, child, push, update } from "firebase/database";

function writeNewPost(uid, username, picture, title, body) {
  const db = getDatabase();

  // A post entry.
  const postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  const newPostKey = push(child(ref(db), 'posts')).key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  const updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return update(ref(db), updates);
}

Web namespaced API

function writeNewPost(uid, username, picture, title, body) {
  // A post entry.
  var postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  var newPostKey = firebase.database().ref().child('posts').push().key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  var updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return firebase.database().ref().update(updates);
}

يستخدم هذا المثال push() لإنشاء منشور في العقدة التي تحتوي على منشورات لجميع المستخدمين في /posts/$postid واسترداد المفتاح في نفس الوقت. يمكن بعد ذلك استخدام المفتاح لإنشاء إدخال ثانٍ في مشاركات المستخدم على /user-posts/$userid/$postid .

باستخدام هذه المسارات، يمكنك إجراء تحديثات متزامنة لمواقع متعددة في شجرة JSON باستخدام استدعاء واحد للتحديث update() ، مثل كيفية إنشاء المنشور الجديد في كلا الموقعين في هذا المثال. التحديثات المتزامنة التي يتم إجراؤها بهذه الطريقة تكون ذرية: إما أن تنجح كافة التحديثات أو تفشل كافة التحديثات.

إضافة رد اتصال الإكمال

إذا كنت تريد معرفة متى تم الالتزام ببياناتك، فيمكنك إضافة رد اتصال للإكمال. يأخذ كل من set() و update() رد اتصال إكمال اختياري يتم استدعاؤه عند الالتزام بالكتابة في قاعدة البيانات. إذا لم ينجح الاستدعاء، فسيتم تمرير كائن خطأ يشير إلى سبب حدوث الفشل.

Web modular API

import { getDatabase, ref, set } from "firebase/database";

const db = getDatabase();
set(ref(db, 'users/' + userId), {
  username: name,
  email: email,
  profile_picture : imageUrl
})
.then(() => {
  // Data saved successfully!
})
.catch((error) => {
  // The write failed...
});

Web namespaced API

firebase.database().ref('users/' + userId).set({
  username: name,
  email: email,
  profile_picture : imageUrl
}, (error) => {
  if (error) {
    // The write failed...
  } else {
    // Data saved successfully!
  }
});

حذف البيانات

إن أبسط طريقة لحذف البيانات هي استدعاء remove() ‎ للإشارة إلى موقع تلك البيانات.

يمكنك أيضًا الحذف عن طريق تحديد null كقيمة لعملية كتابة أخرى مثل set() أو update() . يمكنك استخدام هذه التقنية مع update() لحذف عدة توابع في استدعاء واحد لواجهة برمجة التطبيقات (API).

احصل على Promise

لمعرفة متى يتم إرسال بياناتك إلى خادم Firebase Realtime Database، يمكنك استخدام Promise . يمكن لكل من set() و update() إرجاع Promise يمكنك استخدامه لمعرفة متى يتم الالتزام بالكتابة في قاعدة البيانات.

افصل المستمعين

تتم إزالة عمليات الاسترجاعات عن طريق استدعاء الأسلوب off() في مرجع قاعدة بيانات Firebase.

يمكنك إزالة مستمع واحد عن طريق تمريره كمعلمة إلى off() . يؤدي استدعاء off() على الموقع بدون وسيطات إلى إزالة جميع المستمعين في ذلك الموقع.

إن استدعاء off() على المستمع الأصلي لا يؤدي تلقائيًا إلى إزالة المستمعين المسجلين في العقد الفرعية الخاصة به؛ يجب أيضًا استدعاء off() ‎ على أي مستمع فرعي لإزالة رد الاتصال.

حفظ البيانات كمعاملات

عند العمل مع البيانات التي قد تتلف بسبب التعديلات المتزامنة، مثل العدادات التزايدية، يمكنك استخدام عملية المعاملة . يمكنك منح هذه العملية وظيفة تحديث ورد اتصال إكمال اختياري. تأخذ وظيفة التحديث الحالة الحالية للبيانات كوسيطة وترجع الحالة الجديدة المطلوبة التي ترغب في كتابتها. إذا كتب عميل آخر إلى الموقع قبل كتابة القيمة الجديدة بنجاح، فسيتم استدعاء وظيفة التحديث الخاصة بك مرة أخرى بالقيمة الحالية الجديدة، وتتم إعادة محاولة الكتابة.

على سبيل المثال، في مثال تطبيق التدوين الاجتماعي، يمكنك السماح للمستخدمين بوضع نجمة على المنشورات وإلغاء تمييزها وتتبع عدد النجوم التي حصل عليها المنشور على النحو التالي:

Web modular API

import { getDatabase, ref, runTransaction } from "firebase/database";

function toggleStar(uid) {
  const db = getDatabase();
  const postRef = ref(db, '/posts/foo-bar-123');

  runTransaction(postRef, (post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

Web namespaced API

function toggleStar(postRef, uid) {
  postRef.transaction((post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

يؤدي استخدام المعاملة إلى منع عدد النجوم من أن يكون غير صحيح إذا قام عدة مستخدمين بنجمة نفس المنشور في نفس الوقت أو كان لدى العميل بيانات قديمة. إذا تم رفض المعاملة، يقوم الخادم بإرجاع القيمة الحالية إلى العميل، الذي يقوم بتشغيل المعاملة مرة أخرى بالقيمة المحدثة. يتكرر هذا حتى يتم قبول المعاملة أو تقوم بإلغاء المعاملة.

الزيادات الذرية من جانب الخادم

في حالة الاستخدام المذكورة أعلاه، نكتب قيمتين إلى قاعدة البيانات: معرف المستخدم الذي قام بنجمة/إلغاء تمييز المنشور بنجمة، وعدد النجوم المتزايد. إذا كنا نعرف بالفعل أن المستخدم يقوم بتمييز المنشور بنجمة، فيمكننا استخدام عملية الزيادة الذرية بدلاً من المعاملة.

Web modular API

function addStar(uid, key) {
  import { getDatabase, increment, ref, update } from "firebase/database";
  const dbRef = ref(getDatabase());

  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = increment(1);
  update(dbRef, updates);
}

Web namespaced API

function addStar(uid, key) {
  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  firebase.database().ref().update(updates);
}

لا يستخدم هذا الرمز عملية معاملة، لذلك لا يتم إعادة تشغيله تلقائيًا في حالة وجود تحديث متعارض. ومع ذلك، بما أن عملية الزيادة تتم مباشرة على خادم قاعدة البيانات، فليس هناك احتمال لحدوث تعارض.

إذا كنت تريد اكتشاف التعارضات الخاصة بالتطبيقات ورفضها، مثل قيام مستخدم بتمييز منشور قام بتمييزه بنجمة من قبل، فيجب عليك كتابة قواعد أمان مخصصة لحالة الاستخدام هذه.

العمل مع البيانات دون اتصال

إذا فقد العميل اتصاله بالشبكة، فسيستمر تطبيقك في العمل بشكل صحيح.

يحتفظ كل عميل متصل بقاعدة بيانات Firebase بنسخته الداخلية الخاصة من أي بيانات نشطة. عند كتابة البيانات، يتم كتابتها إلى هذا الإصدار المحلي أولاً. يقوم عميل Firebase بعد ذلك بمزامنة تلك البيانات مع خوادم قاعدة البيانات البعيدة ومع العملاء الآخرين على أساس "أفضل جهد".

ونتيجة لذلك، تؤدي جميع عمليات الكتابة إلى قاعدة البيانات إلى تشغيل الأحداث المحلية على الفور، قبل كتابة أي بيانات إلى الخادم. وهذا يعني أن تطبيقك يظل مستجيبًا بغض النظر عن زمن وصول الشبكة أو اتصالها.

بمجرد إعادة تأسيس الاتصال، يتلقى تطبيقك المجموعة المناسبة من الأحداث بحيث يتزامن العميل مع حالة الخادم الحالية، دون الحاجة إلى كتابة أي تعليمات برمجية مخصصة.

سنتحدث أكثر عن السلوك دون الاتصال بالإنترنت في تعرف على المزيد حول الإمكانيات المتصلة وغير المتصلة بالإنترنت ..

الخطوات التالية