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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 07:25:25