在现代前端开发中,优雅地处理异步数据加载状态是提升用户体验的关键因素之一。本文将介绍如何在Vite6+React18+TypeScript项目模板中封装一个通用的加载中和加载异常组件,帮助开发者统一处理异步操作的各种状态。
1.为什么需要?
在开发React应用时,我们经常需要处理数据加载时的等待状态以及数据加载失败时的错误提示,如果不进行统一封装,每个组件都需要单独处理这些状态,导致代码重复且难以维护。通过封装一个通用的组件,我们可以统一UI风格,减少重复代码,便于后期维护和修改。
2.组件实现
import React from 'react';
import { Spin, Alert } from 'antd';
interface LoadingErrorWrapperProps {
loading: boolean; // 加载状态
error?: Error | null; // 错误状态
children: React.ReactNode; // 子组件
loadingText?: string; // 自定义加载提示
errorText?: string; // 自定义错误提示
}
const LoadingErrorWrapper = ({
loading,
error,
children,
loadingText = 'Loading...',
errorText = 'An error occurred',
}: LoadingErrorWrapperProps) => {
if (loading) {
return <Spin tip={loadingText} />;
}
if (error) {
return (
<Alert message="Error" description={errorText} type="error" showIcon />
);
}
return <>{children}</>;
};
export default LoadingErrorWrapper;
属性说明
loading: 布尔值,表示是否处于加载状态
error: Error对象或null,表示是否发生错误
children: 需要包裹的子组件
loadingText: 可选,自定义加载提示文字
errorText: 可选,自定义错误提示文字
实现逻辑
组件内部采用简单的条件渲染逻辑:
当loading
为true时,显示加载中的Spin组件
当error
存在时,显示错误提示的Alert组件
当既没有加载也没有错误时,渲染子组件
3.组件使用
在项目中使用这个组件非常简单:
import { fetchDemoData } from '@/apis/demo';
import useRequest from '@/components/use-request';
import LoadingErrorWrapper from '@/components/wrap/loading-error-wrapper';
import { Button } from 'antd';
import { useState } from 'react';
interface Subject {
id: number;
name: string;
}
export default function Eg08() {
const [count, setCount] = useState<number>(1);
const [subject, setSubject] = useState<Subject[]>([]);
const { loading, error, run } = useRequest(
async () => {
return await fetchDemoData(count).then((res) => {
setSubject(res.data.data.subject);
});
},
{
refreshDeps: [count],
}
);
return (
<div
style={{
padding: '100px',
border: '1px solid red',
}}
>
<Button onClick={() => setCount(count + 1)}>增加1</Button>
<Button onClick={run}>查询</Button>
<p>useRequestLoading: {JSON.stringify(loading)},</p>
<p>useRequestError: {JSON.stringify(error)}</p>
<LoadingErrorWrapper loading={loading} error={error}>
<ul>
{subject.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</LoadingErrorWrapper>
</div>
);
}
如果需要自定义提示信息,可以这样使用:
<LoadingErrorWrapper
loading={loading}
error={error}
loadingText="正在加载用户数据..."
errorText="加载用户数据失败,请稍后重试"
>
{/* 子组件 */}
</LoadingErrorWrapper>
4.组件扩展及思考
这个基础组件还可以进一步扩展:
自定义加载动画:通过props传入自定义的加载组件
重试机制:在错误状态下添加重试按钮
空状态处理:增加对空数据的处理逻辑
主题定制:支持不同的主题样式
例如,添加重试功能的扩展版本:
interface ExtendedLoadingErrorWrapperProps extends LoadingErrorWrapperProps {
onRetry?: () => void;
}
const ExtendedLoadingErrorWrapper = ({
onRetry,
...props
}: ExtendedLoadingErrorWrapperProps) => {
if (props.error) {
return (
<div className="error-container">
<Alert
message="Error"
description={props.errorText}
type="error"
showIcon
/>
{onRetry && (
<Button type="primary" onClick={onRetry}>
重试
</Button>
)}
</div>
);
}
return <LoadingErrorWrapper {...props} />;
};
再比如,支持传入自定义加载组件,通过扩展原有的LoadingErrorWrapper
组件,增加customLoader
属性:
import React from 'react';
import { Spin, Alert } from 'antd';
interface LoadingErrorWrapperProps {
loading: boolean;
error?: Error | null;
children: React.ReactNode;
loadingText?: string;
errorText?: string;
customLoader?: React.ReactNode; // 新增自定义加载组件
}
const LoadingErrorWrapper = ({
loading,
error,
children,
loadingText = 'Loading...',
errorText = 'An error occurred',
customLoader, // 新增
}: LoadingErrorWrapperProps) => {
if (loading) {
return customLoader || <Spin tip={loadingText} />; // 优先使用自定义加载器
}
if (error) {
return (
<Alert message="Error" description={errorText} type="error" showIcon />
);
}
return <>{children}</>;
};
export default LoadingErrorWrapper;