在 src/pages/view/components/viewers/stack.image.viewer.tsx 文件中,当 viewport.setStack(imageUrls, imageIndex) 被调用时,如果访问某个 URL 时请求响应有错误,现有的 try-catch 并不能防护到这种情况,会导致图像查看器崩溃。
try {
console.log(`重新加载图像----开始`);
await viewport.setStack(imageUrls, imageIndex);
console.log(`重新加载图像----结束`);
} catch (error) {
// 只能捕获同步错误
console.error('[stack.image.viewer] Error setting image stack:', error);
}
局限性:
采用 React Error Boundary + 状态管理错误传递 + 增强错误处理 的三重防护策略:
React Error Boundary 的限制:
原问题代码:
useEffect(() => {
const setup = async () => {
try {
await safeSetStack(viewport, imageUrls, imageIndex);
} catch (error) {
throw error; // ❌ 在 async 函数中抛出 → Promise rejection → Error Boundary 捕获不到!
}
};
setup(); // ❌ 没有 .catch() 处理 Promise rejection
}, [dependencies]);
实现原理:
改进后的代码:
// 1. 添加错误状态
const [renderError, setRenderError] = React.useState<Error | null>(null);
useEffect(() => {
const setup = async () => {
// 清除之前的错误状态
setRenderError(null);
try {
await safeSetStack(viewport, imageUrls, imageIndex);
} catch (error) {
// 2. 不直接抛出,而是保存到状态
const enhancedError = enhanceError(error, { imageUrls, imageIndex, viewportId });
if (isCriticalError(enhancedError)) {
setRenderError(enhancedError); // ✅ 保存到状态,触发重新渲染
return; // 停止执行后续代码
}
}
};
setup();
}, [dependencies]);
// 3. 在渲染期间检查并抛出错误
if (renderError) {
throw renderError; // ✅ Error Boundary 可以捕获!
}
return <div>...</div>;
工作流程:
异步错误发生 → setRenderError(error) → 触发重新渲染
→ 检查 renderError → throw error → Error Boundary 捕获
工作原理:
重试机制详解:
// 1. 初始状态:正常渲染
<ImageViewerErrorBoundary>
<StackViewer /> // 正常显示图像
</ImageViewerErrorBoundary>
// 2. 发生错误后:显示错误UI
<ImageViewerErrorBoundary hasError={true}>
<div>错误提示界面</div> // 显示错误信息和操作按钮
</ImageViewerErrorBoundary>
// 3. 用户点击重试:重新渲染
<ImageViewerErrorBoundary hasError={false}>
<StackViewer /> // 重新执行图像加载逻辑
</ImageViewerErrorBoundary>
// 严重错误:抛出给 Error Boundary 处理
if (isCriticalError(enhancedError)) {
throw enhancedError; // 认证失败、权限错误等
}
// 非严重错误:仅记录日志,继续运行
console.warn('图像加载警告:', enhancedError);
function isCriticalError(error: Error): boolean {
const message = error.message.toLowerCase();
// 需要用户干预的严重错误
const criticalErrors = [
'图像url列表为空',
'图像索引无效',
'401', // 认证失败
'403', // 权限不足
'404', // 文件不存在
'500', // 服务器错误
];
return criticalErrors.some(keyword => message.includes(keyword));
}
文件: src/pages/view/components/viewers/ImageViewerErrorBoundary.tsx
功能特性:
文件: src/pages/view/components/viewers/stack.image.viewer.tsx
改进内容:
StackViewerWithErrorBoundarygraph TD
A[开始加载图像] --> B{验证图像URLs}
B -->|失败| C[抛出严重错误]
B -->|成功| D{验证图像索引}
D -->|失败| C
D -->|成功| E[设置图像栈]
E -->|超时| F[抛出超时错误]
E -->|网络错误| G{判断错误类型}
E -->|认证错误| C
E -->|成功| H[图像加载完成]
G -->|严重错误| C
G -->|轻微错误| I[记录警告日志]
C --> J[Error Boundary 捕获]
J --> K[显示错误界面]
K --> L{用户点击重试}
L -->|是| A
L -->|否| M[结束]
import { StackViewerWithErrorBoundary } from '@/pages/view/components/viewers/stack.image.viewer';
// 在父组件中使用
<StackViewerWithErrorBoundary
imageIndex={0}
imageUrls={imageUrls}
viewportId="viewport1"
renderingEngineId="engine1"
selected={true}
maxRetries={3}
onError={(error, errorInfo) => {
// 自定义错误处理逻辑
console.error('图像加载错误:', error);
}}
/>
// 自定义错误处理
const handleError = (error: Error, errorInfo: ErrorInfo) => {
// 上报到错误监控系统
errorReportingService.captureException(error, {
extra: errorInfo,
tags: { component: 'ImageViewer' }
});
// 显示用户通知
toast.error('图像加载失败,请稍后重试');
};
// 自定义重试策略
const CustomErrorFallback = ({ error, retry, retryCount, maxRetries }) => {
return (
<div className="custom-error-ui">
<h3>图片加载失败</h3>
<p>错误:{error.message}</p>
{retryCount < maxRetries ? (
<button onClick={retry}>
重试 ({maxRetries - retryCount} 次机会)
</button>
) : (
<button onClick={() => window.location.reload()}>
刷新页面
</button>
)}
</div>
);
};
// 使用自定义fallback
<ImageViewerErrorBoundary
maxRetries={5}
onError={handleError}
fallback={CustomErrorFallback}
/>
| 特性 | 原有方案 | 改进方案 |
|---|---|---|
| 异步错误捕获 | ❌ 无法捕获 | ✅ 完全捕获 |
| 用户体验 | ❌ 崩溃/空白 | ✅ 友好提示 |
| 错误恢复 | ❌ 无法恢复 | ✅ 自动重试 |
| 错误分类 | ❌ 无分类 | ✅ 智能分类 |
| 监控集成 | ❌ 无接口 | ✅ 预留接口 |
| 超时保护 | ❌ 无保护 | ✅ 30秒超时 |
withImageViewerErrorBoundary 方便包装describe('ImageViewerErrorBoundary', () => {
it('应该正确捕获图像加载错误', () => {
// 测试错误捕获逻辑
});
it('应该在达到最大重试次数后禁用重试按钮', () => {
// 测试重试限制逻辑
});
it('应该显示正确的错误消息', () => {
// 测试错误消息映射
});
});
describe('StackViewer 错误处理集成', () => {
it('应该在网络错误时显示重试选项', async () => {
// 模拟网络错误场景
});
it('应该在认证失败时显示登录提示', async () => {
// 模拟认证错误场景
});
});
// 接入 Sentry
onError={(error, errorInfo) => {
Sentry.captureException(error, {
contexts: { errorInfo },
tags: { component: 'ImageViewer' }
});
}
// 图像缓存和离线支持
const cacheStrategy = {
enableCache: true,
maxCacheSize: 100MB,
offlineSupport: true
};
// 预加载和批量处理
const preloadStrategy = {
preloadNext: true,
batchSize: 5,
priority: 'high'
};
这次改进彻底解决了图像加载过程中的异步错误处理问题,通过 React Error Boundary 和增强的错误处理逻辑,实现了:
该方案既解决了技术问题,又提供了优秀的用户体验,是一个成熟、可靠的错误处理解决方案。