Чтение и запись данных в Интернете

(Необязательно) Создайте прототип и протестируйте его с помощью пакета локального эмулятора Firebase.

Прежде чем говорить о том, как ваше приложение читает и записывает в базу данных реального времени, давайте представим набор инструментов, которые вы можете использовать для прототипирования и тестирования функциональности базы данных реального времени: Firebase Local Emulator Suite. Если вы тестируете различные модели данных, оптимизируете правила безопасности или работаете над поиском наиболее экономичного способа взаимодействия с серверной частью, возможность работать локально без развертывания действующих сервисов может быть отличной идеей.

Эмулятор базы данных реального времени является частью пакета локальных эмуляторов, который позволяет вашему приложению взаимодействовать с содержимым и конфигурацией эмулируемой базы данных, а также, при необходимости, с ресурсами эмулируемого проекта (функциями, другими базами данных и правилами безопасности).

Использование эмулятора базы данных реального времени включает всего несколько шагов:

  1. Добавление строки кода в тестовую конфигурацию вашего приложения для подключения к эмулятору.
  2. Из корня локального каталога проекта запустите firebase emulators:start .
  3. Выполнение вызовов из кода прототипа вашего приложения с использованием SDK платформы базы данных реального времени, как обычно, или с помощью REST API базы данных реального времени.

Доступно подробное пошаговое руководство, включающее базу данных реального времени и облачные функции . Вам также следует ознакомиться с введением 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, вы можете использовать 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 синхронизирует эти данные с удаленными серверами баз данных и с другими клиентами по принципу «максимально возможно».

В результате все записи в базу данных немедленно вызывают локальные события, прежде чем какие-либо данные будут записаны на сервер. Это означает, что ваше приложение остается отзывчивым независимо от задержки в сети или подключения.

После восстановления подключения ваше приложение получает соответствующий набор событий, чтобы клиент синхронизировался с текущим состоянием сервера без необходимости писать какой-либо специальный код.

Мы поговорим подробнее о поведении в автономном режиме в разделе Узнайте больше о возможностях онлайн и офлайн .

Следующие шаги