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

React useEffect处理API响应时生命周期异常导致countriesData.map报错

核心问题根因

  1. 状态流转逻辑缺失
  • 每次searchValue变化触发新请求时,没有重置error和loading状态,旧状态会被复用,新请求返回前页面会用旧状态渲染,一旦出现状态异常就会直接走到map逻辑。
  • 请求失败的!res.ok分支仅设置了error,没有同步把loading设为false,若出现异常状态流转就会跳过错误判断。
  • 整个异步请求没有用try/catch包裹,网络错误、JSON解析错误等异常会直接中断函数执行,既不会触发setError也不会更新loading,导致页面保留error=false、loading=false的旧状态。
  1. !res.ok不触发的问题
    你测试的搜索词大概率都能命中接口返回200状态码,输入完全不存在的国家名比如asdfghjkl123时,接口会返回404,此时res.ok会为false,就能进入对应分支。

修复后完整代码

import { useEffect, useState } from 'react'
import CountryCard from '../CountryCard'
import { countries } from './CountriesList.module.css'

const CountriesList = ({ searchValue }) => {
    const [error, setError] = useState(false)
    const [loading, setLoading] = useState(true)
    const [countriesData, setCountriesData] = useState([])

    useEffect(() => {
        // 新增请求取消逻辑,避免快速输入时的竞态问题
        const abortController = new AbortController()
        const signal = abortController.signal

        const getCountries = async () => {
            // 每次请求前重置状态
            setError(false)
            setLoading(true)
            try {
                let res = await fetch('https://restcountries.com/v2/all', { signal })
                const trimedValue = searchValue.trim()
                if (trimedValue !== '') {
                    // 搜索参数编码,避免特殊字符导致请求错误
                    res = await fetch(
                        `https://restcountries.com/v2/name/${encodeURIComponent(trimedValue)}`,
                        { signal }
                    )
                }

                if (!res.ok) {
                    throw new Error('请求失败')
                }

                const data = await res.json()
                // 校验返回数据格式,避免非法值赋值
                if (Array.isArray(data)) {
                    setCountriesData(data)
                } else {
                    throw new Error('数据格式错误')
                }
            } catch (err) {
                // 忽略主动取消的请求错误
                if (err.name !== 'AbortError') {
                    setError(true)
                }
            } finally {
                // 无论成功失败都更新loading状态
                if (!signal.aborted) {
                    setLoading(false)
                }
            }
        }

        getCountries()

        // 依赖变化或组件卸载时取消未完成的请求
        return () => abortController.abort()
    }, [searchValue])

    if (error) {
        return <div>Error: please check the country you have typed</div>
    }

    if (loading) {
        return <div>Loading...</div>
    }

    return (
        <section className={`${countries} columns is-multiline`}>
            {countriesData.map((country) => (
                <CountryCard {...country} key={country.name} />
            ))}
        </section>
    )
}

export default CountriesList

额外优化说明

  • 移除了内部不必要的Content组件,直接在主组件内写渲染逻辑即可,避免不必要的性能开销
  • 对搜索参数做了trim和编码处理,避免空内容、特殊字符导致的请求异常
  • 新增了请求取消逻辑,避免快速输入时多个请求先后返回导致的数据覆盖问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 07:45:02