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

React Suspense最佳实践优化:组件内数据获取与状态存储需求

React Suspense 数据获取方案改进建议

问题背景

我正在为React Web应用添加进度指示器,基于React Suspense数据获取的实现方案完成了初始代码,但存在两个问题:

  • 返回的数据未存入组件状态,无法实现数据过滤等交互功能
  • suspender需声明在组件外部,不符合开发习惯

期望将组件改写为内部包含suspender和state的形式,寻求改进建议。

初始实现代码

export interface ISuspender<T> {
    read(): T;
}

const createSuspender = <T>(promise: Promise<T>): ISuspender<T> => {
    let status: string = 'pending';
    let response: T;

    const suspender: Promise<void> = promise.then(
        (result) => {
            status = "susccess";
            response = result;
        },
        (error) => {
            status = "error";
            response = error;
        }
    );
    const read = (): T => {
        switch (status) {
            case 'pending':
                throw suspender;
            case 'error':
                throw response;
            default:
                return response;
        }
    }
    return { read }
}

const getSetsSuspender: ISuspender<Scry.Set[]> = createSuspender(Scry.Sets.all());

export const MtgSetList: React.FunctionComponent = () => {
    const sets: Scry.Set[] = getSetsSuspender.read();

    return (
        <SetList sets={sets} />
    )
}

期望的组件结构

export const MtgSetList: React.FunctionComponent = () => {
    const getSetsSuspender: ISuspender<Scry.Set[]> = createSuspender(Scry.Sets.all());
    const [sets, setSets] = React.useState<Scry.Set[]>();

    useEffect(() => {        
        setSets(getSetsSuspender.read());
    }, [getSetsSuspender]);
   
    return (
        <SetList sets={sets} />
    )
}

改进方案

方案一:保留Suspense机制,同时将数据存入State

该方案既利用Suspense的进度指示器能力,又将数据存入组件State支持交互,且suspender逻辑完全在组件内部:

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

export interface ISuspender<T> {
    read(): T;
}

const createSuspender = <T>(promise: Promise<T>): ISuspender<T> => {
    // 修正拼写错误:susccess -> success,同时用联合类型约束status
    let status: 'pending' | 'success' | 'error' = 'pending';
    let response: T;

    const suspender: Promise<void> = promise.then(
        (result) => {
            status = "success";
            response = result;
        },
        (error) => {
            status = "error";
            response = error as unknown as T;
        }
    );

    const read = (): T => {
        switch (status) {
            case 'pending':
                throw suspender;
            case 'error':
                throw response;
            default:
                return response;
        }
    };

    return { read };
};

export const MtgSetList: React.FunctionComponent = () => {
    const [sets, setSets] = useState<Scry.Set[] | undefined>(undefined);
    // 用useRef缓存suspender,避免组件重渲染时重复发起请求
    const suspenderRef = useRef<ISuspender<Scry.Set[]> | null>(null);

    // 仅在组件首次渲染时创建suspender
    if (!suspenderRef.current) {
        suspenderRef.current = createSuspender(Scry.Sets.all());
    }

    // 渲染阶段调用read(),触发Suspense的加载状态(需父组件用<Suspense fallback={...}>包裹)
    const fetchedSets = suspenderRef.current.read();
    
    // 数据就绪后更新State,支持后续交互操作
    if (fetchedSets && sets !== fetchedSets) {
        setSets(fetchedSets);
    }

    return <SetList sets={sets} />;
};

关键说明:

  • 用useRef缓存suspender,确保组件重新渲染时不会重复发起API请求
  • 在渲染阶段调用read(),符合Suspense的工作机制,触发进度指示器(父组件需包裹<Suspense fallback={<你的进度指示器组件 />}>)
  • 数据获取成功后同步到State,可基于State实现过滤、排序等交互
  • 错误处理需配合React的ErrorBoundary组件,捕获read()抛出的错误

方案二:改用传统useEffect+State方案(放弃Suspense)

如果更习惯传统的异步数据获取模式,可直接用useEffect管理请求,同时维护加载、错误、数据状态,完全不需要Suspender:

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

export const MtgSetList: React.FunctionComponent = () => {
    const [sets, setSets] = useState<Scry.Set[] | undefined>(undefined);
    const [isLoading, setIsLoading] = useState(true);
    const [error, setError] = useState<Error | null>(null);

    useEffect(() => {
        const fetchSets = async () => {
            try {
                const fetchedSets = await Scry.Sets.all();
                setSets(fetchedSets);
            } catch (err) {
                setError(err as Error);
            } finally {
                setIsLoading(false);
            }
        };

        fetchSets();
    }, []);

    // 手动处理加载、错误状态
    if (isLoading) return <你的进度指示器组件 />;
    if (error) return <div>加载失败:{error.message}</div>;

    return <SetList sets={sets} />;
};

关键说明:

  • 完全在组件内部处理请求逻辑,符合你的开发习惯
  • 手动管理加载、错误、数据状态,无需依赖Suspense机制
  • 数据直接存入State,可直接实现过滤等交互功能

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 11:37:07