Firebase实时数据库equalTo()查询异常(React Native Expo)
问题:Firebase实时数据库基于ShopNo随机查询失败
错误表现
成功生成随机ShopNo,但查询时抛出错误:TypeError: undefined is not a function (near '...ualTo)(random)).then(function (snapshot)...')
数据库结构
{ "Bakery": { "Bakery Cuisine": { "Description": "Within North Spine Plaza", "Halal": "Yes", "Latitude": 1.34714, "Location": "50 Nanyang Ave, #01-20 North Spine Plaza, Singapore 639798", "Longitude": 103.68066, "OH": "Mon - Sat : 8 AM to 7 PM, Sun Closed", "ShopNo": 1 } }, "Beverage": { "Beverage": { "Description": "Within the South Spine food court", "Halal": "No", "Latitude": 1.34253, "Location": "21 Nanyang Link, Singapore 637371", "Longitude": 103.68243, "OH": "Mon - Fri: 7 30 am to 8 pm, Sat - Sun/PH Closed", "ShopNo": 2 }, "Beverages": { "Description": "Within North Spine Koufu", "Halal": "No", "Latitude": 1.34708, "Location": "76 Nanyang Dr, #02-03 North Spine Plaza, Singapore 637331", "Longitude": 103.68002, "OH": "Mon - Fri : 7 am to 8 pm, Sat : 7 am to 3 pm, Sun Closed", "ShopNo": 3 }, "Boost": { "Description": "Within North Spine Plaza", "Halal": "No", "Latitude": 1.34735, "Location": "50 Nanyang Ave, #01-11 North Spine Plaza, Singapore 639798", "Longitude": 103.68036, "OH": "Mon - Fri : 10 am to 9 pm, Sat - Sun: 10 am to 6 pm", "ShopNo": 4 } }, "Total": 89 }
错误代码示例
初始代码
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native' import { useNavigation } from '@react-navigation/core' import {db} from '../firebase' import React, {useEffect, useState} from 'react' import {ref, get, orderByChild, equalTo} from 'firebase/database' const SubScreen2 = () => { const navigation = useNavigation() const [todoData, setToDoData] = useState([]) useEffect (() => { var random = 0 get(ref(db, "food/Total")).then(snapshot => { const count = snapshot.val(); random = Math.floor((Math.random() * count)); console.log(random) //Random number generated (Successful) ref(db, `food/`), orderByChild("ShopNo"), equalTo(random) //Failed .then(snapshot => { const data = snapshot.val() setToDoData(data) }) }) },[]) console.log(todoData) }
更新后代码
import {ref, get, orderByChild, equalTo, query} from 'firebase/database' const SubScreen2 = () => { const navigation = useNavigation() const [todoData, setToDoData] = useState([]) useEffect (() => { var random = 0 get(ref(db, "food/Total")).then(snapshot => { const count = snapshot.val(); random = Math.floor((Math.random() * count)); console.log(random) query(ref(db, `food/`), orderByChild("ShopNo"), equalTo(random)) .then(snapshot => { const data = snapshot.val() setToDoData(data) }) }) },[]) console.log(todoData) }
问题根源
- 查询方法调用错误:
query函数仅用于构建查询条件对象,不能直接通过.then()获取数据,必须配合get()方法执行查询。 - 数据层级不匹配:
ShopNo字段存储在food/[分类]/[店铺]的三级节点下,直接在food/节点使用orderByChild("ShopNo")会查找food的直接子节点(如Bakery、Beverage)的ShopNo字段,而这些节点本身没有该字段,因此无法匹配到目标数据。
解决方案
方案一:重构数据库(推荐,提升查询效率)
将所有店铺数据扁平化存储到一个统一节点(如shops),避免嵌套分类层级,方便直接查询:
{ "shops": { "shop1": { "Category": "Bakery", "Name": "Bakery Cuisine", "ShopNo": 1, "Description": "Within North Spine Plaza", "Halal": "Yes", "Latitude": 1.34714, "Location": "50 Nanyang Ave, #01-20 North Spine Plaza, Singapore 639798", "Longitude": 103.68066, "OH": "Mon - Sat : 8 AM to 7 PM, Sun Closed" }, "shop2": { "Category": "Beverage", "Name": "Beverage", "ShopNo": 2, "Description": "Within the South Spine food court", "Halal": "No", "Latitude": 1.34253, "Location": "21 Nanyang Link, Singapore 637371", "Longitude": 103.68243, "OH": "Mon - Fri: 7 30 am to 8 pm, Sat - Sun/PH Closed" } }, "Total": 89 }
对应修正后的代码:
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native' import { useNavigation } from '@react-navigation/core' import { db } from '../firebase' import React, { useEffect, useState } from 'react' import { ref, get, orderByChild, equalTo, query } from 'firebase/database' const SubScreen2 = () => { const navigation = useNavigation() const [todoData, setToDoData] = useState({}) useEffect(() => { // 获取店铺总数并生成随机ShopNo(注意ShopNo从1开始,需+1避免生成0) get(ref(db, "food/Total")) .then(totalSnapshot => { const count = totalSnapshot.val(); const randomShopNo = Math.floor(Math.random() * count) + 1; console.log(randomShopNo) // 构建查询并执行get请求获取数据 const shopQuery = query( ref(db, "food/shops"), orderByChild("ShopNo"), equalTo(randomShopNo) ) return get(shopQuery) }) .then(shopSnapshot => { if (shopSnapshot.exists()) { // query返回的是对象,取第一个匹配的店铺数据 const shopData = Object.values(shopSnapshot.val())[0] setToDoData(shopData) } else { console.log("未找到匹配的店铺") } }) .catch(error => { console.error("查询出错:", error) }) }, []) return ( <View style={styles.container}> {todoData.Name && ( <View> <Text style={styles.shopName}>{todoData.Name}</Text> <Text>分类:{todoData.Category}</Text> <Text>地址:{todoData.Location}</Text> </View> )} </View> ) } const styles = StyleSheet.create({ container: { flex: 1, padding: 20, justifyContent: 'center' }, shopName: { fontSize: 20, fontWeight: 'bold', marginBottom: 10 } }) export default SubScreen2
方案二:保留原数据库结构(效率较低)
如果无法修改数据库,需要先获取所有分类,再遍历每个分类下的店铺查找匹配的ShopNo:
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native' import { useNavigation } from '@react-navigation/core' import { db } from '../firebase' import React, { useEffect, useState } from 'react' import { ref, get } from 'firebase/database' const SubScreen2 = () => { const navigation = useNavigation() const [todoData, setToDoData] = useState({}) useEffect(() => { get(ref(db, "food/Total")) .then(totalSnapshot => { const count = totalSnapshot.val(); const randomShopNo = Math.floor(Math.random() * count) + 1; console.log(randomShopNo) // 获取所有分类数据 return get(ref(db, "food")) }) .then(foodSnapshot => { const foodData = foodSnapshot.val(); // 遍历分类,跳过Total字段 for (const category in foodData) { if (category === "Total") continue; const shops = foodData[category]; // 遍历分类下的店铺 for (const shopName in shops) { const shop = shops[shopName]; if (shop.ShopNo === randomShopNo) { setToDoData({...shop, Category: category, Name: shopName}) return; // 找到后终止遍历 } } } console.log("未找到匹配的店铺") }) .catch(error => { console.error("查询出错:", error) }) }, []) return ( <View style={styles.container}> {todoData.Name && ( <View> <Text style={styles.shopName}>{todoData.Name}</Text> <Text>分类:{todoData.Category}</Text> <Text>地址:{todoData.Location}</Text> </View> )} </View> ) } const styles = StyleSheet.create({ container: { flex: 1, padding: 20, justifyContent: 'center' }, shopName: { fontSize: 20, fontWeight: 'bold', marginBottom: 10 } }) export default SubScreen2
内容的提问来源于stack exchange,提问作者L.Calvin
相关产品推荐
相关产品推荐

