React数据更新了useState列表视图没更新的问题解决

文章目录

  • 1. 前言
  • 2. 问题
  • 3. 问题分析
  • 4. 解决方案
  • 5. 总结

1. 前言

在 React18 项目里,我开发了一个包含 AI 生图功能的页面。该页面具备一个 AI 生图任务列表,同时有一个新建 AI 生图任务的按钮。用户点击按钮,就能创建新的生图任务,新任务的相关数据会被插入到列表中,初始状态显示为 Loading 图片。之后,系统会在后台持续轮询该任务的进度,当任务成功完成时,会触发回调函数,将列表中对应任务的 Loading 图片替换为正常的图片和数据。

2. 问题

然而,在实际使用过程中,我们发现了一个问题。当同时存在多个生图任务在进行轮询查询时,回调函数里获取到的useState列表数据并非最新状态。这就导致了任务明明已经成功返回结果,但列表中的数据却没有正确更新,出现显示异常的情况。

3. 问题分析

这个问题的根源在于 React 中 useState 钩子的特性。useState 是 React 用于在函数组件中添加状态的钩子。当调用 setState 函数更新状态时,React 会将新的状态值放入队列中,在合适的时机进行批量更新。这意味着在某些情况下,尤其是在异步操作(如轮询查询)的回调函数里,useState 所管理的状态值可能不会立即更新,而是保留着旧的值。所以,当多个任务同时进行轮询时,回调函数里获取到的 dataList 数据可能不是最新的数据,从而导致使用Array.find来匹配数据更新出现错误。

4. 解决方案

为了解决这个问题,我们采用了结合 useRef 钩子的方法。useRef 是 React 提供的另一个钩子,它返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(初始值)。ref 对象在组件的整个生命周期内保持不变。我们可以利用 useRef 来存储数据,并且在状态更新时同步更新 ref 对象的值。这样,在回调函数中,我们就可以从 useRef 中获取到最新的数据进行Array.find查找和替换等操作,而不是取useState 定义的数据来查找,然后再将更新后的数据同步给 useState 管理的状态。

  • 具体的代码实现:
import React, { useState, useRef, useEffect } from 'react';

// 假设这是任务数据的接口类型
interface I_TaskDataRes {
    taskId: string;
    // 其他属性...
}

const AIArtGenerationPage = () => {
    // 使用 useState 定义数据列表
    const [dataList, setDataList] = useState([]);
    // 使用 useRef 定义一个同样的数据列表,初始值为空数组
    const dataListRef = useRef([]);

    // 当 dataList 发生变化时,同步更新 dataListRef 的值
    useEffect(() => {
        dataListRef.current = dataList;
    }, [dataList]);

    // 处理新建生图任务的函数
    const handleCreateNewTask = () => {
        // 模拟创建新任务的数据
        const newTask: I_TaskDataRes = {
            taskId: Date.now().toString(),
            // 其他初始属性...
        };
        // 更新 dataList 状态,插入新任务
        setDataList((prevDataList) => [...prevDataList, newTask]);
        // 开始轮询任务进度
        startTaskPolling(newTask.taskId);
    };

    // 模拟轮询任务进度的函数
    const startTaskPolling = (taskId: string) => {
        // 模拟轮询逻辑
        const intervalId = setInterval(() => {
            // 模拟任务成功返回结果
            const mockResult: I_TaskDataRes = {
                taskId,
                // 其他成功后的属性...
            };
            // 触发任务成功的回调函数
            handleItemLoopSuccess(mockResult);
            // 清除轮询定时器
            clearInterval(intervalId);
        }, 2000);
    };

    // 任务成功的回调函数
    const handleItemLoopSuccess = (taskItem: I_TaskDataRes) => {
        // 从 dataListRef 中获取最新的数据列表
        const tempDataList = [...dataListRef.current];
        // 查找任务在列表中的索引
        const index = tempDataList.findIndex((item) => item.taskId === taskItem.taskId);
        if (index > -1) {
            // 替换列表中对应任务的数据
            tempDataList[index] = taskItem;
            // 更新 dataList 状态
            setDataList(() => [...tempDataList]);
        }
    };

    return (
        
    {dataList.map((item) => (
  • {/* 根据任务状态显示 Loading 图片或正常图片和数据 */} {item.isLoading ? ( Loading ) : ( Generated Art )}
  • ))}
); }; export default AIArtGenerationPage;
  • 代码解释
    • dataList:使用 useState 定义的任务数据列表,用于在组件中渲染任务列表。
    • dataListRef:使用 useRef 定义的可变对象,其 .current 属性存储着与 dataList 相同的数据。
    • 通过 useEffect 钩子,当 dataList 发生变化时,将其值同步到 dataListRef.current 中,确保 dataListRef 始终存储着最新的数据。
    • handleItemLoopSuccess 函数中,从 dataListRef.current 中获取最新的数据列表进行查找和替换操作,避免了使用 dataList 可能带来的旧数据问题。最后,将更新后的数据同步给 dataList,触发组件的重新渲染。

5. 总结

通过结合 useState 和 useRef 钩子,我们成功解决了 React 项目中数据更新异常的问题。useState 用于管理组件的状态,确保组件能够正确地重新渲染;useRef 则用于存储最新的数据,避免在异步操作的回调函数中获取到旧数据。这种方法充分利用了 React 提供的钩子特性,有效地解决了实际开发中遇到的问题,提升了项目的稳定性和用户体验。在今后的 React 开发中,当遇到类似的状态更新问题时,我们可以考虑采用这种结合 useState 和 useRef 的方法来解决。


本次分享就到这儿啦,我是鹏多多,如果您看了觉得有帮助,欢迎评论,关注,点赞,转发,我们下次见~

往期文章

  • flutter-使用extended_image操作图片的加载和状态处理以及缓存和下载
  • flutter-制作可缩放底部弹出抽屉评论区效果
  • flutter-实现Tabs吸顶的PageView效果
  • Vue2全家桶+Element搭建的PC端在线音乐网站
  • 助你上手Vue3全家桶之Vue3教程
  • 助你上手Vue3全家桶之VueX4教程
  • 助你上手Vue3全家桶之Vue-Router4教程
  • 超详细!Vue的九种通信方式
  • 超详细!Vuex手把手教程
  • 使用nvm管理node.js版本以及更换npm淘宝镜像源
  • vue中利用.env文件存储全局环境变量,以及配置vue启动和打包命令

个人主页

  • CSDN
  • GitHub
  • 简书
  • 博客园
  • 掘金

你可能感兴趣的:(React,react.js,javascript,前端)