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

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;

问题根源

  1. 初始状态导致无效项:localData初始值为undefined,组件挂载时会触发依赖localData的useEffect,将undefined添加到localDataList,导致首项无效。
  2. 竞态与状态覆盖:用forEach循环发起多个异步请求,每个请求完成后调用setLocal会覆盖之前的localData,最终只有最后一个请求的结果会触发localData更新;同时setLocalDataList依赖闭包中的旧状态,可能导致多次更新时状态计算错误。
  3. 不必要的中间状态: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;

关键改进点

  1. 异步逻辑同步化:使用async/await替代链式then,代码更易读且便于错误处理。
  2. 批量请求处理:用Promise.all并行请求所有详情,避免多次状态更新,提升性能。
  3. 移除中间状态:直接将详情结果数组设置到localDataList,消除状态不同步问题。
  4. 加载状态与错误处理:添加加载提示和错误捕获,提升用户体验与调试效率。
  5. 类型安全:明确localDataList的类型,避免any类型带来的潜在问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 02:36:31