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

React/TypeScript图片预加载遇更新深度超限,如何实现进度条?

解决React图片预加载的无限更新错误并实现进度条

我想在网站加载前实现一个进度条,通过预加载图片及其他资源将加载进度转换为百分比。用了以下React/TypeScript脚本做图片预加载,但遇到了「Maximum update depth exceeded」错误,错误提示:

This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

原代码如下:

import React from "react";
import ReactDOM from "react-dom";

interface AppInterface {
    loading: boolean,
    totalImagesLoaded: number
}

class App extends React.Component<{}, AppInterface> {
    
    private totalImagesToLoad = 0;

    constructor(props: {}) {
        super(props);
        this.state = {
            loading: false,
            totalImagesLoaded: 0
        }
    }

    setLoading = (loadingstate: boolean) => {
        this.setState({
            loading: loadingstate
        })
    }

    totalLoadingProgress = () => {
        this.setState({
            totalImagesLoaded: this.state.totalImagesLoaded + 1
        })
    }

    cacheImagesprogress = (srcArray: Array<string>) => {
        const self = this;

        srcArray.map((src:string, index:number) => {
            const xhr = new XMLHttpRequest();

            xhr.open("GET", src, true);
            xhr.onload = function (e) {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        self.totalLoadingProgress();
                    }
                }
            };
            xhr.send();
        });

    }
    
    componentDidMount() {
        this.setLoading(true);

        const folder = 'https://picsum.photos/'; // settings
        const images = [
            folder + '200/300',
            folder + '400/500',
            folder + '800/500',
        ]
        this.totalImagesToLoad = images.length;
        this.cacheImagesprogress(images);
    }

    componentDidUpdate() {
        if(this.state.totalImagesLoaded >= this.totalImagesToLoad) {
            this.setLoading(false);
        }
    }

    render() {
        return (
            <main className="app">
                {this.state.loading ? (
                    <>
                      <div>Loading..</div>
                    </>
                ) : (
                    <>
                        Done loading!
                    </>
                )}
            </main>
        )
    }
}

export default App;

const container = document.getElementById('root');

// Create a root.
const root = ReactDOM.createRoot(container);

// Initial render
root.render(<App />);

错误原因分析

componentDidUpdate会在每次state更新后触发。当图片加载完成,totalImagesLoaded等于totalImagesToLoad时,调用setLoading(false)会触发state更新,再次进入componentDidUpdate。此时判断条件依然成立,又会重复调用setLoading(false),形成无限循环,触发报错。

重构方案与实现

1. 修复无限循环

在componentDidUpdate中增加状态判断,仅当当前处于加载状态时才切换为已加载状态:

componentDidUpdate(prevProps, prevState) {
    if (this.state.loading && this.state.totalImagesLoaded >= this.totalImagesToLoad) {
        this.setLoading(false);
    }
}

2. 实现进度百分比计算

通过(已加载数量 / 总数量) * 100计算进度,同时处理总数量为0的边界情况避免出现NaN:

const progress = this.totalImagesToLoad > 0 
    ? Math.round((totalImagesLoaded / this.totalImagesToLoad) * 100)
    : 0;

3. 优化图片预加载逻辑

用原生Image对象代替XHR,更贴合图片预加载场景,代码更简洁:

cacheImagesProgress = (srcArray: Array<string>) => {
    this.totalImagesToLoad = srcArray.length;
    srcArray.forEach(src => {
        const img = new Image();
        img.onload = this.totalLoadingProgress;
        // 处理加载失败,避免进度卡住
        img.onerror = this.totalLoadingProgress;
        img.src = src;
    });
}

4. 优化state更新方式

使用函数式更新setState,避免依赖当前state值导致的异步更新问题:

totalLoadingProgress = () => {
    this.setState(prevState => ({
        totalImagesLoaded: prevState.totalImagesLoaded + 1
    }));
}

完整重构代码

import React from "react";
import ReactDOM from "react-dom";

interface AppInterface {
    loading: boolean;
    totalImagesLoaded: number;
}

class App extends React.Component<{}, AppInterface> {
    private totalImagesToLoad = 0;

    constructor(props: {}) {
        super(props);
        this.state = {
            loading: false,
            totalImagesLoaded: 0
        };
    }

    setLoading = (loadingState: boolean) => {
        this.setState({ loading: loadingState });
    };

    totalLoadingProgress = () => {
        // 函数式更新确保拿到最新的state值
        this.setState(prevState => ({
            totalImagesLoaded: prevState.totalImagesLoaded + 1
        }));
    };

    cacheImagesProgress = (srcArray: Array<string>) => {
        this.totalImagesToLoad = srcArray.length;
        srcArray.forEach(src => {
            const img = new Image();
            img.onload = this.totalLoadingProgress;
            // 处理加载失败,避免进度卡住
            img.onerror = this.totalLoadingProgress;
            img.src = src;
        });
    };

    componentDidMount() {
        this.setLoading(true);
        const folder = "https://picsum.photos/";
        const images = [
            folder + "200/300",
            folder + "400/500",
            folder + "800/500"
        ];
        this.cacheImagesProgress(images);
    }

    componentDidUpdate(prevProps, prevState) {
        // 仅当当前处于加载状态且所有图片加载完成时,才切换状态
        if (this.state.loading && this.state.totalImagesLoaded >= this.totalImagesToLoad) {
            this.setLoading(false);
        }
    }

    render() {
        const { loading, totalImagesLoaded } = this.state;
        const progress = this.totalImagesToLoad > 0 
            ? Math.round((totalImagesLoaded / this.totalImagesToLoad) * 100)
            : 0;

        return (
            <main className="app">
                {loading ? (
                    <div className="loading-container">
                        <div className="progress-bar">
                            <div 
                                className="progress-fill" 
                                style={{ width: `${progress}%` }}
                            />
                        </div>
                        <p>加载中:{progress}%</p>
                    </div>
                ) : (
                    <div>加载完成!</div>
                )}
            </main>
        );
    }
}

export default App;

const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
root.render(<App />);

可选样式补充

添加简单CSS让进度条更直观:

.loading-container {
    width: 80%;
    margin: 20px auto;
    text-align: center;
}

.progress-bar {
    width: 100%;
    height: 20px;
    border: 1px solid #ccc;
    border-radius: 10px;
    overflow: hidden;
    margin-bottom: 10px;
}

.progress-fill {
    height: 100%;
    background-color: #4CAF50;
    transition: width 0.3s ease;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 22:57:22