You need to enable JavaScript to run this app. React useEffect中扩展运算符处理Promise返回值为空无法触发更新
优惠活动
大模型
产品
解决方案
定价
更多

React useEffect中扩展运算符处理Promise返回值为空无法触发更新

核心问题根因

排查下来两个问题是连锁反应的:

  • 你用[...result]拿不到任何元素,本质是getHiscores()返回的result根本不是实现了迭代协议的标准数组。Firebase的查询返回通常是两种结构:带.docs属性的QuerySnapshot对象、以文档ID为键的普通对象,这两种结构直接用扩展运算符展开,只会返回空数组——因为扩展运算符只对实现了Symbol.iterator的结构生效,普通对象、没有迭代器的类数组对象都会被展开成空内容。
  • 直接写setHiscores(result)不触发重渲染,不是因为React检测不到数组变更——本来存的就不是数组,加上你用来生成表格的useEffect完全没写依赖数组,默认每次组件渲染都会执行,没有明确的触发条件。你之前为了等数据出来去掉依赖数组反复跑的方案,本质是形成了“执行useEffect→setState→触发重渲染→再执行useEffect”的无限循环,才会触发最大更新深度警告。

修复步骤

  1. 把Firebase返回的非数组结构转成真正的标准数组,再用扩展运算符生成新引用
    不要直接把Promise返回的根对象当数组用,在.then回调里先把数据拍平成数组:
    .then((result) => {
      // 兼容两种常见Firebase返回结构
      let hiscoreList = []
      if (result.docs) {
        // 兼容Firestore QuerySnapshot结构
        hiscoreList = result.docs.map(doc => doc.data())
      } else if (typeof result === 'object' && !Array.isArray(result)) {
        // 兼容Realtime Database返回的键值对对象
        hiscoreList = Object.values(result)
      } else if (Array.isArray(result)) {
        hiscoreList = result
      }
      // 这时候hiscoreList是标准数组,扩展运算符完全有效,生成的新引用能被React正确检测到
      setHiscores([...hiscoreList])
      setLoading(false)
    })
    
  2. 给生成表格的useEffect补上明确的依赖列表,删掉无意义的兜底循环
    之前无依赖的useEffect完全不需要反复执行,只要把依赖项设为hiscores和error,就会在数据真正更新的时候自动触发表格渲染:
    useEffect(() => {
      // 没有有效数据的时候清空表格
      if (error || hiscores.length === 0) {
        setHiscoresTable(null)
        return
      }
      // 直接遍历hiscores生成表格行,不需要额外的临时变量
      const tableRows = hiscores.map((item, index) => (
        // 列表渲染加上key避免警告
        <tr key={item.id ?? index}>
          <td>{index + 1}</td>
          <td>{item.name}</td>
          <td>{new Date(item.time * 1000).toISOString().substr(11, 8)}</td>
        </tr>
      ))
      setHiscoresTable(tableRows)
    }, [hiscores, error]) // 明确指定依赖,不会无限制触发执行
    
  3. 优化初始拉取的useEffect依赖
    原来的拉取逻辑是空依赖数组[],如果用户切换关卡的时候,levelToShow的props变了,组件不会重新拉取对应关卡的高分。把props.levelToShow加到依赖列表里,同时在拉取前重置加载状态,避免旧数据闪现。

调整后的可运行组件代码

function HiscoresDisplay(props) {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);
  const [hiscores, setHiscores] = useState([]);
  const [hiscoresTable, setHiscoresTable] = useState(null);

  useEffect(() => {
    // 状态重置,避免跨关卡的旧数据展示
    setLoading(true)
    setError(false)
    getHiscores(props.levelToShow.getLevelName())
      .then((result) => {
        let hiscoreList = []
        if (result.docs) {
          hiscoreList = result.docs.map(doc => doc.data())
        } else if (typeof result === 'object' && !Array.isArray(result)) {
          hiscoreList = Object.values(result)
        } else if (Array.isArray(result)) {
          hiscoreList = result
        }
        setHiscores([...hiscoreList])
        setLoading(false)
      })
      .catch(() => {
        setError(true)
        setLoading(false)  
      })
  }, [props.levelToShow])

  useEffect(() => {
    if (error || hiscores.length === 0) {
      setHiscoresTable(null)
      return
    }
    const tableRows = hiscores.map((item, index) => (
      <tr key={item.id ?? index}>
        <td>{index + 1}</td>
        <td>{item.name}</td>
        <td>{new Date(item.time * 1000).toISOString().substr(11, 8)}</td>
      </tr>
    ))
    setHiscoresTable(tableRows)
  }, [hiscores, error])

  return (
    <>
      <header>
        <h1 style={{textAlign: 'center', width: '100%'}}>Where's Wally?</h1>
      </header>
      <p className="under-header">{props.levelToShow.getLevelName()} Hiscores</p>
      <button 
        onClick={() => props.setDisplay(0)} 
        style={{display: 'block', margin: '0 auto'}}
      >
        ◀&nbsp; Back to Main Menu
      </button>

      {loading && <h1 className="loading-levels">Loading hiscores...</h1>}
      {error && <h1 className="loading-levels">Failed to load hiscores</h1>}

      {!error && !loading && (
        <div className="hiscores-container">
          <table 
            className="hiscores" 
            style={{backgroundImage: `url(${props.levelToShow.getImgSrc()})`}}
          >
            <thead>
              <tr>
                <th>#</th>
                <th>Name</th>
                <th>Time</th>
              </tr>
            </thead>
            <tbody>
              {hiscoresTable}
            </tbody>
          </table>  
        </div>
      )}
    </>
  )
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.05 22:45:04