(ไม่บังคับ) สร้างต้นแบบและทดสอบด้วย Firebase Local Emulator Suite
ก่อนจะพูดถึงวิธีที่แอปอ่านและเขียนข้อมูลลงใน Realtime Database เรามาแนะนำชุดเครื่องมือที่คุณใช้สร้างต้นแบบและทดสอบ Realtime Database ฟังก์ชันการทำงานได้ นั่นก็คือ Firebase Local Emulator Suite หากคุณกำลังลองใช้โมเดลข้อมูลต่างๆ ปรับกฎการรักษาความปลอดภัยให้เหมาะสม หรือพยายามหาวิธีที่มีประสิทธิภาพสูงสุดในการโต้ตอบกับแบ็กเอนด์ การทำงานในเครื่องโดยไม่ต้องติดตั้งใช้งานบริการจริงอาจเป็นความคิดที่ดี
โปรแกรมจำลอง Realtime Database เป็นส่วนหนึ่งของ Local Emulator Suite ซึ่งช่วยให้แอปโต้ตอบกับเนื้อหาและการกำหนดค่าฐานข้อมูลที่จำลอง รวมถึงทรัพยากรโปรเจ็กต์ที่จำลอง (ฟังก์ชัน ฐานข้อมูลอื่นๆ และกฎการรักษาความปลอดภัย) ได้ด้วย (ไม่บังคับ)
การใช้โปรแกรมจำลอง Realtime Database มีขั้นตอนเพียงไม่กี่ขั้นตอน ดังนี้
- เพิ่มบรรทัดโค้ดลงในการกำหนดค่าการทดสอบของแอปเพื่อเชื่อมต่อกับโปรแกรมจำลอง
- เรียกใช้
firebase emulators:startจากรูทของไดเรกทอรีโปรเจ็กต์ที่อยู่ในเครื่อง - เรียกใช้โค้ดต้นแบบของแอปโดยใช้ Realtime Database แพลตฟอร์ม 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 และอีกครั้งทุกครั้งที่ข้อมูลมีการเปลี่ยนแปลง ซึ่งรวมถึงข้อมูลลูก ระบบจะส่งสแนปชอตที่มีข้อมูลทั้งหมดในตำแหน่งนั้น รวมถึงข้อมูลลูก ไปยังฟังก์ชันเรียกกลับของเหตุการณ์ หากไม่มีข้อมูล สแนปชอตจะแสดงผล 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()
อ่านข้อมูล 1 ครั้ง
อ่านข้อมูล 1 ครั้งด้วย get()
SDK ได้รับการออกแบบมาเพื่อจัดการการโต้ตอบกับเซิร์ฟเวอร์ฐานข้อมูล ไม่ว่าแอปของคุณจะออนไลน์หรือออฟไลน์
โดยทั่วไป คุณควรใช้เทคนิคเหตุการณ์ค่าที่อธิบายไว้ข้างต้นเพื่ออ่านข้อมูลเพื่อรับการแจ้งเตือนเกี่ยวกับการอัปเดตข้อมูลจากแบ็กเอนด์ เทคนิค Listener จะช่วยลดการใช้งานและการเรียกเก็บเงิน รวมถึงได้รับการปรับให้เหมาะสมเพื่อมอบประสบการณ์ที่ดีที่สุดแก่ผู้ใช้เมื่อออนไลน์และออฟไลน์
หากต้องการข้อมูลเพียงครั้งเดียว คุณสามารถใช้ 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); });
อ่านข้อมูล 1 ครั้งด้วย Observer
ในบางกรณี คุณอาจต้องการให้ระบบแสดงผลค่าจากแคชในเครื่องทันทีแทนที่จะตรวจสอบค่าที่อัปเดตในเซิร์ฟเวอร์ ในกรณีดังกล่าว คุณสามารถใช้ 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 ตำแหน่ง การอัปเดตพร้อมกันที่ทำด้วยวิธีนี้จะเป็นแบบอะตอมมิก กล่าวคือ การอัปเดตทั้งหมดจะสำเร็จหรือล้มเหลวทั้งหมด
เพิ่มฟังก์ชันเรียกกลับเมื่อเสร็จสมบูรณ์
หากต้องการทราบว่าข้อมูลได้รับการคอมมิตแล้วเมื่อใด คุณสามารถเพิ่มฟังก์ชันเรียกกลับเมื่อเสร็จสมบูรณ์ได้ ทั้ง set() และ update() จะใช้ฟังก์ชันเรียกกลับเมื่อเสร็จสมบูรณ์ที่ไม่บังคับ ซึ่งจะเรียกใช้เมื่อมีการคอมมิตการเขียนลงในฐานข้อมูล หากการเรียกไม่สำเร็จ ระบบจะส่งออบเจ็กต์ข้อผิดพลาดไปยังฟังก์ชันเรียกกลับเพื่อระบุสาเหตุที่ทำให้เกิดข้อผิดพลาด
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 ที่คุณใช้เพื่อทราบว่าการเขียนได้รับการคอมมิตลงในฐานข้อมูลแล้วเมื่อใด
ยกเลิกการแนบ Listener
ระบบจะนำฟังก์ชันเรียกกลับออกโดยการเรียกเมธอด off() ในการอ้างอิงฐานข้อมูล Firebase
คุณสามารถนำ Listener รายเดียวออกได้โดยส่ง Listener นั้นเป็นพารามิเตอร์ไปยัง off()
การเรียก off() ในตำแหน่งที่ไม่มีอาร์กิวเมนต์จะนำ Listener ทั้งหมดในตำแหน่งนั้นออก
การเรียก off() ใน Listener ระดับบนจะไม่นำ Listener ที่ลงทะเบียนในโหนดลูกออกโดยอัตโนมัติ คุณต้องเรียก off() ใน Listener ระดับลูกด้วยเพื่อนำฟังก์ชันเรียกกลับออก
บันทึกข้อมูลเป็นธุรกรรม
เมื่อทำงานกับข้อมูลที่อาจเสียหายจากการแก้ไขพร้อมกัน เช่น ตัวนับแบบเพิ่มค่า คุณสามารถใช้การ ดำเนินการธุรกรรมได้ คุณสามารถกำหนดฟังก์ชันอัปเดตและฟังก์ชันเรียกกลับเมื่อเสร็จสมบูรณ์ที่ไม่บังคับสำหรับการดำเนินการนี้ ฟังก์ชันอัปเดตจะใช้สถานะปัจจุบันของข้อมูลเป็นอาร์กิวเมนต์และแสดงผลสถานะใหม่ที่ต้องการเขียน หากไคลเอ็นต์อื่นเขียนข้อมูลลงในตำแหน่งก่อนที่จะเขียนค่าใหม่สำเร็จ ระบบจะเรียกฟังก์ชันอัปเดตอีกครั้งด้วยค่าปัจจุบันใหม่ และลองเขียนอีกครั้ง
ตัวอย่างเช่น ในแอปบล็อกโซเชียล คุณอาจอนุญาตให้ผู้ใช้ติดดาวและนำดาวออกจากโพสต์ รวมถึงติดตามจำนวนดาวที่โพสต์ได้รับดังนี้
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 จะซิงโครไนซ์ข้อมูลนั้นกับเซิร์ฟเวอร์ฐานข้อมูลระยะไกลและกับไคลเอ็นต์อื่นๆ ตามความสามารถที่ดีที่สุด
ด้วยเหตุนี้ การเขียนข้อมูลทั้งหมดลงในฐานข้อมูลจึงทริกเกอร์เหตุการณ์ในเครื่องทันทีก่อนที่จะเขียนข้อมูลลงในเซิร์ฟเวอร์ ซึ่งหมายความว่าแอปของคุณจะยังคงตอบสนองได้ไม่ว่าจะมีเวลาในการรับส่งข้อมูลผ่านเครือข่ายหรือการเชื่อมต่อเป็นอย่างไร
เมื่อมีการเชื่อมต่ออีกครั้ง แอปของคุณจะได้รับชุดเหตุการณ์ที่เหมาะสมเพื่อให้ไคลเอ็นต์ซิงค์กับสถานะเซิร์ฟเวอร์ปัจจุบันโดยไม่ต้องเขียนโค้ดที่กำหนดเอง
เราจะพูดถึงลักษณะการทำงานแบบออฟไลน์เพิ่มเติมใน ดูข้อมูลเพิ่มเติมเกี่ยวกับความสามารถแบบออนไลน์และออฟไลน์