forked from 77media/video-flow
90 lines
2.6 KiB
TypeScript
90 lines
2.6 KiB
TypeScript
'use client';
|
|
|
|
import React from 'react';
|
|
import { AlertTriangle, RefreshCw } from 'lucide-react';
|
|
import { motion } from 'framer-motion';
|
|
|
|
interface ErrorBoundaryState {
|
|
hasError: boolean;
|
|
error: Error | null;
|
|
}
|
|
|
|
interface ErrorBoundaryProps {
|
|
children: React.ReactNode;
|
|
fallback?: React.ComponentType<{ error: Error; resetError: () => void }>;
|
|
}
|
|
|
|
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
constructor(props: ErrorBoundaryProps) {
|
|
super(props);
|
|
this.state = { hasError: false, error: null };
|
|
}
|
|
|
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
return { hasError: true, error };
|
|
}
|
|
|
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
console.error('Error caught by boundary:', error, errorInfo);
|
|
}
|
|
|
|
resetError = () => {
|
|
this.setState({ hasError: false, error: null });
|
|
};
|
|
|
|
render() {
|
|
if (this.state.hasError) {
|
|
if (this.props.fallback) {
|
|
const FallbackComponent = this.props.fallback;
|
|
return <FallbackComponent error={this.state.error!} resetError={this.resetError} />;
|
|
}
|
|
|
|
return (
|
|
<motion.div
|
|
className="flex flex-col items-center justify-center min-h-[400px] p-6 bg-red-500/10 border border-red-500/20 rounded-lg"
|
|
initial={{ opacity: 0, scale: 0.9 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
transition={{ duration: 0.3 }}
|
|
>
|
|
<AlertTriangle className="w-12 h-12 text-red-500 mb-4" />
|
|
<h3 className="text-lg font-semibold text-red-400 mb-2">出现错误</h3>
|
|
<p className="text-white/70 text-center mb-4 max-w-md">
|
|
{this.state.error?.message || '组件渲染时发生错误,请尝试刷新页面或重置组件'}
|
|
</p>
|
|
<motion.button
|
|
className="flex items-center gap-2 px-4 py-2 bg-red-500/20 text-red-400 border border-red-500/30 rounded-lg hover:bg-red-500/30 transition-colors"
|
|
onClick={this.resetError}
|
|
whileHover={{ scale: 1.05 }}
|
|
whileTap={{ scale: 0.95 }}
|
|
>
|
|
<RefreshCw className="w-4 h-4" />
|
|
重试
|
|
</motion.button>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
return this.props.children;
|
|
}
|
|
}
|
|
|
|
// Hook 版本的错误边界
|
|
export function useErrorBoundary() {
|
|
const [error, setError] = React.useState<Error | null>(null);
|
|
|
|
const resetError = React.useCallback(() => {
|
|
setError(null);
|
|
}, []);
|
|
|
|
const captureError = React.useCallback((error: Error) => {
|
|
setError(error);
|
|
}, []);
|
|
|
|
React.useEffect(() => {
|
|
if (error) {
|
|
throw error;
|
|
}
|
|
}, [error]);
|
|
|
|
return { captureError, resetError };
|
|
}
|