0

0

React组件中异步数据获取与状态更新:解决UI不显示问题

花韻仙語

花韻仙語

发布时间:2025-11-23 17:13:01

|

235人浏览过

|

来源于php中文网

原创

React组件中异步数据获取与状态更新:解决UI不显示问题

本文旨在解决react组件中异步数据加载后ui不更新的常见问题。通过分析一个实际案例,我们将探讨如何正确使用react的`usestate`和`useeffect`钩子来管理异步状态,确保数据获取完成后组件能够重新渲染并显示最新信息。教程将涵盖数据结构选择、异步操作协调以及typescript最佳实践,提供清晰的解决方案和示例代码。

引言

在现代Web开发中,React组件经常需要从外部API获取数据,然后根据这些数据更新UI。然而,初学者在处理异步数据流时,可能会遇到数据已成功获取并打印到控制台,但组件界面却未能相应更新的问题。这通常是由于对React的状态管理机制理解不足所导致的。本教程将深入分析一个典型的异步数据获取场景,并提供一个健壮的解决方案,确保数据能够正确地在组件中显示。

问题剖析:为何数据未显示?

原始代码的目标是获取一系列资金池(Pools)的APY(年化收益率)数据,然后找出APY最高的资金池并显示其标题。尽管数据在控制台正确打印,UI却未能更新。这背后的主要原因有以下几点:

  1. 局部变量无法触发UI更新:在React函数组件中,直接修改诸如poolDetails这样的局部变量不会通知React重新渲染组件。React只有在state发生变化时,才会重新渲染组件及其子组件。
  2. 不恰当的数据结构:poolsArray最初被定义为一个空对象{},但后续操作中试图通过索引poolsArray[pool.targetedAPYId]来赋值,这对于追踪多个异步请求的结果并进行统一处理来说,并不是最优或最直观的方式。
  3. 异步操作协调不足:多个fetch请求是并行发生的。虽然使用counter来判断所有请求是否完成,但对poolsArray的更新和最终poolDetails的赋值逻辑,没有与React的状态更新机制有效结合。
  4. TypeScript类型安全问题:代码中存在多处@ts-ignore注释,这表明存在类型不匹配或类型推断不明确的问题,降低了代码的可读性和可维护性。
  5. 比较运算符使用:在某些比较场景中使用了非严格相等运算符==,虽然在某些情况下可能有效,但通常推荐使用严格相等运算符===以避免潜在的类型转换问题。

核心解决方案:利用React状态与异步控制

要解决上述问题,我们需要将异步获取的数据存储到组件的状态中,并确保在所有数据都准备好后才更新UI。

1. 利用 useState 管理 UI 状态

最关键的改变是将poolDetails转换为一个状态变量。当这个状态变量被更新时,React会知道组件需要重新渲染。

import React, { useEffect, useState } from 'react';

// ... 其他代码

export const FeaturedPool = () => {
  const [loading, setLoading] = useState(true);
  // 使用useState来存储最高APY的资金池信息
  const [featuredPool, setFeaturedPool] = useState(undefined);

  // ... useEffect 钩子
};

在数据获取完成后,我们不再直接赋值给poolDetails,而是调用setFeaturedPool来更新状态:

// ... 在所有数据获取和计算完成后
setFeaturedPool(foundPool);
setLoading(false); // 数据加载完成,设置加载状态为false

2. 优化数据结构以追踪异步结果

为了更好地管理每个资金池的APY数据,将poolsArray定义为一个包含targetedAPYId和apyReward的对象数组会更加清晰和类型安全。

// 定义一个类型来表示每个资金池的临时数据
export type PoolData = {
  targetedAPYId: string | undefined; // 考虑到targetedAPYId可能不存在
  apyReward: string;
};

// ... 在FeaturedPool组件内部
let poolsArray: PoolData[] = []; // 定义为数组

在forEach循环中,我们首先为每个资金池添加一个占位符到poolsArray:

POOLS?.filter((x) => x.stableCoins)?.forEach((pool) => {
  poolsArray.push({ targetedAPYId: pool.targetedAPYId, apyReward: "" });
  // ... fetch 请求
});

当fetch请求返回结果时,遍历poolsArray并更新对应的apyReward:

Petalica Paint
Petalica Paint

用AI为你的画自动上色!

下载
.then((res) => {
  const result = res.data.at(-1).apyReward.toFixed(2);
  poolsArray.forEach((poolItem) => {
    if (poolItem.targetedAPYId === pool.targetedAPYId) {
      poolItem.apyReward = result;
    }
  });
  // ... counter 逻辑
});

3. 精确协调异步请求完成状态

counter变量的逻辑是正确的,它确保所有预期的fetch请求都已完成。关键在于当counter达到预期值时,执行查找最高APY资金池的逻辑,并更新状态。

counter++;
if (counter === 3) { // 使用严格相等运算符
  // 提取所有APY奖励值,并找到最大值
  const arr = poolsArray.map((poolItem) => parseFloat(poolItem.apyReward)); // 转换为数字进行比较
  const max = Math.max(...arr);

  // 找到对应最大APY的资金池ID
  const poolKey = poolsArray.find((poolItem) => parseFloat(poolItem.apyReward) === max)?.targetedAPYId;

  if (poolKey) {
    // 从原始POOLS列表中找到完整的资金池信息
    const foundPool = POOLS.find((pool) => pool.targetedAPYId === poolKey);
    setFeaturedPool(foundPool); // 更新状态
  }
  setLoading(false); // 所有操作完成,关闭加载状态
}

注意:poolsArray.map((poolItem) => poolItem.apyReward)会得到字符串数组,Math.max在处理字符串时可能行为不符合预期。应先将字符串转换为数字,例如使用parseFloat。

4. TypeScript 类型安全

通过定义PoolData类型并正确使用PoolInfo,可以减少@ts-ignore的使用,提高代码的健壮性和可读性。确保POOLS变量的类型是PoolInfo[]。

完整示例代码

结合上述修正,FeaturedPool组件的最终代码如下:

import React, { useEffect, useState } from 'react';

// 假设 POOLS 是一个 PoolInfo 对象的数组,可能从其他文件导入或在此处定义。
// 例如:
// import { POOLS } from '../constants/pools';

// 定义用于临时存储APY数据的类型
export type PoolData = {
  targetedAPYId: string | undefined;
  apyReward: string;
};

// 定义资金池信息的类型,与问题中提供的结构一致
export type PoolInfo = {
  id: string;
  title: string;
  description: string;
  icon: string;
  score: number;
  risk: string;
  apyRange: string;
  targetedAPYId?: string;
    targetedAPY: string;
  tvlId?: string;
  strategy: string;
  vaultAddress: string;
  strategyAddress: string;
  zapAddress: string;
  isRetired?: boolean;
  stableCoins?: boolean;
  wantToken: string;
  isOld?: boolean;
  details?: string;
  benefits?: string[];
  promptTokens?: any[]; // 根据实际情况替换为Token[]
};

// 假设 POOLS 变量已定义并可用,例如:
const POOLS: PoolInfo[] = [
  { id: '1', title: 'Vault A', description: '', icon: '', score: 0, risk: '', apyRange: '', targetedAPY: '', strategy: '', vaultAddress: '', strategyAddress: '', zapAddress: '', targetedAPYId: 'vault-a-apy', stableCoins: true },
  { id: '2', title: 'Vault B', description: '', icon: '', score: 0, risk: '', apyRange: '', targetedAPY: '', strategy: '', vaultAddress: '', strategyAddress: '', zapAddress: '', targetedAPYId: 'vault-b-apy', stableCoins: true },
  { id: '3', title: 'Vault C', description: '', icon: '', score: 0, risk: '', apyRange: '', targetedAPY: '', strategy: '', vaultAddress: '', strategyAddress: '', zapAddress: '', targetedAPYId: 'vault-c-apy', stableCoins: true },
  { id: '4', title: 'Vault D', description: '', icon: '', score: 0, risk: '', apyRange: '', targetedAPY: '', strategy: '', vaultAddress: '', strategyAddress: '', zapAddress: '', targetedAPYId: 'vault-d-apy', stableCoins: false },
];


export const FeaturedPool = () => {
  const [loading, setLoading] = useState(true);
  const [featuredPool, setFeaturedPool] = useState(undefined);

  useEffect(() => {
    let counter = 0;
    // 定义 poolsArray 为 PoolData 类型的数组
    let poolsArray: PoolData[] = [];

    const stablePools = POOLS?.filter((x) => x.stableCoins);
    const totalStablePools = stablePools?.length || 0;

    if (totalStablePools === 0) {
      setLoading(false);
      return; // 没有符合条件的资金池,直接结束
    }

    stablePools?.forEach((pool) => {
      // 为每个符合条件的资金池添加一个占位符
      poolsArray.push({ targetedAPYId: pool.targetedAPYId, apyReward: "" });

      fetch("https://yields.llama.fi/chart/" + pool.targetedAPYId)
        .then((response) => {
          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
          }
          return response.json();
        })
        .then((res) => {
          // 确保数据存在且结构正确
          const latestData = res.data?.at(-1);
          const result = latestData?.apyReward !== undefined ? latestData.apyReward.toFixed(2) : "0.00";

          // 更新 poolsArray 中对应的 apyReward
          poolsArray.forEach((poolItem) => {
            if (poolItem.targetedAPYId === pool.targetedAPYId) {
              poolItem.apyReward = result;
            }
          });

          counter++;
          // 当所有请求都完成时
          if (counter === totalStablePools) { // 比较 counter 与实际的稳定币资金池数量
            // 将字符串APY转换为数字进行比较
            const apyValues = poolsArray.map((poolItem) => parseFloat(poolItem.apyReward));
            const maxApy = Math.max(...apyValues);

            // 找到具有最高APY的资金池的 targetedAPYId
            const poolKey = poolsArray.find((poolItem) => parseFloat(poolItem.apyReward) === maxApy)?.targetedAPYId;

            if (poolKey) {
              // 从原始 POOLS 列表中找到完整的资金池信息
              const foundPool = POOLS.find((p) => p.targetedAPYId === poolKey);
              setFeaturedPool(foundPool); // 更新状态
            }
            setLoading(false); // 关闭加载状态
          }
        })
        .catch((error) => {
          console.error("Error fetching APY data:", error);
          counter++; // 即使出错也要增加计数器,避免死锁
          if (counter === totalStablePools) {
            setLoading(false); // 确保在所有请求(包括失败的)完成后关闭加载状态
          }
        });
    });
  }, []); // 空数组表示只在组件挂载时运行一次

  return (
    <>
      {loading ? 

Loading...

:

Loaded {featuredPool?.title}

} ); };

注意事项与最佳实践

  1. 状态是UI更新的唯一触发器:始终记住,在React函数组件中,只有通过useState或useReducer管理的状态发生变化时,组件才会重新渲染。直接修改局部变量不会影响UI。
  2. 选择合适的数据结构:根据数据的特点和操作需求选择最佳的数据结构。对于需要按特定ID查找和更新的集合,如果ID是唯一的,对象可能更方便;但如果需要迭代、过滤或保持顺序,数组通常是更好的选择。本例中,使用PoolData[]数组并结合find方法进行更新,既保持了可读性也避免了@ts-ignore。
  3. 严格的异步流程控制:当依赖多个异步请求的结果时,务必使用计数器或其他Promise管理技术(如Promise.all)来确保所有依赖项都已完成,然后再执行最终的状态更新。
  4. 使用严格相等运算符(===):在JavaScript中,==会进行类型强制转换,可能导致意外行为。===则要求值和类型都相等,能有效避免潜在错误。
  5. 充分利用TypeScript的优势:为数据定义清晰的类型(如PoolInfo, PoolData),可以极大地提高代码的可读性、可维护性,并在开发阶段捕获潜在的类型错误。避免滥用@ts-ignore。
  6. 错误处理:在fetch请求中添加.catch()块,处理网络错误或API返回的非成功状态,提高应用的健壮性。
  7. 依赖项数组:useEffect的第二个参数是依赖项数组。空数组[]表示该效果只在组件挂载时运行一次。如果效果依赖于组件外部的某个变量,应将其包含在依赖项数组中。

通过遵循这些原则,您可以更有效地管理React组件中的异步数据流,确保UI能够及时、准确地响应数据变化。

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

554

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

374

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

731

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

477

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

394

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

991

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

656

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

551

2023.09.20

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

2

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.7万人学习

国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 1.0万人学习

React核心原理新老生命周期精讲
React核心原理新老生命周期精讲

共12课时 | 1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号