Lesen und schreiben Sie Daten im Web

(Optional) Prototypen erstellen und mit der Firebase Local Emulator Suite testen

Bevor wir darüber sprechen, wie Ihre App aus der Echtzeitdatenbank liest und in sie schreibt, stellen wir eine Reihe von Tools vor, mit denen Sie die Funktionalität der Echtzeitdatenbank prototypisieren und testen können: Firebase Local Emulator Suite. Wenn Sie verschiedene Datenmodelle ausprobieren, Ihre Sicherheitsregeln optimieren oder daran arbeiten, die kostengünstigste Möglichkeit für die Interaktion mit dem Back-End zu finden, kann die Möglichkeit, lokal zu arbeiten, ohne Live-Dienste bereitzustellen, eine gute Idee sein.

Ein Echtzeitdatenbank-Emulator ist Teil der Local Emulator Suite, der es Ihrer App ermöglicht, mit Ihrem emulierten Datenbankinhalt und Ihrer emulierten Datenbankkonfiguration sowie optional mit Ihren emulierten Projektressourcen (Funktionen, andere Datenbanken und Sicherheitsregeln) zu interagieren.

Die Verwendung des Echtzeitdatenbank-Emulators erfordert nur wenige Schritte:

  1. Fügen Sie der Testkonfiguration Ihrer App eine Codezeile hinzu, um eine Verbindung zum Emulator herzustellen.
  2. Führen Sie im Stammverzeichnis Ihres lokalen Projektverzeichnisses firebase emulators:start .
  3. Aufrufe aus dem Prototypcode Ihrer App wie gewohnt mit einem Realtime Database-Plattform-SDK oder mit der Realtime Database REST API durchführen.

Eine detaillierte Anleitung zu Echtzeitdatenbank- und Cloud-Funktionen ist verfügbar. Sie sollten sich auch die Einführung zur Local Emulator Suite ansehen.

Holen Sie sich eine Datenbankreferenz

Um Daten aus der Datenbank zu lesen oder zu schreiben, benötigen Sie eine Instanz von firebase.database.Reference :

Web modular API

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web namespaced API

var database = firebase.database();

Daten schreiben

In diesem Dokument werden die Grundlagen zum Abrufen von Daten sowie zum Sortieren und Filtern von Firebase-Daten behandelt.

Firebase-Daten werden abgerufen, indem ein asynchroner Listener an eine firebase.database.Reference angehängt wird. Der Listener wird einmal für den Anfangszustand der Daten und jedes Mal, wenn sich die Daten ändern, erneut ausgelöst.

Grundlegende Schreiboperationen

Für grundlegende Schreibvorgänge können Sie set() verwenden, um Daten in einer angegebenen Referenz zu speichern und alle vorhandenen Daten in diesem Pfad zu ersetzen. Beispielsweise könnte eine Social-Blogging-Anwendung einen Benutzer mit set() wie folgt hinzufügen:

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
  });
}

Die Verwendung von set() überschreibt Daten am angegebenen Speicherort, einschließlich aller untergeordneten Knoten.

Daten lesen

Achten Sie auf Wertereignisse

Um Daten an einem Pfad zu lesen und auf Änderungen zu warten, verwenden Sie onValue() , um Ereignisse zu beobachten. Mit diesem Ereignis können Sie statische Snapshots des Inhalts in einem bestimmten Pfad lesen, so wie er zum Zeitpunkt des Ereignisses vorhanden war. Diese Methode wird einmal ausgelöst, wenn der Listener angehängt wird, und jedes Mal erneut, wenn sich die Daten, einschließlich untergeordneter Daten, ändern. Dem Ereignisrückruf wird ein Snapshot übergeben, der alle Daten an diesem Speicherort enthält, einschließlich untergeordneter Daten. Wenn keine Daten vorhanden sind, gibt der Snapshot false zurück, wenn Sie exists() aufrufen, und null wenn Sie val() dafür aufrufen.

Das folgende Beispiel zeigt eine Social-Blogging-Anwendung, die die Anzahl der Sterne eines Beitrags aus der Datenbank abruft:

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);
});

Der Listener erhält einen snapshot , der die Daten am angegebenen Ort in der Datenbank zum Zeitpunkt des Ereignisses enthält. Sie können die Daten im snapshot mit der Methode val() abrufen.

Daten einmal lesen

Daten einmal mit get() lesen

Das SDK ist für die Verwaltung von Interaktionen mit Datenbankservern konzipiert, unabhängig davon, ob Ihre App online oder offline ist.

Im Allgemeinen sollten Sie die oben beschriebenen Wertereignistechniken verwenden, um Daten zu lesen und über Aktualisierungen der Daten vom Backend benachrichtigt zu werden. Die Listener-Techniken reduzieren Ihren Verbrauch und Ihre Kosten und sind optimiert, um Ihren Benutzern das beste Erlebnis zu bieten, wenn sie online und offline gehen.

Wenn Sie die Daten nur einmal benötigen, können Sie get() einen Snapshot der Daten aus der Datenbank abrufen. Wenn get() aus irgendeinem Grund den Serverwert nicht zurückgeben kann, prüft der Client den lokalen Speichercache und gibt eine Fehlermeldung zurück, wenn der Wert immer noch nicht gefunden wird.

Die unnötige Verwendung von get() kann die Nutzung der Bandbreite erhöhen und zu Leistungseinbußen führen, was durch die Verwendung eines Echtzeit-Listeners wie oben gezeigt verhindert werden kann.

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);
});

Lesen Sie die Daten einmal mit einem Beobachter

In einigen Fällen möchten Sie möglicherweise, dass der Wert aus dem lokalen Cache sofort zurückgegeben wird, anstatt auf dem Server nach einem aktualisierten Wert zu suchen. In diesen Fällen können Sie once() verwenden, um die Daten sofort aus dem lokalen Festplatten-Cache abzurufen.

Dies ist nützlich für Daten, die nur einmal geladen werden müssen und sich voraussichtlich nicht häufig ändern oder eine aktive Überwachung erfordern. Beispielsweise verwendet die Blogging-App in den vorherigen Beispielen diese Methode, um das Profil eines Benutzers zu laden, wenn dieser mit dem Verfassen eines neuen Beitrags beginnt:

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';
  // ...
});

Daten aktualisieren oder löschen

Aktualisieren Sie bestimmte Felder

Um gleichzeitig auf bestimmte untergeordnete Knoten eines Knotens zu schreiben, ohne andere untergeordnete Knoten zu überschreiben, verwenden Sie die Methode update() .

Wenn Sie update() aufrufen, können Sie untergeordnete Werte auf niedrigerer Ebene aktualisieren, indem Sie einen Pfad für den Schlüssel angeben. Wenn Daten zur besseren Skalierung an mehreren Standorten gespeichert werden, können Sie alle Instanzen dieser Daten mithilfe von Daten-Fanout aktualisieren.

Beispielsweise könnte eine Social-Blogging-App einen Beitrag erstellen und ihn gleichzeitig mit Code wie dem folgenden auf den Feed der letzten Aktivitäten und den Aktivitäts-Feed des postenden Benutzers aktualisieren:

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);
}

In diesem Beispiel wird push() verwendet, um einen Beitrag im Knoten zu erstellen, der Beiträge für alle Benutzer unter /posts/$postid enthält, und gleichzeitig den Schlüssel abzurufen. Der Schlüssel kann dann verwendet werden, um einen zweiten Eintrag in den Beiträgen des Benutzers unter /user-posts/$userid/$postid zu erstellen.

Mit diesen Pfaden können Sie mit einem einzigen Aufruf von update() gleichzeitige Aktualisierungen an mehreren Stellen im JSON-Baum durchführen, so wie in diesem Beispiel der neue Beitrag an beiden Stellen erstellt wird. Auf diese Weise durchgeführte gleichzeitige Aktualisierungen sind atomar: Entweder sind alle Aktualisierungen erfolgreich oder alle Aktualisierungen schlagen fehl.

Fügen Sie einen Abschlussrückruf hinzu

Wenn Sie wissen möchten, wann Ihre Daten festgeschrieben wurden, können Sie einen Abschlussrückruf hinzufügen. Sowohl set() als auch update() nehmen einen optionalen Abschlussrückruf entgegen, der aufgerufen wird, wenn der Schreibvorgang in der Datenbank festgeschrieben wurde. Wenn der Aufruf nicht erfolgreich war, wird dem Rückruf ein Fehlerobjekt übergeben, das angibt, warum der Fehler aufgetreten ist.

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!
  }
});

Daten löschen

Der einfachste Weg, Daten zu löschen, besteht darin remove() für einen Verweis auf den Speicherort dieser Daten aufzurufen.

Sie können auch löschen, indem Sie null als Wert für einen anderen Schreibvorgang angeben, z. B. set() oder update() . Sie können diese Technik mit update() verwenden, um mehrere untergeordnete Elemente in einem einzigen API-Aufruf zu löschen.

Erhalten Sie ein Promise

Um zu wissen, wann Ihre Daten an den Firebase Realtime Database-Server übertragen werden, können Sie ein Promise verwenden. Sowohl set() als auch update() können ein Promise zurückgeben, mit dem Sie wissen können, wann der Schreibvorgang in die Datenbank übernommen wird.

Trennen Sie Zuhörer ab

Rückrufe werden entfernt, indem Sie die Methode off() in Ihrer Firebase-Datenbankreferenz aufrufen.

Sie können einen einzelnen Listener entfernen, indem Sie ihn als Parameter an off() übergeben. Durch den Aufruf von off() an der Position ohne Argumente werden alle Listener an dieser Position entfernt.

Durch den Aufruf von off() für einen übergeordneten Listener werden die auf seinen untergeordneten Knoten registrierten Listener nicht automatisch entfernt. off() muss auch für alle untergeordneten Listener aufgerufen werden, um den Rückruf zu entfernen.

Daten als Transaktionen speichern

Wenn Sie mit Daten arbeiten, die durch gleichzeitige Änderungen beschädigt werden könnten, z. B. inkrementelle Zähler, können Sie eine Transaktionsoperation verwenden. Sie können diesem Vorgang eine Aktualisierungsfunktion und einen optionalen Abschlussrückruf zuweisen. Die Update-Funktion nimmt den aktuellen Zustand der Daten als Argument und gibt den neuen gewünschten Zustand zurück, den Sie schreiben möchten. Wenn ein anderer Client an den Speicherort schreibt, bevor Ihr neuer Wert erfolgreich geschrieben wurde, wird Ihre Aktualisierungsfunktion erneut mit dem neuen aktuellen Wert aufgerufen und der Schreibvorgang wird wiederholt.

In der Beispiel-Social-Blogging-App könnten Sie Benutzern beispielsweise erlauben, Beiträge zu markieren bzw. die Markierung aufzuheben und zu verfolgen, wie viele Sterne ein Beitrag wie folgt erhalten hat:

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;
  });
}

Mithilfe einer Transaktion wird verhindert, dass die Anzahl der Sterne falsch ist, wenn mehrere Benutzer gleichzeitig denselben Beitrag markieren oder der Kunde über veraltete Daten verfügt. Wenn die Transaktion abgelehnt wird, gibt der Server den aktuellen Wert an den Client zurück, der die Transaktion erneut mit dem aktualisierten Wert ausführt. Dies wiederholt sich, bis die Transaktion akzeptiert wird oder Sie die Transaktion abbrechen.

Atomare serverseitige Inkremente

Im obigen Anwendungsfall schreiben wir zwei Werte in die Datenbank: die ID des Benutzers, der den Beitrag markiert bzw. die Markierung aufhebt, und die erhöhte Anzahl der Sterne. Wenn wir bereits wissen, dass der Benutzer den Beitrag markiert, können wir anstelle einer Transaktion eine atomare Inkrementierungsoperation verwenden.

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);
}

Dieser Code verwendet keinen Transaktionsvorgang und wird daher nicht automatisch erneut ausgeführt, wenn es zu einem Konflikt mit der Aktualisierung kommt. Da der Inkrementierungsvorgang jedoch direkt auf dem Datenbankserver erfolgt, besteht keine Möglichkeit eines Konflikts.

Wenn Sie anwendungsspezifische Konflikte erkennen und ablehnen möchten, beispielsweise wenn ein Benutzer einen Beitrag markiert, den er bereits zuvor markiert hat, sollten Sie benutzerdefinierte Sicherheitsregeln für diesen Anwendungsfall schreiben.

Arbeiten Sie offline mit Daten

Wenn ein Client seine Netzwerkverbindung verliert, funktioniert Ihre App weiterhin ordnungsgemäß.

Jeder Client, der mit einer Firebase-Datenbank verbunden ist, verwaltet seine eigene interne Version aller aktiven Daten. Wenn Daten geschrieben werden, werden sie zuerst in diese lokale Version geschrieben. Der Firebase-Client synchronisiert diese Daten dann nach dem „Best-Effort“-Prinzip mit den Remote-Datenbankservern und mit anderen Clients.

Dadurch lösen alle Schreibvorgänge in die Datenbank sofort lokale Ereignisse aus, bevor Daten auf den Server geschrieben werden. Dies bedeutet, dass Ihre App unabhängig von Netzwerklatenz oder Konnektivität reaktionsfähig bleibt.

Sobald die Verbindung wiederhergestellt ist, empfängt Ihre App die entsprechenden Ereignisse, sodass der Client mit dem aktuellen Serverstatus synchronisiert wird, ohne dass benutzerdefinierter Code geschrieben werden muss.

Weitere Informationen zum Offline-Verhalten finden Sie unter Weitere Informationen zu Online- und Offline-Funktionen .

Nächste Schritte