Đọc và ghi dữ liệu trên web

(Không bắt buộc) Tạo nguyên mẫu và kiểm thử bằng Firebase Local Emulator Suite

Trước khi nói về cách ứng dụng đọc và ghi vào Realtime Database, hãy giới thiệu một bộ công cụ mà bạn có thể sử dụng để tạo nguyên mẫu và kiểm thử chức năng Realtime Database: Firebase Local Emulator Suite. Nếu bạn đang thử dùng dữ liệu khác mô hình, tối ưu hoá các quy tắc bảo mật hoặc nỗ lực tìm ra cách tiết kiệm chi phí để tương tác với hệ thống phụ trợ, nhờ đó có thể làm việc tại địa phương mà không triển khai dịch vụ trực tiếp có thể là ý tưởng hay.

Trình mô phỏng Realtime Database là một phần của Local Emulator Suite, cho phép ứng dụng của bạn tương tác với cấu hình và nội dung cơ sở dữ liệu được mô phỏng, như cũng như các tài nguyên dự án được mô phỏng (không bắt buộc) (các hàm, cơ sở dữ liệu khác, và quy tắc bảo mật).

Bạn chỉ cần thực hiện vài bước để sử dụng trình mô phỏng Realtime Database:

  1. Thêm một dòng mã vào cấu hình kiểm thử của ứng dụng để kết nối với trình mô phỏng.
  2. Chạy firebase emulators:start từ thư mục gốc của dự án cục bộ.
  3. Gọi điện từ mã nguyên mẫu của ứng dụng bằng nền tảng Realtime Database SDK như thường lệ hoặc sử dụng API REST Realtime Database.

Có sẵn hướng dẫn chi tiết liên quan đến Realtime DatabaseCloud Functions. Bạn cũng nên xem hướng dẫn về Local Emulator Suite.

Lấy tham chiếu cơ sở dữ liệu

Để đọc hoặc ghi dữ liệu từ cơ sở dữ liệu, bạn cần có một thực thể của firebase.database.Reference:

Web

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web

var database = firebase.database();

Ghi dữ liệu

Tài liệu này trình bày các thông tin cơ bản về việc truy xuất dữ liệu, cách sắp xếp và lọc dữ liệu Dữ liệu Firebase.

Bạn có thể truy xuất dữ liệu Firebase bằng cách đính kèm một trình nghe không đồng bộ vào một firebase.database.Reference. Trình nghe này được kích hoạt một lần cho trạng thái ban đầu của dữ liệu và nhắc lại bất cứ khi nào dữ liệu thay đổi.

Các thao tác ghi cơ bản

Đối với các thao tác ghi cơ bản, bạn có thể sử dụng set() để lưu dữ liệu vào một thuộc tính cụ thể thay thế mọi dữ liệu hiện có tại đường dẫn đó. Ví dụ: một ứng dụng viết blog trên mạng xã hội có thể thêm người dùng bằng set() như sau:

Web

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

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

Việc sử dụng set() sẽ ghi đè dữ liệu ở vị trí được chỉ định, bao gồm bất kỳ dữ liệu con nào nút.

Đọc dữ liệu

Theo dõi các sự kiện giá trị

Để đọc dữ liệu tại một đường dẫn và theo dõi các thay đổi, hãy dùng onValue() để quan sát sự kiện. Bạn có thể sử dụng sự kiện này để đọc ảnh chụp nhanh tĩnh về nội dung tại đường dẫn cụ thể, như đã tồn tại tại thời điểm xảy ra sự kiện. Phương thức này được kích hoạt một lần khi trình nghe được đính kèm và lặp lại mỗi khi dữ liệu (bao gồm cả phần tử con) thay đổi. Lệnh gọi lại sự kiện được truyền một ảnh chụp nhanh chứa tất cả dữ liệu ở vị trí đó, bao gồm cả dữ liệu của trẻ. Nếu không có dữ liệu, Tổng quan nhanh sẽ trả về false khi bạn gọi exists()null khi bạn gọi val() trên đó.

Ví dụ sau minh hoạ một ứng dụng viết blog trên mạng xã hội truy xuất số sao của một bài đăng trong cơ sở dữ liệu:

Web

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

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

Trình nghe sẽ nhận được một snapshot chứa dữ liệu theo phương thức đã chỉ định vị trí trong cơ sở dữ liệu tại thời điểm diễn ra sự kiện. Bạn có thể truy xuất dữ liệu trong snapshot bằng phương thức val().

Đọc dữ liệu một lần

Đọc dữ liệu một lần bằng get()

SDK này được thiết kế để quản lý các hoạt động tương tác với máy chủ cơ sở dữ liệu, cho dù ứng dụng của bạn đang ở chế độ trực tuyến hay ngoại tuyến.

Thông thường, bạn nên sử dụng các kỹ thuật sự kiện giá trị được mô tả ở trên để đọc để nhận thông báo về nội dung cập nhật đối với dữ liệu từ phần phụ trợ. Trình nghe các kỹ thuật giúp giảm mức sử dụng và thanh toán, đồng thời được tối ưu hoá để cung cấp cho người dùng trải nghiệm tốt nhất khi họ trực tuyến và ngoại tuyến.

Nếu chỉ cần dữ liệu một lần, bạn có thể sử dụng get() để có ảnh chụp nhanh khỏi cơ sở dữ liệu. Nếu vì bất kỳ lý do gì, get() không thể trả về máy chủ máy khách sẽ thăm dò bộ nhớ đệm của bộ nhớ cục bộ và trả về lỗi nếu giá trị vẫn không tìm thấy giá trị.

Việc sử dụng get() không cần thiết có thể làm tăng mức sử dụng băng thông và dẫn đến mất Bạn có thể ngăn chặn điều này bằng cách sử dụng trình nghe theo thời gian thực như minh hoạ ở trên.

Web

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

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

Đọc dữ liệu một lần bằng trình quan sát

Trong một số trường hợp, bạn có thể muốn trả về giá trị từ bộ nhớ đệm cục bộ ngay lập tức, thay vì kiểm tra giá trị đã cập nhật trên máy chủ. Trong các bạn có thể sử dụng once() để lấy dữ liệu từ bộ nhớ đệm của ổ đĩa cục bộ ngay lập tức.

Điều này hữu ích đối với dữ liệu chỉ cần tải một lần và dự kiến sẽ không thay đổi thường xuyên hoặc cần chủ động lắng nghe. Ví dụ: ứng dụng viết blog trong các ví dụ trước sẽ sử dụng phương thức này để tải hồ sơ của người dùng khi họ bắt đầu viết một bài đăng mới:

Web

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

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

Cập nhật hoặc xoá dữ liệu

Cập nhật các trường cụ thể

Để đồng thời ghi vào các nút con cụ thể của một nút mà không ghi đè các nút con khác, hãy sử dụng phương thức update().

Khi gọi update(), bạn có thể cập nhật các giá trị con ở cấp thấp hơn bằng cách chỉ định đường dẫn cho khoá. Nếu dữ liệu được lưu trữ ở nhiều vị trí để mở rộng quy mô tốt hơn, bạn có thể cập nhật tất cả các phiên bản của dữ liệu đó bằng cách sử dụng ngừng sử dụng dữ liệu.

Ví dụ: một ứng dụng blog xã hội có thể tạo một bài đăng và đồng thời cập nhật bài đăng đó vào nguồn cấp dữ liệu hoạt động gần đây và nguồn cấp dữ liệu hoạt động của người dùng đăng bằng cách sử dụng mã như sau:

Web

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

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

Ví dụ này sử dụng push() để tạo bài đăng trong nút chứa bài đăng cho tất cả người dùng trong /posts/$postid, đồng thời truy xuất khoá. Sau đó, khoá này có thể được dùng để tạo mục nhập thứ hai trong bài đăng của người dùng tại /user-posts/$userid/$postid.

Khi sử dụng các đường dẫn này, bạn có thể cập nhật đồng thời nhiều vị trí trong cây JSON với một lệnh gọi duy nhất đến update(), chẳng hạn như trong ví dụ này tạo bài đăng mới ở cả hai vị trí. Cập nhật đồng thời theo cách này rất quan trọng: tất cả cập nhật thành công hoặc tất cả cập nhật đều không thành công.

Thêm lệnh gọi lại khi hoàn tất

Nếu muốn biết thời điểm dữ liệu của bạn đã được xác nhận, bạn có thể thêm lệnh gọi lại hoàn thành. Cả set()update() đều không bắt buộc phải hoàn thành lệnh gọi lại được gọi khi quá trình ghi được xác nhận vào cơ sở dữ liệu. Nếu lệnh gọi không thành công, lệnh gọi lại sẽ được truyền một đối tượng lỗi cho biết lý do xảy ra lỗi.

Web

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

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

Xóa dữ liệu

Cách đơn giản nhất để xoá dữ liệu là gọi remove() trên một tham chiếu đến vị trí của dữ liệu đó.

Bạn cũng có thể xoá bằng cách chỉ định null làm giá trị cho lần ghi khác chẳng hạn như set() hoặc update(). Bạn có thể sử dụng kỹ thuật này với update() để xoá nhiều phần tử con trong một lệnh gọi API.

Nhận một Promise

Để biết thời điểm dữ liệu của bạn được cam kết với máy chủ Firebase Realtime Database, bạn có thể sử dụng Promise. Cả set()update() đều có thể trả về một Promise mà bạn có thể sử dụng để biết khi nào ghi được cam kết vào cơ sở dữ liệu.

Tách trình nghe

Các lệnh gọi lại sẽ bị xoá bằng cách gọi phương thức off() trên Tài liệu tham khảo về cơ sở dữ liệu Firebase.

Bạn có thể xoá một trình nghe bằng cách truyền trình nghe đó dưới dạng tham số đến off(). Việc gọi off() trên vị trí không có đối số sẽ xoá tất cả trình nghe tại vị trí đó vị trí.

Việc gọi off() trên trình nghe cha mẹ sẽ không tự động xoá trình nghe đã đăng ký trên các nút con; off() cũng phải được gọi trên mọi trình nghe con để loại bỏ lệnh gọi lại.

Lưu dữ liệu dưới dạng giao dịch

Khi làm việc với dữ liệu có thể bị hỏng do đồng thời sửa đổi, chẳng hạn như bộ đếm tăng dần, bạn có thể sử dụng hoạt động giao dịch. Bạn có thể cung cấp cho thao tác này một hàm cập nhật và một tuỳ chọn lệnh gọi lại hoàn thành. Hàm cập nhật lấy trạng thái hiện tại của dữ liệu làm một đối số và trả về trạng thái mong muốn mới mà bạn muốn viết. Nếu một khách hàng khác ghi vào vị trí đó trước khi giá trị mới của bạn được áp dụng thành công thì hàm cập nhật của bạn sẽ được gọi lại bằng giá trị hiện tại mới và quá trình ghi được thử lại.

Ví dụ: trong ứng dụng viết blog xã hội ví dụ, bạn có thể cho phép người dùng gắn dấu sao và bỏ gắn dấu sao bài đăng, đồng thời theo dõi số sao mà một bài đăng đã nhận được như sau:

Web

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

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

Việc sử dụng giao dịch sẽ giúp tránh việc tính số sao không chính xác nếu có nhiều người dùng gắn dấu sao cùng một bài đăng cùng một lúc hoặc ứng dụng khách có dữ liệu cũ. Nếu giao dịch bị từ chối, máy chủ trả về giá trị hiện tại đối với máy khách, lệnh này sẽ chạy lại giao dịch với giá trị đã cập nhật. Thao tác này lặp lại cho đến khi giao dịch được chấp nhận hoặc bạn huỷ giao dịch.

Số gia ở phía máy chủ nguyên tử

Trong trường hợp sử dụng trên, chúng ta viết hai giá trị vào cơ sở dữ liệu: mã nhận dạng của người dùng gắn dấu sao/bỏ gắn dấu sao bài đăng và số sao tăng lên. Nếu chúng tôi đã biết rằng người dùng đang gắn dấu sao bài đăng, chúng ta có thể sử dụng gia số nguyên tử thay vì một giao dịch.

Web

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

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

Mã này không sử dụng hoạt động giao dịch nên không tự động nhận được chạy lại nếu có thông tin cập nhật gây xung đột. Tuy nhiên, vì thao tác tăng giá trị diễn ra trực tiếp trên máy chủ cơ sở dữ liệu, nên không có khả năng xảy ra xung đột.

Nếu bạn muốn phát hiện và từ chối xung đột dành riêng cho ứng dụng, chẳng hạn như một người dùng gắn dấu sao bài đăng mà họ đã gắn dấu sao trước đó, bạn nên viết quy tắc bảo mật cho trường hợp sử dụng đó.

Làm việc với dữ liệu khi không có mạng

Nếu máy khách bị mất kết nối mạng, ứng dụng của bạn sẽ tiếp tục hoạt động chính xác.

Mỗi ứng dụng được kết nối với cơ sở dữ liệu Firebase đều duy trì phiên bản nội bộ riêng của mọi dữ liệu đang hoạt động. Khi được ghi, dữ liệu sẽ được ghi vào phiên bản cục bộ này đầu tiên. Sau đó, ứng dụng Firebase sẽ đồng bộ hoá dữ liệu đó với cơ sở dữ liệu từ xa máy chủ của bạn và với các ứng dụng khách khác với "nỗ lực tối đa" cơ sở.

Do đó, tất cả hoạt động ghi vào cơ sở dữ liệu sẽ kích hoạt các sự kiện cục bộ ngay lập tức, trước khi mọi dữ liệu đều được ghi vào máy chủ. Điều này có nghĩa là ứng dụng của bạn vẫn phản hồi bất kể độ trễ hoặc khả năng kết nối mạng.

Sau khi kết nối được thiết lập lại, ứng dụng của bạn sẽ nhận được nhóm các sự kiện để máy khách đồng bộ hóa với trạng thái máy chủ hiện tại mà không phải viết bất kỳ mã tuỳ chỉnh nào.

Chúng ta sẽ thảo luận thêm về hành vi ngoại tuyến trong phần Tìm hiểu thêm về các tính năng trực tuyến và ngoại tuyến.

Các bước tiếp theo