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

React将API数据存入state的实现及空值报错问题解析

问题描述

我学习React已有一周时间,学习过程中觉得这个框架十分有趣,但近期遇到问题卡住了开发进度:

  • 调用API接口时,使用自定义useFetch hook获取数据:从hook返回值中解构出data,以及错误提示、加载态相关的通用状态与方法,用于数据加载完成前展示加载器、请求出错时展示错误提示。
  • 最初直接将data作为props传入名为AnimeList的组件,由该组件遍历数据完成渲染。现在需要先将获取到的API数据存入state,方便后续按需修改数据,之后再将state作为props传入AnimeList组件,而非直接传入原始data。
  • 声明名为topAnime的state后,使用useEffect钩子将topAnime的状态值设置为获取到的data,但控制台报错提示data为null。初步猜测问题可能和useEffect钩子的依赖数组有关但无法确定,初始代码如下:

初始报错代码

import { useFetch } from '../hooks/useFetch'
import Error from "./Error"
import Spinner from "../components/spinner/Spinner"
import AnimeList from "../components/animelist/AnimeList"
import Header from '../Header'
import './TopAnime.css'
import { useState } from 'react'
import { useEffect } from 'react'
function TopAnime() {

  const [topAnime, setTopAnime] = useState([])
  const {data, isPending, error} = useFetch('https://api.jikan.moe/v4/top/anime')
  useEffect(() => {
    (async () => {
      setTopAnime(data);
    })();
  }, [data]);

  return (
    <div className="top-anime">
    <Header/>
    {error && <Error/>}
    {isPending && <Spinner/>}
    {data && <h1>Top Anime</h1>}
    {data && <AnimeList animelist={topAnime}/>}
  </div>
  )
}

export default TopAnime

临时修复方案

自行摸索后找到临时修复方法:和判断data存在的逻辑一致,增加topAnime的存在性判断即可正常运行,但不清楚该操作能修复问题的原理,修复后的可运行代码如下:

import { useFetch } from '../hooks/useFetch'
import Error from "./Error"
import Spinner from "../components/spinner/Spinner"
import AnimeList from "../components/animelist/AnimeList"
import Header from '../Header'
import './TopAnime.css'
import { useState } from 'react'
import { useEffect } from 'react'
function TopAnime() {

  const [topAnime, setTopAnime] = useState([])
  const {data, isPending, error} = useFetch('https://api.jikan.moe/v4/top/anime')
  useEffect(() => {
    (async () => {
      setTopAnime(data);
    })();
  });
  return (
    <div className="top-anime">
    <Header/>
    {error && <Error/>}
    {isPending && <Spinner/>}
    {data && <h1>Top Anime</h1>}
    {data && topAnime && <AnimeList animelist={topAnime}/>}
  </div>
  )
}

export default TopAnime
问题原理解释

报错的核心原因是React状态更新的异步特性,以及渲染逻辑和effect执行的时序差:

  • 组件首次渲染时,自定义fetch hook还在请求接口,此时data的初始值为null。写的useEffect会在组件渲染完成后执行,第一次执行时就会把topAnime的值设为null,覆盖掉初始的空数组。
  • 当接口请求完成,data变为有值的接口返回数据,组件触发重渲染。此时渲染逻辑里只判断了data为真值就渲染AnimeList组件,但setTopAnime(data)的状态更新是异步排队执行的,这一帧渲染时topAnime还是上一轮的null,把null作为props传入AnimeList后,组件内部尝试对null执行遍历、属性读取操作,就会抛出报错。
  • 增加topAnime的真值判断后,相当于等topAnime也被更新为有效值之后再渲染子组件,自然不会出现传入null的问题。
代码优化建议

当前的修复版本虽然能跑,但存在不必要的性能损耗,建议做两处调整:

  • 不要删掉useEffect的依赖数组。没有依赖数组的useEffect会在组件每一次重渲染时都执行,反复触发setTopAnime造成多余的渲染,正确写法是保留[data]作为依赖,仅当接口返回的data变化时才同步状态。
  • 从根源避免topAnime被设为null:在useEffect内部先判断data是否存在,有值时再执行状态更新,就不需要在渲染层做多余的真值判断了。优化后的核心逻辑如下:
const [topAnime, setTopAnime] = useState([])
const {data, isPending, error} = useFetch('https://api.jikan.moe/v4/top/anime')
useEffect(() => {
  // 仅当data有值时才同步到state
  if (data) {
    setTopAnime(data);
  }
}, [data]);

return (
  <div className="top-anime">
    <Header/>
    {error && <Error/>}
    {isPending && <Spinner/>}
    {topAnime && <h1>Top Anime</h1>}
    {topAnime && <AnimeList animelist={topAnime}/>}
  </div>
)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 14:01:04