React/TypeScript嵌套API调用的标准写法及问题修复
问题描述
开发小型应用,流程为:组件加载时调用接口获取物品列表,再遍历列表拉取每个物品的详情。当前实现违反Hook规则,出现两个异常:
localDataList首项为null/undefined- 多次刷新时,有时仅返回1条详情数据
预期流程:组件加载 → 触发useEffect设置Globals → 触发useEffect拉取每个物品详情 → 将详情添加到localDataList
当前代码:
import React from 'react'; import { useState, useEffect } from 'react'; import { Buffer } from 'buffer'; import algosdk from 'algosdk'; export declare module DataStore { export interface Events { "global-state": KeyValue[]; } export interface EventDetails { "key-value": KeyValue[]; } export interface Value { bytes: string; type: number; uint: number; } export interface KeyValue { key: string; value: Value; } } function App() { const [globals, setGlobals] = useState<DataStore.Events[]>([]); const [localDataList, setLocalDataList ] = useState<any>([]) const [localData, setLocal] = useState<DataStore.EventDetails[]>(); useEffect(() => { fetch("https://node.algoexplorerapi.io/v2/applications/1022971265") .then((response) => response.json()) .then((data) => setGlobals(data.params["global-state"].filter((item: any) => item.key !== "VGl0bGU=" ))); }, []); useEffect(() => { globals.forEach(function (item: any) { const pretty_address = algosdk.encodeAddress(new Buffer(item.value.bytes, 'base64')); fetch("https://node.algoexplorerapi.io/v2/accounts/" + pretty_address + "/applications/1022971265") .then((response) => response.json()) .then((data) => setLocal(data["app-local-state"]["key-value"] )); }) }, [globals]); useEffect(() => { console.log(localData) setLocalDataList([...localDataList, localData ]) }, [localData]); return ( <div> Globals: <ul> {globals.map((item: any) => ( <li key={item.key}>{ JSON.stringify(item) }</li> ))} </ul> <br/> Local list: <ul> {localDataList?.map((item) => ( <li> {JSON.stringify(item)} </li> )) } </ul> </div> ); } export default App;
问题根源
- 初始状态导致无效项:
localData初始值为undefined,组件挂载时会触发依赖localData的useEffect,将undefined添加到localDataList,导致首项无效。 - 竞态与状态覆盖:用
forEach循环发起多个异步请求,每个请求完成后调用setLocal会覆盖之前的localData,最终只有最后一个请求的结果会触发localData更新;同时setLocalDataList依赖闭包中的旧状态,可能导致多次更新时状态计算错误。 - 不必要的中间状态:
localData作为中间状态完全多余,反而引发多次重渲染和状态不同步问题。
正确实现方案
优化思路
- 移除多余的
localData状态,直接在获取全局列表后批量拉取所有详情,一次性更新localDataList。 - 使用
Promise.all并行处理异步请求,提升效率并避免竞态问题。 - 用函数式更新确保状态更新基于最新值,避免闭包陷阱。
- 过滤无效数据,确保只有有效详情被添加到列表。
优化后代码
import React from 'react'; import { useState, useEffect } from 'react'; import { Buffer } from 'buffer'; import algosdk from 'algosdk'; export declare module DataStore { export interface Events { "global-state": KeyValue[]; } export interface EventDetails { "key-value": KeyValue[]; } export interface Value { bytes: string; type: number; uint: number; } export interface KeyValue { key: string; value: Value; } } function App() { const [globals, setGlobals] = useState<DataStore.Events[]>([]); const [localDataList, setLocalDataList] = useState<DataStore.EventDetails["key-value"][]>([]); const [isLoading, setIsLoading] = useState(true); // 添加加载状态提升体验 // 第一步:获取全局数据 useEffect(() => { const fetchGlobals = async () => { try { const response = await fetch("https://node.algoexplorerapi.io/v2/applications/1022971265"); const data = await response.json(); // 过滤无效项并设置全局数据 const filteredGlobals = data.params["global-state"].filter((item: any) => item.key !== "VGl0bGU="); setGlobals(filteredGlobals); } catch (error) { console.error("获取全局数据失败:", error); } finally { setIsLoading(false); } }; fetchGlobals(); }, []); // 第二步:根据全局数据批量拉取详情 useEffect(() => { if (globals.length === 0) return; const fetchLocalDetails = async () => { try { // 并行发起所有详情请求 const detailPromises = globals.map(async (item) => { const prettyAddress = algosdk.encodeAddress(new Buffer(item.value.bytes, 'base64')); const response = await fetch(`https://node.algoexplorerapi.io/v2/accounts/${prettyAddress}/applications/1022971265`); const data = await response.json(); return data["app-local-state"]["key-value"]; }); // 等待所有请求完成,获取结果数组 const details = await Promise.all(detailPromises); // 过滤掉可能的无效数据 const validDetails = details.filter(detail => detail && detail.length > 0); // 一次性设置详情列表 setLocalDataList(validDetails); } catch (error) { console.error("获取详情数据失败:", error); } }; fetchLocalDetails(); }, [globals]); if (isLoading) { return <div>加载中...</div>; } return ( <div> <h3>Globals:</h3> <ul> {globals.map((item) => ( <li key={item.key}>{JSON.stringify(item)}</li> ))} </ul> <br /> <h3>Local list:</h3> <ul> {localDataList.length > 0 ? ( localDataList.map((item, index) => ( <li key={index}>{JSON.stringify(item)}</li> )) ) : ( <li>暂无详情数据</li> )} </ul> </div> ); } export default App;
关键改进点
- 异步逻辑同步化:使用
async/await替代链式then,代码更易读且便于错误处理。 - 批量请求处理:用
Promise.all并行请求所有详情,避免多次状态更新,提升性能。 - 移除中间状态:直接将详情结果数组设置到
localDataList,消除状态不同步问题。 - 加载状态与错误处理:添加加载提示和错误捕获,提升用户体验与调试效率。
- 类型安全:明确
localDataList的类型,避免any类型带来的潜在问题。
内容的提问来源于stack exchange,提问作者landphil
相关产品推荐
相关产品推荐

