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

React问题:更新state后组件未重新渲染已映射状态

问题分析与修复方案

核心问题

  1. this指向丢失:setTimeout(component.getApplications, 30000)中,getApplications的this不再指向组件实例,导致更新state失败。
  2. 直接操作DOM违背React状态驱动原则:checkPing里手动修改DOM内容,React无法感知这些变化,组件重新渲染时会被初始的"Pinging..."覆盖。
  3. setState使用错误:setState是异步操作,不能用await直接等待,你在axios的then里用await setState完全无效。
  4. key值不合理:用index作为列表项key,当应用列表顺序变化时,React不会正确更新组件。

修复步骤

1. 修正this指向

把setTimeout的回调改成箭头函数,确保this指向组件实例:

setTimeout(() => this.getApplications(), 30000);

2. 用state管理ping状态

给每个应用添加ping状态(loading/running/offline),存在state中,render时根据状态渲染内容,不再直接操作DOM。

首先修改state结构:

this.state = {
    applications: [],
    pingStatus: {} // 用对象存储每个应用的ping状态,key为applicationId,value为状态值
}

然后修改checkPing,通过更新state来同步状态:

async checkPing(applicationId : number) {
    // 先设置为加载中状态
    this.setState(prev => ({
        pingStatus: {...prev.pingStatus, [applicationId]: 'loading'}
    }));
    try {
        const response = await axios.get(`${process.env.REACT_APP_API_URL}/applications/${applicationId}/ping`);
        const status = response.status === 404 ? 'offline' : 'running';
        this.setState(prev => ({
            pingStatus: {...prev.pingStatus, [applicationId]: status}
        }));
    } catch (error) {
        this.setState(prev => ({
            pingStatus: {...prev.pingStatus, [applicationId]: 'offline'}
        }));
    }
}

3. 修正setState的使用

getApplications中,axios返回后直接更新state,在state更新完成的回调里执行ping操作:

async getApplications() {
    try {
        const response = await axios.get(`${process.env.REACT_APP_API_URL}/applications`);
        // 初始化新应用的ping状态为loading
        const initialPingStatus = {};
        response.data.forEach(app => {
            initialPingStatus[app.id] = 'loading';
        });
        this.setState({
            applications: response.data,
            pingStatus: initialPingStatus
        }, async () => {
            // 状态更新完成后执行ping
            await this.pingApplications();
        });
    } catch (error) {
        console.log(error);
    } finally {
        setTimeout(() => this.getApplications(), 30000);
    }
}

4. 优化render逻辑,使用正确的key

map时用application.id作为key,根据pingStatus渲染对应状态:

{this.state.applications.map(application => {
    const status = this.state.pingStatus[application.id] || 'loading';
    let statusClass = 'bg-orange-400';
    let statusText = 'Pinging...';
    if (status === 'running') {
        statusClass = 'bg-green-500';
        statusText = 'Running';
    } else if (status === 'offline') {
        statusClass = 'bg-red-500';
        statusText = 'Offline';
    }
    return (<tr key={application.id} className="bg-white border-b">
        <th scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
            <a href={`/applications/${application.id}`}>{application.name}</a>
        </th>
        <td className="px-6 py-4">
            {application.repository.url.split("/").pop()}
        </td>
        <td className="px-6 py-4">
            <div className="flex items-center">
                <div className={`h-2.5 w-2.5 rounded-full ${statusClass} mr-2`}></div>
                {statusText}
            </div>
        </td>
    </tr>);
})}

5. 移除无用的componentDidUpdate

你的componentDidUpdate里判断的isLoading和error在state中不存在,且每次更新都执行ping会导致重复请求,直接删除这个生命周期函数。

完整修复后的代码

import axios from "axios";
import React from "react";

class Applications extends React.Component<any, any> {
    constructor() {
        super({});

        this.state = {
            applications: [],
            pingStatus: {}
        }

        this.logout = this.logout.bind(this);
    }

    async pingApplications() {
        for (const application of this.state.applications) {
            await this.checkPing(application.id);
        }
    }

    async checkPing(applicationId : number) {
        this.setState(prev => ({
            pingStatus: {...prev.pingStatus, [applicationId]: 'loading'}
        }));
        try {
            const response = await axios.get(`${process.env.REACT_APP_API_URL}/applications/${applicationId}/ping`);
            const status = response.status === 404 ? 'offline' : 'running';
            this.setState(prev => ({
                pingStatus: {...prev.pingStatus, [applicationId]: status}
            }));
        } catch (error) {
            this.setState(prev => ({
                pingStatus: {...prev.pingStatus, [applicationId]: 'offline'}
            }));
        }
    }

    async getApplications() {
        try {
            const response = await axios.get(`${process.env.REACT_APP_API_URL}/applications`);
            const initialPingStatus = {};
            response.data.forEach(app => {
                initialPingStatus[app.id] = 'loading';
            });
            this.setState({
                applications: response.data,
                pingStatus: initialPingStatus
            }, async () => {
                await this.pingApplications();
            });
        } catch (error) {
            console.log(error);
        } finally {
            setTimeout(() => this.getApplications(), 30000);
        }
    }

    logout() {
        localStorage.removeItem('access_token');
        document.location.reload()
    }

    async componentDidMount() {
        await this.getApplications();
    }

    render() {
        return (<div className="p-4">
            <div className="flex">
                <a href="/applications/create"
                   className="bg-green-500 hover:bg-green-400 text-white px-5 py-2 mb-6 rounded shadow text-center mr-4 block">
                    Create
                </a>
                <div className="bg-red-500 hover:bg-red-400 text-white px-5 py-2 mb-6 rounded shadow text-center"
                     onClick={this.logout}>
                    Logout
                </div>
            </div>
            <div className="relative overflow-x-auto shadow-md sm:rounded-lg">
                <table className="w-full text-sm text-left text-gray-500">
                    <thead className="text-xs text-gray-700 uppercase bg-gray-50">
                    <tr>
                        <th scope="col" className="px-6 py-3">
                            Name
                        </th>
                        <th scope="col" className="px-6 py-3">
                            Repository
                        </th>
                        <th scope="col" className="px-6 py-3">
                            Status
                        </th>
                    </tr>
                    </thead>
                    <tbody>
                    {this.state.applications.map(application => {
                        const status = this.state.pingStatus[application.id] || 'loading';
                        let statusClass = 'bg-orange-400';
                        let statusText = 'Pinging...';
                        if (status === 'running') {
                            statusClass = 'bg-green-500';
                            statusText = 'Running';
                        } else if (status === 'offline') {
                            statusClass = 'bg-red-500';
                            statusText = 'Offline';
                        }
                        return (<tr key={application.id} className="bg-white border-b">
                            <th scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
                                <a href={`/applications/${application.id}`}>{application.name}</a>
                            </th>
                            <td className="px-6 py-4">
                                {application.repository.url.split("/").pop()}
                            </td>
                            <td className="px-6 py-4">
                                <div className="flex items-center">
                                    <div className={`h-2.5 w-2.5 rounded-full ${statusClass} mr-2`}></div>
                                    {statusText}
                                </div>
                            </td>
                        </tr>);
                    })}
                    </tbody>
                </table>
            </div>
        </div>)
    }
}

export default Applications;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 04:53:17