อ่านและเขียนข้อมูลบนเว็บ

(ไม่บังคับ) สร้างต้นแบบและทดสอบกับ Firebase Local Emulator Suite

ก่อนที่จะพูดถึงวิธีที่แอปของคุณอ่านและเขียนไปยัง Realtime Database เรามาแนะนำชุดเครื่องมือที่คุณสามารถใช้สร้างต้นแบบและทดสอบ Realtime Database กัน ฟังก์ชัน: Firebase Local Emulator Suite หากคุณกำลังลองใช้ข้อมูลอื่น โมเดล, การเพิ่มประสิทธิภาพของกฎความปลอดภัย หรือการทำงานเพื่อค้นหา วิธีที่คุ้มค่าในการโต้ตอบกับระบบหลังบ้าน ทำให้สามารถทำงานในองค์กรได้ โดยไม่ต้องใช้บริการแบบสดอาจเป็นความคิดที่ดี

โปรแกรมจำลอง Realtime Database เป็นส่วนหนึ่งของ Local Emulator Suite ซึ่งช่วยให้แอปโต้ตอบกับเนื้อหาและการกำหนดค่าฐานข้อมูลที่จำลอง รวมถึงทรัพยากรโปรเจ็กต์ที่จำลอง (ฟังก์ชัน ฐานข้อมูลอื่นๆ และกฎการรักษาความปลอดภัย) ได้ด้วยหากต้องการ

การใช้โปรแกรมจำลอง Realtime Database มีเพียงไม่กี่ขั้นตอนดังนี้

  1. การเพิ่มบรรทัดโค้ดลงในการกำหนดค่าการทดสอบของแอปเพื่อเชื่อมต่อกับโปรแกรมจำลอง
  2. จากรูทของไดเรกทอรีโปรเจ็กต์ในเครื่อง โดยเรียกใช้ firebase emulators:start
  3. การเรียกใช้จากโค้ดโปรโตไทป์ของแอปโดยใช้ Realtime Database Platform SDK ตามปกติ หรือใช้ Realtime Database REST API

โปรดดูคำแนะนำโดยละเอียดเกี่ยวกับ Realtime Database และ Cloud Functions นอกจากนี้คุณควรดูข้อมูลเบื้องต้นเกี่ยวกับ Local Emulator Suite ด้วย

รับการอ้างอิงฐานข้อมูล

หากต้องการอ่านหรือเขียนข้อมูลจากฐานข้อมูล คุณต้องมีอินสแตนซ์ firebase.database.Reference:

Web

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web

var database = firebase.database();

เขียนข้อมูล

เอกสารนี้ครอบคลุมข้อมูลเบื้องต้นเกี่ยวกับการดึงข้อมูล รวมถึงวิธีจัดเรียงและกรองข้อมูล Firebase

ระบบจะดึงข้อมูล Firebase โดยการแนบ Listener แบบไม่พร้อมกันเข้ากับ firebase.database.Reference Listener จะถูกทริกเกอร์ 1 ครั้งสำหรับ สถานะเริ่มต้นของข้อมูล และจะทำอีกครั้งเมื่อข้อมูลเปลี่ยนแปลง

การดำเนินการเขียนพื้นฐาน

สำหรับการดำเนินการเขียนพื้นฐาน คุณสามารถใช้ set() เพื่อบันทึกข้อมูลไปยัง โดยแทนที่ข้อมูลที่มีอยู่ในเส้นทางนั้น เช่น โซเชียล แอปพลิเคชันการเขียนบล็อกอาจเพิ่มผู้ใช้ด้วย set() ดังนี้

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

การใช้ set() จะเขียนทับข้อมูลในตำแหน่งที่ระบุ รวมถึงโหนดย่อย

อ่านข้อมูล

รอรับเหตุการณ์ที่มีมูลค่า

หากต้องการอ่านข้อมูลที่เส้นทางและฟังการเปลี่ยนแปลง ให้ใช้ onValue() เพื่อสังเกตการณ์ กิจกรรม คุณสามารถใช้เหตุการณ์นี้เพื่ออ่านสแนปชอตแบบภาพนิ่งของเนื้อหาใน ตามที่มีอยู่ ณ เวลาที่เกิดเหตุการณ์ วิธีนี้ จะทริกเกอร์ 1 ครั้งเมื่อมีการแนบ Listener และอีกครั้งทุกครั้งที่ข้อมูลรวมถึงเด็กเปลี่ยนแปลง Callback ของเหตุการณ์จะส่งผ่านสแนปชอตที่มี ข้อมูลทั้งหมดในตำแหน่งนั้น รวมถึงข้อมูลย่อยด้วย หากไม่มีข้อมูล ระบบจะ สแนปชอตจะแสดง false เมื่อคุณเรียกใช้ exists() และ null เมื่อคุณเรียกใช้ val() ในนั้น

ตัวอย่างต่อไปนี้แสดงแอปพลิเคชันการเขียนบล็อกโซเชียลที่เรียก จำนวนดาวของโพสต์จากฐานข้อมูล:

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

Listener จะได้รับ snapshot ที่มีข้อมูลตามที่ระบุ ในฐานข้อมูล ณ เวลาที่เกิดเหตุการณ์ คุณสามารถเรียกดูข้อมูล ใน snapshot ด้วยเมธอด val()

อ่านข้อมูลครั้งเดียว

อ่านข้อมูลเพียงครั้งเดียวด้วย get()

SDK ออกแบบมาเพื่อจัดการการโต้ตอบกับเซิร์ฟเวอร์ฐานข้อมูล ไม่ว่าแอปจะออนไลน์หรือออฟไลน์ก็ตาม

โดยทั่วไป คุณควรใช้เทคนิคเหตุการณ์มูลค่าที่อธิบายไว้ข้างต้นในการอ่าน เพื่อรับการแจ้งเตือนเมื่อมีการอัปเดตข้อมูลจากแบ็กเอนด์ ผู้ฟัง ช่วยลดการใช้งานและการเรียกเก็บเงิน และเพิ่มประสิทธิภาพเพื่อ มอบประสบการณ์ที่ดีที่สุดแก่ผู้ใช้ทั้งออนไลน์และออฟไลน์

หากต้องการข้อมูลเพียงครั้งเดียว คุณสามารถใช้ get() เพื่อดูภาพรวมของ จากฐานข้อมูล หาก get() แสดงค่าของเซิร์ฟเวอร์ไม่ได้ไม่ว่าด้วยเหตุผลใดก็ตาม ไคลเอ็นต์จะตรวจสอบแคชพื้นที่เก็บข้อมูลในเครื่องและแสดงข้อผิดพลาดหากยังคงไม่พบค่า

การใช้ get() โดยไม่จำเป็นจะเพิ่มการใช้แบนด์วิดท์และทำให้สูญเสีย ซึ่งป้องกันได้โดยใช้ Listener แบบเรียลไทม์ดังที่แสดงด้านบน

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

อ่านข้อมูลครั้งเดียวกับผู้สังเกตการณ์

ในบางกรณี คุณอาจต้องการให้คืนค่าจากแคชในเครื่อง ทันที แทนที่จะตรวจสอบค่าที่อัปเดตแล้วบนเซิร์ฟเวอร์ ในการแสดงผลเหล่านั้น คุณสามารถใช้ once() เพื่อรับข้อมูลจากดิสก์แคชในเครื่องทันที

วิธีนี้เป็นประโยชน์สําหรับข้อมูลที่ต้องโหลดเพียงครั้งเดียวและไม่คาดว่าจะโหลด เปลี่ยนบ่อยหรือต้องใช้การฟังอย่างต่อเนื่อง เช่น แอปการเขียนบล็อก ในตัวอย่างก่อนหน้านี้ใช้วิธีนี้เพื่อโหลดโปรไฟล์ของผู้ใช้ เริ่มเขียนโพสต์ใหม่:

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

การอัปเดตหรือลบข้อมูล

อัปเดตช่องข้อมูลที่เฉพาะเจาะจง

เพื่อเขียนไปยังโหนดย่อยที่ต้องการพร้อมกันโดยไม่เขียนทับโหนดอื่น โหนดย่อย ให้ใช้เมธอด update()

เมื่อโทรหา update() คุณจะอัปเดตค่าย่อยระดับล่างได้โดย โดยระบุเส้นทางของคีย์นี้ หากมีการจัดเก็บข้อมูลในหลายตำแหน่งเพื่อปรับขนาด คุณจะสามารถอัปเดตอินสแตนซ์ทั้งหมดของข้อมูลนั้นได้โดยใช้ การขยายข้อมูล

เช่น แอปบล็อกบนโซเชียลอาจสร้างโพสต์และอัปเดตไปพร้อมๆ กัน ไปยังฟีดกิจกรรมล่าสุดและฟีดกิจกรรมของผู้ใช้ที่โพสต์โดยใช้ ดังนี้

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

ตัวอย่างนี้ใช้ push() เพื่อสร้างโพสต์ในโหนดที่มีโพสต์สำหรับ ผู้ใช้ทั้งหมดที่ /posts/$postid และดึงคีย์ไปพร้อมๆ กัน จากนั้นจะใช้คีย์ดังกล่าวเพื่อสร้างรายการที่ 2 ในโพสต์ของผู้ใช้ที่ /user-posts/$userid/$postid ได้

เมื่อใช้เส้นทางเหล่านี้ คุณจะอัปเดตตำแหน่งหลายแห่งในต้นไม้ JSON ได้พร้อมกันด้วยการเรียกใช้ update() เพียงครั้งเดียว เช่น ตัวอย่างนี้สร้างโพสต์ใหม่ในทั้ง 2 ตำแหน่ง การอัปเดตพร้อมกันในลักษณะนี้ เป็นแบบอะตอมมิก นั่นคือการอัปเดตทั้งหมดสำเร็จหรือล้มเหลว

เพิ่ม Callback ที่เสร็จสมบูรณ์

หากต้องการทราบว่ามีการบันทึกข้อมูลแล้วเมื่อใด คุณสามารถเพิ่มการเรียกกลับเมื่อเสร็จสมบูรณ์ได้ ทั้ง set() และ update() จะเลือกดำเนินการให้เสร็จสมบูรณ์หรือไม่ก็ได้ Callback ที่เรียกใช้เมื่อมีการคอมมิตการเขียนกับฐานข้อมูล ถ้า การโทรไม่สำเร็จ การเรียกกลับถูกส่งผ่าน ที่ระบุสาเหตุของความล้มเหลวขึ้น

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

ลบข้อมูล

วิธีที่ง่ายที่สุดในการลบข้อมูลคือการเรียกใช้ remove() ตามการอ้างอิง ตำแหน่งของข้อมูลนั้น

ก็ลบได้ด้วยการระบุ null เป็นค่าสำหรับการเขียนอื่น เช่น set() หรือ update() คุณใช้เทคนิคนี้ได้ กับ update() เพื่อลบเด็กหลายคนในการเรียก API ครั้งเดียว

รับ Promise

หากต้องการทราบว่าเมื่อใดที่ข้อมูลของคุณผูกอยู่กับเซิร์ฟเวอร์ Firebase Realtime Database คุณต้องทำดังนี้ คุณสามารถใช้ Promise ทั้ง set() และ update() สามารถแสดงผล Promise ที่คุณสามารถใช้เพื่อดูว่ามีการเขียนข้อมูลไปยังฐานข้อมูลแล้วเมื่อใด

ปลดผู้ฟังออก

ระบบจะนำการติดต่อกลับออกโดยการเรียกใช้เมธอด off() บน การอ้างอิงฐานข้อมูล Firebase

คุณนำ Listener เดียวออกได้โดยการส่งผ่านเป็นพารามิเตอร์ไปยัง off() การเรียกใช้ off() ในตำแหน่งที่ไม่มีอาร์กิวเมนต์จะนำ Listener ทั้งหมดออกจากตำแหน่งนั้น ตำแหน่งนั้น

การเรียกใช้ off() บน Listener หลักไม่ได้ นำ Listener ที่ลงทะเบียนไว้ในโหนดย่อยออกโดยอัตโนมัติ ต้องเรียก off() ใน Listener ที่เป็นเด็กด้วย เพื่อนำการติดต่อกลับ

บันทึกข้อมูลเป็นธุรกรรม

เมื่อทำงานกับข้อมูลที่อาจเสียหายจากการเชื่อมต่อพร้อมกัน เช่น ตัวนับที่เพิ่มขึ้น คุณสามารถใช้ การดำเนินการธุรกรรม คุณสามารถกำหนดให้การทำงานนี้มีฟังก์ชันการอัปเดต และฟังก์ชันที่ไม่บังคับ Callback ที่เสร็จสมบูรณ์ ฟังก์ชันอัปเดตจะใช้สถานะปัจจุบันของข้อมูลเป็น อาร์กิวเมนต์ และแสดงสถานะใหม่ที่ต้องการเขียน ถ้า ไคลเอ็นต์อื่นเขียนไปยังตำแหน่งก่อนที่ค่าใหม่จะสำเร็จ เขียน ระบบจะเรียกฟังก์ชันอัปเดตอีกครั้งพร้อมด้วยค่าปัจจุบันใหม่ และ ระบบจะพยายามเขียนอีกครั้ง

เช่น ในตัวอย่างแอปการเขียนบล็อกโซเชียล คุณสามารถอนุญาตให้ผู้ใช้ ติดดาวและเลิกติดดาวโพสต์ และติดตามจำนวนดาวที่โพสต์ได้รับ ดังนี้

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

การใช้ธุรกรรมจะป้องกันไม่ให้จำนวนดาวไม่ถูกต้องหากมีหลายครั้ง ผู้ใช้ติดดาวโพสต์เดียวกันในเวลาเดียวกัน หรือไคลเอ็นต์มีข้อมูลเก่า หาก ธุรกรรมถูกปฏิเสธ เซิร์ฟเวอร์ส่งคืน มูลค่าปัจจุบันให้กับลูกค้า ซึ่งจะเรียกใช้ธุรกรรมอีกครั้งด้วย ค่าที่อัปเดต การดำเนินการซ้ำจนกว่าธุรกรรมจะได้รับการยอมรับหรือคุณล้มเลิก ธุรกรรมนั้น

ส่วนเพิ่มจากฝั่งเซิร์ฟเวอร์แบบอะตอม

ในกรณีการใช้งานด้านบน เราจะเขียนค่า 2 ค่าลงในฐานข้อมูลซึ่งได้แก่ รหัสของ ผู้ใช้ที่ติดดาว/ยกเลิกการติดดาวโพสต์ และจำนวนดาวที่เพิ่มขึ้น หากเราทราบอยู่แล้วว่าผู้ใช้ติดดาวโพสต์ เราสามารถใช้การดำเนินการการเพิ่มแบบอะตอมแทนธุรกรรมได้

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

โค้ดนี้ไม่ได้ใช้การดำเนินการธุรกรรม ดังนั้นจึงไม่ได้รับโดยอัตโนมัติ ให้เรียกใช้อีกครั้งหากมีการอัปเดตที่ขัดแย้งกัน อย่างไรก็ตาม เนื่องจากการดำเนินการเพิ่มเกิดขึ้นในเซิร์ฟเวอร์ฐานข้อมูลโดยตรง จึงไม่มีความขัดแย้ง

หากต้องการตรวจหาและปฏิเสธความขัดแย้งเฉพาะแอปพลิเคชัน เช่น ผู้ใช้ ติดดาวโพสต์ที่ติดดาวอยู่แล้ว คุณควรเขียนแบบกำหนดเอง กฎความปลอดภัยสำหรับกรณีการใช้งานนั้น

ทำงานกับข้อมูลแบบออฟไลน์

หากไคลเอ็นต์ขาดการเชื่อมต่อเครือข่าย แอปจะยังคงทํางานต่อไปอย่างถูกต้อง

ลูกค้าทุกรายที่เชื่อมต่อกับฐานข้อมูล Firebase จะมีเวอร์ชันภายในของตนเอง ของข้อมูลที่ใช้งานอยู่ เมื่อมีการเขียนข้อมูล ระบบจะเขียนข้อมูลลงในเวอร์ชันในเครื่องนี้ก่อน จากนั้นไคลเอ็นต์ Firebase จะซิงค์ข้อมูลนั้นกับฐานข้อมูลระยะไกล เซิร์ฟเวอร์และไคลเอ็นต์อื่นๆ อย่าง "สุดความสามารถ" พื้นฐาน

ด้วยเหตุนี้ การเขียนทั้งหมดลงในฐานข้อมูลจึงทริกเกอร์เหตุการณ์ในเครื่องทันทีก่อนที่จะมีการเขียนข้อมูลลงในเซิร์ฟเวอร์ ซึ่งหมายความว่าแอปของคุณจะยัง ตอบสนองตามอุปกรณ์โดยไม่คำนึงถึงเวลาในการตอบสนองหรือการเชื่อมต่อของเครือข่าย

เมื่อเชื่อมต่ออินเทอร์เน็ตอีกครั้งแล้ว แอปจะได้รับชุด เหตุการณ์เพื่อให้ไคลเอ็นต์ซิงค์กับสถานะเซิร์ฟเวอร์ปัจจุบันโดยไม่ต้อง เขียนโค้ดที่กำหนดเอง

เราจะพูดคุยเพิ่มเติมเกี่ยวกับพฤติกรรมออฟไลน์ใน ดูข้อมูลเพิ่มเติมเกี่ยวกับความสามารถในการทำงานแบบออนไลน์และออฟไลน์

ขั้นตอนถัดไป