React TypeScript应用API错误处理及useEffect执行问题排查
问题解决:初始加载触发两次API请求及错误提示问题
为什么初始加载会执行两次useEffect?
React 18在开发模式下的StrictMode会强制组件完成挂载后立即卸载,随后再次重新挂载。这个机制是为了提前发现组件中未正确清理的副作用(比如未取消的网络请求、定时器),所以useEffect会被执行两次。这个行为仅存在于开发环境,生产环境不会出现。
解决方案
核心思路是严格控制API请求的触发时机,避免空值或无效邮编发起请求,同时调整状态更新逻辑,确保只有用户主动搜索时才触发请求。
1. 拆分输入状态与搜索触发状态
将实时输入的内容和用于触发请求的状态分开,只有用户点击搜索按钮时,才把输入框的有效值同步到触发状态:
2. 在useEffect中添加请求前置校验
在发起API请求前,先检查邮编是否非空且符合美国邮编格式(5位数字,可选后缀4位),不符合则直接返回,不发起请求。
完整代码示例(TypeScript)
import { useState, useEffect } from 'react'; // 定义接口类型,约束返回数据结构 interface LocationData { 'post code': string; country: string; places: Array<{ 'place name': string; state: string; 'state abbreviation': string; latitude: string; longitude: string; }>; } export default function ZipCodeLookup() { // 存储用户实时输入的内容 const [inputValue, setInputValue] = useState(''); // 仅在点击搜索时更新,用于触发API请求 const [targetZip, setTargetZip] = useState(''); const [location, setLocation] = useState<LocationData | null>(null); const [errorMsg, setErrorMsg] = useState(''); useEffect(() => { // 校验美国邮编格式:5位数字,或5-4格式 const isValidZip = /^\d{5}(-\d{4})?$/.test(targetZip); // 非空且格式有效才发起请求 if (!targetZip || !isValidZip) { setErrorMsg(''); setLocation(null); return; } // 异步请求逻辑 const fetchLocation = async () => { try { setErrorMsg(''); const res = await fetch(`https://api.zippopotam.us/us/${targetZip}`); if (!res.ok) throw new Error('该邮编不存在,请检查输入'); const data = await res.json(); setLocation(data); } catch (err) { setErrorMsg(err instanceof Error ? err.message : '获取地点信息失败'); setLocation(null); } }; fetchLocation(); }, [targetZip]); // 仅依赖targetZip,只有它变化时才执行 // 点击搜索按钮时同步输入值到targetZip const handleSearch = () => { setTargetZip(inputValue.trim()); }; return ( <div style={{ padding: '20px' }}> <div> <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} placeholder="输入美国邮编(如90210)" style={{ padding: '8px', width: '200px' }} /> <button onClick={handleSearch} style={{ padding: '8px 16px', marginLeft: '10px' }} > 搜索 </button> </div> {/* 错误提示 */} {errorMsg && <p style={{ color: 'red', marginTop: '10px' }}>{errorMsg}</p>} {/* 地点信息展示 */} {location && ( <div style={{ marginTop: '15px' }}> <h3>邮编 {location['post code']} 信息</h3> <p>国家:{location.country}</p> {location.places.map((place, idx) => ( <div key={idx} style={{ marginTop: '10px', padding: '10px', border: '1px solid #eee' }}> <p>城市:{place['place name']}</p> <p>州:{place.state} ({place['state abbreviation']})</p> <p>坐标:{place.latitude}, {place.longitude}</p> </div> ))} </div> )} </div> ); }
关键说明
- 拆分
inputValue和targetZip两个状态,确保只有用户主动点击搜索时才会触发API请求,避免输入过程中或初始空值触发请求。 - 前置的邮编格式校验,进一步过滤无效请求,即使
targetZip被意外设置为无效值,也不会发起API调用。 - 开发环境下StrictMode导致的两次初始useEffect执行,会因为
targetZip初始为空而直接返回,不会发起请求,也就不会出现初始404错误。
内容的提问来源于stack exchange,提问作者chucknor
相关产品推荐
相关产品推荐

