React将API数据存入state的实现及空值报错问题解析
问题描述
我学习React已有一周时间,学习过程中觉得这个框架十分有趣,但近期遇到问题卡住了开发进度:
- 调用API接口时,使用自定义
useFetchhook获取数据:从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
相关产品推荐
相关产品推荐

