React 版本:18.x 难度:中级
考察要点:
- React.lazy 和 Suspense 的工作原理
- 动态导入和代码分割策略
- 加载状态和错误处理
- 性能优化和预加载技术
解答:
1. 概念解释
基本定义:
- React.lazy:允许动态导入组件的函数
- Suspense:在组件加载时显示后备 UI 的组件
- 代码分割:将应用拆分成多个包,按需加载
工作原理:
- React.lazy 返回一个 Promise
- Suspense 捕获加载状态
- 自动处理加载和错误边界
应用场景:
- 路由级别代码分割
- 大型组件按需加载
- 条件渲染的复杂组件
2. 代码示例
基础示例:
import React, { Suspense } from 'react';
// 👉 基础的动态导入
const LazyComponent = React.lazy(() =>
import('./HeavyComponent')
);
// 👉 基础加载组件
const LoadingSpinner: React.FC = () => (
<div className="loading-spinner">Loading...</div>
);
// 👉 使用 Suspense 包装懒加载组件
export const BasicExample: React.FC = () => {
return (
<Suspense fallback={<LoadingSpinner />}>
<LazyComponent />
</Suspense>
);
};
进阶示例:
import React, { Suspense, useState, useTransition } from 'react';
// 👉 定义错误边界组件
class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
{ hasError: boolean }
> {
constructor(props: { children: React.ReactNode }) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <div>Something went wrong. Please try again.</div>;
}
return this.props.children;
}
}
// 👉 懒加载组件包装器
interface LazyComponentProps {
componentPath: string;
}
const LazyLoadWrapper: React.FC<LazyComponentProps> = ({ componentPath }) => {
const [isPending, startTransition] = useTransition();
const LazyComponent = React.lazy(() => import(componentPath));
return (
<div>
{isPending && <LoadingSpinner />}
<ErrorBoundary>
<Suspense fallback={<LoadingSpinner />}>
<LazyComponent />
</Suspense>
</ErrorBoundary>
</div>
);
};
// 👉 使用示例
export const AdvancedExample: React.FC = () => {
const [showComponent, setShowComponent] = useState(false);
const handleClick = () => {
startTransition(() => {
setShowComponent(true);
});
};
return (
<div>
<button onClick={handleClick}>Load Component</button>
{showComponent && (
<LazyLoadWrapper componentPath="./HeavyComponent" />
)}
</div>
);
};
3. 注意事项与最佳实践
❌ 常见错误示例:
// ❌ 错误示范:在条件语句中使用 React.lazy
const BadComponent: React.FC<{ condition: boolean }> = ({ condition }) => {
const DynamicComponent = condition
? React.lazy(() => import('./ComponentA'))
: React.lazy(() => import('./ComponentB'));
return (
<Suspense fallback={<div>Loading...</div>}>
<DynamicComponent />
</Suspense>
);
};
// ❌ 错误示范:没有错误处理
const AnotherBadComponent: React.FC = () => {
const LazyComponent = React.lazy(() => import('./NonExistentComponent'));
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
};
✅ 正确实现方式:
// ✅ 正确示范:预定义懒加载组件
const ComponentA = React.lazy(() => import('./ComponentA'));
const ComponentB = React.lazy(() => import('./ComponentB'));
const GoodComponent: React.FC<{ condition: boolean }> = ({ condition }) => {
return (
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
{condition ? <ComponentA /> : <ComponentB />}
</Suspense>
</ErrorBoundary>
);
};
// ✅ 正确示范:使用预加载
const PreloadableComponent = {
Component: React.lazy(() => import('./HeavyComponent')),
preload: () => import('./HeavyComponent')
};
const AnotherGoodComponent: React.FC = () => {
const handleMouseEnter = () => {
// 鼠标悬停时预加载
PreloadableComponent.preload();
};
return (
<div onMouseEnter={handleMouseEnter}>
<Suspense fallback={<LoadingSpinner />}>
<PreloadableComponent.Component />
</Suspense>
</div>
);
};
4. 性能优化
import React, { Suspense, useCallback, useMemo } from 'react';
// 👉 优化的加载策略
const useOptimizedLazyLoad = (componentPath: string) => {
// 使用 useMemo 缓存懒加载组件
const LazyComponent = useMemo(
() => React.lazy(() => import(componentPath)),
[componentPath]
);
// 预加载函数
const preload = useCallback(() => {
import(componentPath);
}, [componentPath]);
return { LazyComponent, preload };
};
// 👉 实现组件
export const OptimizedComponent: React.FC = () => {
const { LazyComponent, preload } = useOptimizedLazyLoad('./HeavyComponent');
return (
<div onMouseEnter={preload}>
<Suspense fallback={<LoadingSpinner />}>
<LazyComponent />
</Suspense>
</div>
);
};
5. 测试策略
import { render, screen, fireEvent } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
describe('LazyLoadComponent', () => {
it('should show loading state initially', () => {
render(<OptimizedComponent />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('should load component on mouse enter', async () => {
render(<OptimizedComponent />);
await act(async () => {
fireEvent.mouseEnter(screen.getByRole('div'));
});
// 等待组件加载
const component = await screen.findByTestId('heavy-component');
expect(component).toBeInTheDocument();
});
});
6. 实际应用示例
import React, { Suspense, useTransition } from 'react';
import { Routes, Route } from 'react-router-dom';
// 👉 路由级别的代码分割
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Profile = React.lazy(() => import('./pages/Profile'));
const Settings = React.lazy(() => import('./pages/Settings'));
// 👉 加载状态组件
const PageLoader: React.FC = () => (
<div className="page-loader">
<LoadingSpinner />
<p>Loading page...</p>
</div>
);
// 👉 路由配置
export const AppRoutes: React.FC = () => {
const [isPending, startTransition] = useTransition();
return (
<>
{isPending && <PageLoader />}
<ErrorBoundary>
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</ErrorBoundary>
</>
);
};