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

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.

(Tùy chọn) Nguyên mẫu và thử nghiệm với Firebase Local Emulator Suite

Trước khi nói về cách ứng dụng của bạn đọc và ghi vào Cơ sở dữ liệu thời gian thực, hãy giới thiệu một bộ công cụ bạn có thể sử dụng để tạo mẫu và thử nghiệm chức năng Cơ sở dữ liệu thời gian thực: Firebase Local Emulator Suite. Nếu bạn đang thử các mô hình dữ liệu khác nhau, tối ưu hóa các quy tắc bảo mật của mình hoặc làm việc để tìm ra cách hiệu quả nhất về chi phí để tương tác với back-end, có thể làm việc cục bộ mà không cần triển khai các dịch vụ trực tiếp có thể là một ý tưởng tuyệt vời.

Trình giả lập Cơ sở dữ liệu thời gian thực là một phần của Bộ mô phỏng cục bộ, 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, cũng như tùy chọn các tài nguyên dự án được mô phỏng của bạn (chức năng, cơ sở dữ liệu khác và quy tắc bảo mật).

Sử dụng trình giả lập Cơ sở dữ liệu thời gian thực chỉ bao gồm một vài bước:

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

Có sẵn hướng dẫn chi tiết liên quan đến Cơ sở dữ liệu thời gian thực và Chức năng đám mây . Bạn cũng nên xem phần giới thiệu Local Emulator Suite .

Nhận 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 một phiên bản của firebase.database.Reference .

Web version 9

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web version 8

var database = firebase.database();

Viết dữ liệu

Tài liệu này trình bày những kiến ​​thức cơ bản về truy xuất dữ liệu cũng như cách sắp xếp và lọc dữ liệu Firebase.

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

Các thao tác viết 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 tham chiếu được chỉ định, thay thế bất kỳ dữ liệu hiện có nào tại đường dẫn đó. Ví dụ: một ứng dụng viết blog xã hội có thể thêm một người dùng với set() như sau:

Web version 9

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 version 8

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

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

Đọc dữ liệu

Lắng nghe các sự kiện giá trị

Để đọc dữ liệu tại một đường dẫn và lắng nghe các thay đổi, hãy sử dụng onValue() để quan sát các sự kiện. Bạn có thể sử dụng sự kiện này để đọc ảnh chụp nhanh tĩnh của nội dung tại một đường dẫn nhất định, vì chúng đã tồn tại tại thời điểm diễn ra sự kiện. Phương thức này được kích hoạt một lần khi người 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. Sự kiện gọi lại được chuyển qua một ảnh chụp nhanh chứa tất cả dữ liệu tại vị trí đó, bao gồm cả dữ liệu con. Nếu không có dữ liệu, ảnh chụp nhanh sẽ trả về giá trị false khi bạn gọi exists()null khi bạn gọi val() trên đó.

Ví dụ sau minh họa một ứng dụng viết blog xã hội lấy số lượng sao của một bài đăng từ cơ sở dữ liệu:

Web version 9

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 version 8

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

Người nghe nhận được một snapshot có chứa dữ liệu tại vị trí được chỉ định 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 với get ()

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

Nói chung, bạn nên sử dụng các kỹ thuật sự kiện giá trị được mô tả ở trên để đọc dữ liệu nhằm nhận thông báo về các cập nhật dữ liệu từ chương trình phụ trợ. Các kỹ thuật lắng nghe làm giảm việc sử dụng và thanh toán của bạn, đồng thời được tối ưu hóa để mang đến cho người dùng của bạn trải nghiệm tốt nhất khi họ trực tuyến và ngoại tuyến.

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

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

Web version 9

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 version 8

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 với một người quan sát

Trong một số trường hợp, bạn có thể muốn trả lại giá trị từ bộ đệ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 những trường hợp đó, bạn có thể sử dụng once() để lấy dữ liệu từ bộ nhớ cache trên đĩa cục bộ ngay lập tức.

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

Web version 9

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 version 8

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 xóa dữ liệu

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

Để ghi đồng thời cho các nút con cụ thể 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 khóa. 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 quạt dữ liệu .

Ví dụ: một ứng dụng viết blog xã hội có thể tạo một bài đăng và đồng thời cập nhật nó 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ài bằng cách sử dụng mã như sau:

Web version 9

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 version 8

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 một bài đăng trong nút chứa bài đăng cho tất cả người dùng tại /posts/$postid và đồng thời lấy khóa. Sau đó, khóa có thể được sử dụng để tạo mục nhập thứ hai trong các bài đăng của người dùng tại /user-posts/$userid/$postid .

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

Thêm một cuộc gọi lại hoàn thành

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

Web version 9

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 version 8

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 để xóa 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ể xóa bằng cách chỉ định null làm giá trị cho một thao tác ghi khác như set() hoặc update() . Bạn có thể sử dụng kỹ thuật này với update() để xóa nhiều phần tử con trong một lệnh gọi API.

Nhận một Promise

Để biết khi nào dữ liệu của bạn được cam kết với máy chủ Cơ sở dữ liệu thời gian thực của Firebase, 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 việc ghi được cam kết vào cơ sở dữ liệu.

Tách người nghe

Các lệnh gọi lại bị xóa bằng cách gọi phương thức off() trên tham chiếu cơ sở dữ liệu Firebase của bạn.

Bạn có thể loại bỏ một trình nghe bằng cách chuyển nó dưới dạng tham số cho off() . Gọi off() trên vị trí không có đối số sẽ xóa tất cả người nghe tại vị trí đó.

Việc gọi off() trên một trình nghe chính không tự động xóa các trình nghe đã đăng ký trên các nút con của nó; off() cũng phải được gọi trên bất kỳ bộ nghe con nào để 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 sửa đổi đồng thời, chẳng hạn như bộ đếm tăng dần, bạn có thể sử dụng thao tác giao dịch . Bạn có thể cung cấp cho thao tác này một chức năng cập nhật và một lệnh gọi lại hoàn thành tùy chọn. Hàm cập nhật lấy trạng thái hiện tại của dữ liệu làm đối số và trả về trạng thái mong muốn mới mà bạn muốn ghi. Nếu một ứng dụng khách khác ghi vào vị trí trước khi giá trị mới của bạn được ghi thành công, thì hàm cập nhật của bạn sẽ được gọi lại với giá trị hiện tại mới và quá trình ghi sẽ được thử lại.

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

Web version 9

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 version 8

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 một giao dịch ngăn không cho số lượng sao không chính xác nếu nhiều người dùng gắn dấu sao cho cùng một bài đăng cùng một lúc hoặc khách hàng có dữ liệu cũ. Nếu giao dịch bị từ chối, máy chủ sẽ trả về giá trị hiện tại cho máy khách, giá trị này sẽ chạy lại giao dịch với giá trị được cập nhật. Điều này lặp lại cho đến khi giao dịch được chấp nhận hoặc bạn hủy bỏ giao dịch.

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

Trong trường hợp sử dụng ở trên, chúng tôi đang ghi hai giá trị vào cơ sở dữ liệu: ID của người dùng gắn dấu sao / bỏ dấu sao bài đăng và số sao tăng dầ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 tôi có thể sử dụng hoạt động tăng nguyên tử thay vì giao dịch.

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 thao tác giao dịch, vì vậy nó không tự động chạy lại nếu có bản cập nhật xung đột. Tuy nhiên, vì hoạt động tăng dần xảy 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 các xung đột dành riêng cho ứng dụng, chẳng hạn như người dùng gắn dấu sao cho một bài đăng mà họ đã gắn dấu sao trước đó, bạn nên viết các quy tắc bảo mật tùy chỉnh cho trường hợp sử dụng đó.

Làm việc với dữ liệu ngoại tuyến

Nếu ứng dụng mất kết nối mạng, ứng dụng của bạn sẽ tiếp tục hoạt động bình thường.

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

Kết quả là, tất cả các lần 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 bất kỳ dữ liệu nào được ghi vào máy chủ. Điều này có nghĩa là ứng dụng của bạn vẫn đáp ứng bất kể độ trễ mạng hoặc kết nối.

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

Chúng ta sẽ nói nhiều hơn về hành vi ngoại tuyến trong Tìm hiểu thêm về các khả năng trực tuyến và ngoại tuyến ..

Bước tiếp theo