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

如何优化API调用速度并解决useEffect中状态未就绪的映射错误?

问题解决与优化方案

一、解决"Cannot read properties of null (reading 'map')"错误

这个错误的核心是:API请求未完成时,questions还是初始的null状态,快速点击按钮刚好触发了此时的map调用,导致报错。可以通过以下几种方式解决:

1. 调整初始状态为数组

把questions的初始值从null改为空数组[],这样即使没有数据,map方法也能安全执行(空数组调用map不会报错):

const [questions, setQuestions] = React.useState([])

2. 点击时增加状态判断

在handleClick里先检查questions是否存在,再执行map:

function handleClick() {
  setStartGame(false)
  // 确保questions有值再执行map
  if (questions) {
    setQuestionElements(questions.map(question => <Question key={question.id} data={question} />))
  }
}

3. 推荐:直接在渲染阶段处理(避免存JSX到state)

React不建议把JSX元素存在state中,更好的做法是直接根据questions和startGame的状态在渲染时生成元素,同时自然处理空值情况:

// 移除questionElements这个state,直接在return里渲染
return (
  <>
    {!startGame && questions && questions.map(question => (
      <Question key={question.id} data={question} />
    ))}
    <button onClick={handleClick}>开始测验</button>
  </>
)

二、优化API调用速度与状态设置即时性

1. 避免重复发起API请求

原代码的useEffect没有依赖数组,会在组件每次重渲染时重复调用fetch,浪费资源。添加空依赖数组,让请求只在组件挂载时执行一次:

React.useEffect(() => {
  fetch("https://opentdb.com/api.php?amount=5&type=multiple")
    .then(res => res.json())
    .then(data => setQuestions(data.results))
}, []) // 空数组确保只执行一次

2. 添加请求缓存

用localStorage缓存请求结果,下次加载时先读取缓存展示,再后台更新最新数据,提升用户感知速度:

React.useEffect(() => {
  // 先读缓存
  const cached = localStorage.getItem('quiz_questions')
  if (cached) {
    setQuestions(JSON.parse(cached))
  }
  // 后台请求最新数据
  fetch("https://opentdb.com/api.php?amount=5&type=multiple")
    .then(res => res.json())
    .then(data => {
      setQuestions(data.results)
      localStorage.setItem('quiz_questions', JSON.stringify(data.results))
    })
}, [])

3. 增加加载状态,防止无效点击

添加isLoading状态,在请求未完成时禁用按钮并显示加载提示,既给用户反馈,又避免加载时的错误点击:

const [questions, setQuestions] = React.useState([])
const [isLoading, setIsLoading] = React.useState(true)
const [startGame, setStartGame] = React.useState(true)

React.useEffect(() => {
  fetch("https://opentdb.com/api.php?amount=5&type=multiple")
    .then(res => res.json())
    .then(data => {
      setQuestions(data.results)
      setIsLoading(false)
    })
    .catch(err => {
      console.error('请求失败:', err)
      setIsLoading(false)
    })
}, [])

function handleClick() {
  if (!isLoading) {
    setStartGame(false)
  }
}

return (
  <>
    {isLoading ? (
      <p>正在加载题目...</p>
    ) : (
      <>
        {!startGame && questions.map(question => (
          <Question key={question.id} data={question} />
        ))}
        <button onClick={handleClick} disabled={isLoading}>开始测验</button>
      </>
    )}
  </>
)

4. 理解React状态更新的异步性

React的setState是异步操作,不要在调用setState后立即依赖新的state值。如果需要在状态更新后执行逻辑,应该用useEffect监听状态变化:

// 比如监听questions变化,自动处理元素生成
React.useEffect(() => {
  if (questions.length > 0) {
    // 这里可以处理questions更新后的逻辑
  }
}, [questions])

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 15:05:32