混合使用 useEffect 和 Firebase 的 onValue
Mix useEffect and Firebase's onValue
在使用 Firebase
和 React
时,为了根据状态变化或内部数据库变化(例如来自另一个用户)获取数据,我经常依赖如下代码片段这个:
useEffect(() => {
const getGamesInSelectedGroup = () => {
if (!state.currentGroup) {
return
}
const db = getDatabase();
const resp = ref(db, `/games/${state.currentGroup.name}`);
onValue(resp, (snap) => {
if (snap.exists()) {
const data = snap.val()
const games = Object.keys(data).map(k => ({id: k, group: state.currentGroup.name, ...data[k]}))
setState((prev) => ({
...prev,
games: games,
isLoaded: true,
}));
return
}
setState((prev) => ({
...prev,
games: null,
isLoaded: true,
}));
toast.warning("no data for " + state.currentGroup.name)
})
}
getGamesInSelectedGroup();
}, [state.currentGroup])
但是,我想知道,每当 state.currentGroup
发生变化时,是否会创建一个新的 /games/${state.currentGroup.name}
侦听器?如果是这样,是否有办法在创建新监听器之前取消订阅以前的监听器?
我正在考虑用 get
调用替换 onValue
,仍然以 state.currentGroup
为条件,并在 useEffect
之外使用 onValue
来反映“内部”数据库更改。
与其将所有内容嵌套在 getGamesInSelectedGroup
函数中(这是您将用于 Promise-based API 的模式),不如在 useEffect
的主体中调用它以进行管理听众更简单:
useEffect(() => {
if (!state.currentGroup) {
return
}
const db = getDatabase();
const resp = ref(db, `/games/${state.currentGroup.name}`);
return onValue(resp, (snap) => { // <--- return the unsubscriber!
if (snap.exists()) {
const data = snap.val()
const games = Object.keys(data)
.map(k => ({
id: k,
group: state.currentGroup.name,
...data[k]
}));
setState((prev) => ({
...prev,
games, // you can use this instead of "games: games"
isLoaded: true,
}));
return
}
setState((prev) => ({
...prev,
games: null,
isLoaded: true,
}));
toast.warning("no data for " + state.currentGroup.name)
});
}, [state.currentGroup])
我还建议使用“快照到子数组”函数,而不是使用 Object.keys(snapshot.val())
来维护查询的排序顺序(使用代码 as-is 会忽略它)。不幸的是,在撰写本文时,还没有适用于 RTDB 的 Firestore QuerySnapshot#docs
的等价物。但是我们自己制作很容易:
// returns array of DataSnapshot objects under this DataSnapshot
// put this outside of your component, like in a common function library file
const getSnapshotChildren = (snapshot) => {
const children = [];
// note: the curly braces on the next line are important! If the
// callback returns a truthy value, forEach will stop iterating
snapshot.forEach(child => { children.push(child) })
return children;
}
useEffect(() => {
if (!state.currentGroup) {
return
}
const db = getDatabase();
const resp = ref(db, `/games/${state.currentGroup.name}`);
return onValue(resp, (snap) => { // <--- return the unsubscriber!
if (snap.exists()) {
const games = getSnapshotChildren(snap)
.map(child => ({
id: child.key,
group: state.currentGroup.name,
...child.val()
}));
setState((prev) => ({
...prev,
games, // you can use this instead of "games: games"
isLoaded: true,
}));
return
}
setState((prev) => ({
...prev,
games: null,
isLoaded: true,
}));
toast.warning("no data for " + state.currentGroup.name)
});
}, [state.currentGroup])
在使用 Firebase
和 React
时,为了根据状态变化或内部数据库变化(例如来自另一个用户)获取数据,我经常依赖如下代码片段这个:
useEffect(() => {
const getGamesInSelectedGroup = () => {
if (!state.currentGroup) {
return
}
const db = getDatabase();
const resp = ref(db, `/games/${state.currentGroup.name}`);
onValue(resp, (snap) => {
if (snap.exists()) {
const data = snap.val()
const games = Object.keys(data).map(k => ({id: k, group: state.currentGroup.name, ...data[k]}))
setState((prev) => ({
...prev,
games: games,
isLoaded: true,
}));
return
}
setState((prev) => ({
...prev,
games: null,
isLoaded: true,
}));
toast.warning("no data for " + state.currentGroup.name)
})
}
getGamesInSelectedGroup();
}, [state.currentGroup])
但是,我想知道,每当 state.currentGroup
发生变化时,是否会创建一个新的 /games/${state.currentGroup.name}
侦听器?如果是这样,是否有办法在创建新监听器之前取消订阅以前的监听器?
我正在考虑用 get
调用替换 onValue
,仍然以 state.currentGroup
为条件,并在 useEffect
之外使用 onValue
来反映“内部”数据库更改。
与其将所有内容嵌套在 getGamesInSelectedGroup
函数中(这是您将用于 Promise-based API 的模式),不如在 useEffect
的主体中调用它以进行管理听众更简单:
useEffect(() => {
if (!state.currentGroup) {
return
}
const db = getDatabase();
const resp = ref(db, `/games/${state.currentGroup.name}`);
return onValue(resp, (snap) => { // <--- return the unsubscriber!
if (snap.exists()) {
const data = snap.val()
const games = Object.keys(data)
.map(k => ({
id: k,
group: state.currentGroup.name,
...data[k]
}));
setState((prev) => ({
...prev,
games, // you can use this instead of "games: games"
isLoaded: true,
}));
return
}
setState((prev) => ({
...prev,
games: null,
isLoaded: true,
}));
toast.warning("no data for " + state.currentGroup.name)
});
}, [state.currentGroup])
我还建议使用“快照到子数组”函数,而不是使用 Object.keys(snapshot.val())
来维护查询的排序顺序(使用代码 as-is 会忽略它)。不幸的是,在撰写本文时,还没有适用于 RTDB 的 Firestore QuerySnapshot#docs
的等价物。但是我们自己制作很容易:
// returns array of DataSnapshot objects under this DataSnapshot
// put this outside of your component, like in a common function library file
const getSnapshotChildren = (snapshot) => {
const children = [];
// note: the curly braces on the next line are important! If the
// callback returns a truthy value, forEach will stop iterating
snapshot.forEach(child => { children.push(child) })
return children;
}
useEffect(() => {
if (!state.currentGroup) {
return
}
const db = getDatabase();
const resp = ref(db, `/games/${state.currentGroup.name}`);
return onValue(resp, (snap) => { // <--- return the unsubscriber!
if (snap.exists()) {
const games = getSnapshotChildren(snap)
.map(child => ({
id: child.key,
group: state.currentGroup.name,
...child.val()
}));
setState((prev) => ({
...prev,
games, // you can use this instead of "games: games"
isLoaded: true,
}));
return
}
setState((prev) => ({
...prev,
games: null,
isLoaded: true,
}));
toast.warning("no data for " + state.currentGroup.name)
});
}, [state.currentGroup])