混合使用 useEffect 和 Firebase 的 onValue

Mix useEffect and Firebase's onValue

在使用 FirebaseReact 时,为了根据状态变化或内部数据库变化(例如来自另一个用户)获取数据,我经常依赖如下代码片段这个:

    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])