You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于Firebase实时数据库更新正确重渲染计数器UI

问题:如何基于Firebase实时数据库正确更新计数器及UI?

我尝试基于Firebase实时数据库的数据更新一组计数器,使用onValue函数时遇到以下问题:

  • 直接调用readFeed导致无限重渲染,React Native阻止应用进入无限循环;
  • 将readFeed设为const后,数值无法自动更新,仅显示为1,需手动重渲染应用;
  • 使用useEffect仅能在首次刷新,计数结果不正确。

用户可点击ButtonGrid组件中的按钮增加对应计数器的数值。

原代码如下:

const Tracker = ({ selectedLocation }) => {
  //TODO: Selected location now includes userID, which is confusing and bad code

  const db = getDatabase();
  const [whiteCounter, setWhiteCounter] = useState(0);
  const [greenCounter, setGreenCounter] = useState(0);
  const [blueCounter, setBlueCounter] = useState(0);
  const [yellowCounter, setYellowCounter] = useState(0);
  const [redCounter, setRedCounter] = useState(0);

  const test2 = selectedLocation.split(".app");
  const ref2 = test2[1] + "/logs";

  function readFeed() {
    onValue(ref(db, ref2), (snapshot) => {
      const data = snapshot.val();

      for (let key in data ? data : []) {
        switch (data[key].type) {
          case "White":
            setWhiteCounter(whiteCounter + 1);
            break;
          case "Green":
            setGreenCounter(greenCounter + 1);

            break;
          case "Blue":
            setBlueCounter(blueCounter + 1);

            break;
          case "Yellow":
            setYellowCounter(yellowCounter + 1);

            break;
          case "Red":
            setRedCounter(redCounter + 1);

            break;

          default:
            break;
        }
      }
    });
  }

  useEffect(() => {
    readFeed();
  }, []);

  return (
    <View style={styles.home}>
      <Text>Antall klart pr. farge på denne session:</Text>
      <View style={styles.imageRow}>
        <ImageBackground style={styles.imgStyle} source={images.white_dot}>
          <Text style={styles.counterText}> {whiteCounter}</Text>
        </ImageBackground>

        <ImageBackground style={styles.imgStyle} source={images.green_dot}>
          <Text style={styles.counterText}>{greenCounter} </Text>
        </ImageBackground>

        <ImageBackground style={styles.imgStyle} source={images.blue_dot}>
          <Text style={styles.counterText}>{blueCounter}</Text>
        </ImageBackground>

        <ImageBackground style={styles.imgStyle} source={images.yellow_dot}>
          <Text style={styles.counterText}>{yellowCounter}</Text>
        </ImageBackground>

        <ImageBackground style={styles.imgStyle} source={images.red_dot}>
          <Text style={styles.counterText}>{redCounter}</Text>
        </ImageBackground>
      </View>
      <ButtonGrid sessionRef={selectedLocation} />
    </View>
  );
};

export default Tracker;

问题根源

  1. 闭包捕获旧状态:你直接用setWhiteCounter(whiteCounter + 1)这类写法,闭包会捕获初始状态值,导致每次快照更新时都基于旧值累加,结果错误。
  2. 未清理Firebase监听:onValue会持续监听数据变化,组件卸载或依赖变化时不清理监听,会引发内存泄漏和无效状态更新。
  3. 依赖缺失:ref2生成依赖selectedLocation,但useEffect依赖数组为空,selectedLocation变化时无法重新监听。
  4. 多次状态更新:循环中逐个调用setState,触发多次重渲染,且状态更新可能不同步。

正确实现方案

修改后的完整代码

import { useState, useEffect, useMemo } from 'react';
import { View, Text, ImageBackground } from 'react-native';
import { getDatabase, ref, onValue } from 'firebase/database';

const Tracker = ({ selectedLocation }) => {
  const db = getDatabase();
  // 合并计数器状态,简化管理
  const [counters, setCounters] = useState({
    White: 0,
    Green: 0,
    Blue: 0,
    Yellow: 0,
    Red: 0
  });

  // 使用useMemo避免每次渲染重复生成数据库引用
  const logsRef = useMemo(() => {
    const test2 = selectedLocation.split(".app");
    const refPath = test2[1] + "/logs";
    return ref(db, refPath);
  }, [selectedLocation]);

  useEffect(() => {
    // 定义数据变化处理函数
    const handleDataUpdate = (snapshot) => {
      const data = snapshot.val();
      // 初始化计数器
      const newCounts = {
        White: 0,
        Green: 0,
        Blue: 0,
        Yellow: 0,
        Red: 0
      };

      if (data) {
        // 批量统计每种类型的数量
        Object.values(data).forEach(item => {
          const type = item.type;
          if (newCounts.hasOwnProperty(type)) {
            newCounts[type]++;
          }
        });
      }

      // 一次性更新所有计数器
      setCounters(newCounts);
    };

    // 订阅Firebase数据变化,获取取消订阅函数
    const unsubscribe = onValue(logsRef, handleDataUpdate);

    // 组件卸载或依赖变化时取消订阅
    return () => unsubscribe();
  }, [logsRef]);

  return (
    <View style={styles.home}>
      <Text>Antall klart pr. farge på denne session:</Text>
      <View style={styles.imageRow}>
        <ImageBackground style={styles.imgStyle} source={images.white_dot}>
          <Text style={styles.counterText}> {counters.White}</Text>
        </ImageBackground>
        <ImageBackground style={styles.imgStyle} source={images.green_dot}>
          <Text style={styles.counterText}>{counters.Green} </Text>
        </ImageBackground>
        <ImageBackground style={styles.imgStyle} source={images.blue_dot}>
          <Text style={styles.counterText}>{counters.Blue}</Text>
        </ImageBackground>
        <ImageBackground style={styles.imgStyle} source={images.yellow_dot}>
          <Text style={styles.counterText}>{counters.Yellow}</Text>
        </ImageBackground>
        <ImageBackground style={styles.imgStyle} source={images.red_dot}>
          <Text style={styles.counterText}>{counters.Red}</Text>
        </ImageBackground>
      </View>
      <ButtonGrid sessionRef={selectedLocation} />
    </View>
  );
};

export default Tracker;

关键改进点

  • 合并状态:将多个独立计数器合并为一个对象,减少状态更新次数,提升性能和可维护性。
  • useMemo优化引用:避免每次渲染重复分割字符串和创建数据库引用,只有selectedLocation变化时才重新生成。
  • 监听生命周期管理:在useEffect中返回取消订阅函数,防止内存泄漏;依赖数组包含logsRef,确保selectedLocation变化时重新监听。
  • 批量统计更新:先遍历所有数据统计总数,再一次性更新状态,避免多次setState导致的重渲染和状态不一致。
  • 空数据处理:当数据库无数据时,自动重置计数器为0,避免显示旧值。

内容的提问来源于stack exchange,提问作者Dukes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 04:00:07