组件级异步加载与预加载策略
组件级异步加载与预加载策略
1. 核心架构设计
1.1 异步组件加载器
// 组件加载状态枚举
enum ComponentLoadStatus {
IDLE = 'idle',
LOADING = 'loading',
LOADED = 'loaded',
ERROR = 'error'
}
// 组件配置接口
interface ComponentConfig {
name: string;
path: string; // 组件路径
chunkName?: string; // Webpack Chunk 名称
preload?: boolean; // 是否预加载
prefetch?: boolean; // 是否预获取
loadingComponent?: string; // 加载中组件
errorComponent?: string; // 错误组件
timeout?: number; // 加载超时时间
retryCount?: number; // 重试次数
}
// 组件缓存项
interface ComponentCacheItem {
component: any;
status: ComponentLoadStatus;
loadTime: number;
lastUsed: number;
size: number;
}
1.2 高级组件加载器
class AdvancedComponentLoader {
private componentCache: Map<string, ComponentCacheItem> = new Map();
private loadingQueue: Map<string, Promise<any>> = new Map();
private preloadQueue: Set<string> = new Set();
private observer: IntersectionObserver;
private maxCacheSize: number = 50; // 最大缓存组件数
private cacheSize: number = 0;
constructor() {
// 初始化 IntersectionObserver 用于可视区域预加载
this.observer = new IntersectionObserver(
this.handleIntersection.bind(this),
{
rootMargin: '100px 0px', // 提前 100px 加载
threshold: 0.1
}
);
}
// 异步加载组件
async loadComponent(config: ComponentConfig): Promise<any> {
const { name, path, retryCount = 3, timeout = 30000 } = config;
// 检查缓存
if (this.componentCache.has(name)) {
const cached = this.componentCache.get(name)!;
cached.lastUsed = Date.now();
return cached.component;
}
// 检查是否正在加载
if (this.loadingQueue.has(name)) {
return this.loadingQueue.get(name)!;
}
// 创建加载 Promise
const loadPromise = this.loadWithRetry(path, retryCount, timeout)
.then(component => {
this.cacheComponent(name, component);
this.loadingQueue.delete(name);
return component;
})
.catch(error => {
this.loadingQueue.delete(name);
throw error;
});
this.loadingQueue.set(name, loadPromise);
return loadPromise;
}
// 带重试的加载
private async loadWithRetry(
path: string,
retryCount: number,
timeout: number
): Promise<any> {
for (let attempt = 1; attempt <= retryCount; attempt++) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const component = await this.importComponent(path, controller.signal);
clearTimeout(timeoutId);
return component;
} catch (error) {
console.warn(`组件加载失败 (尝试 ${attempt}/${retryCount}):`, error);
if (attempt === retryCount) {
throw error;
}
// 指数退避重试
await this.delay(Math.pow(2, attempt) * 1000);
}
}
}
// 动态导入组件
private async importComponent(path: string, signal?: AbortSignal): Promise<any> {
// 使用 Webpack 魔法注释
const component = await import(
/* webpackChunkName: "[request]" */
/* webpackMode: "lazy" */
/* webpackPrefetch: true */
`@/components/${path}`
);
if (signal?.aborted) {
throw new Error('加载被取消');
}
return component.default || component;
}
// 缓存组件
private cacheComponent(name: string, component: any): void {
const cacheItem: ComponentCacheItem = {
component,
status: ComponentLoadStatus.LOADED,
loadTime: Date.now(),
lastUsed: Date.now(),
size: this.estimateSize(component)
};
// 检查缓存大小
if (this.cacheSize + cacheItem.size > this.maxCacheSize) {
this.evictCache();
}
this.componentCache.set(name, cacheItem);
this.cacheSize += cacheItem.size;
}
// 估算组件大小
private estimateSize(component: any): number {
// 简单的估算方法
return JSON.stringify(component).length;
}
// 缓存淘汰策略 (LRU)
private evictCache(): void {
const entries = Array.from(this.componentCache.entries());
const sorted = entries.sort((a, b) => a[1].lastUsed - b[1].lastUsed);
const toRemove = sorted.slice(0, Math.floor(sorted.length * 0.2)); // 移除 20%
toRemove.forEach(([name]) => {
const item = this.componentCache.get(name)!;
this.cacheSize -= item.size;
this.componentCache.delete(name);
});
}
// 预加载组件
preloadComponent(config: ComponentConfig): void {
const { name, path } = config;
if (this.componentCache.has(name) || this.loadingQueue.has(name)) {
return;
}
// 使用 requestIdleCallback 在空闲时预加载
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
this.loadComponent(config).catch(() => {
// 静默处理预加载错误
});
});
} else {
// 回退方案
setTimeout(() => {
this.loadComponent(config).catch(() => {});
}, 1000);
}
}
// 观察元素进行智能预加载
observeElement(element: HTMLElement, config: ComponentConfig): void {
this.observer.observe(element);
this.preloadQueue.add(config.name);
}
private handleIntersection(entries: IntersectionObserverEntry[]): void {
entries.forEach(entry => {
if (entry.isIntersecting) {
const element = entry.target;
this.observer.unobserve(element);
// 触发预加载
this.preloadQueue.forEach(name => {
// 这里需要根据元素找到对应的配置
// 简化实现
});
}
});
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 清理缓存
clearCache(): void {
this.componentCache.clear();
this.loadingQueue.clear();
this.preloadQueue.clear();
this.cacheSize = 0;
}
// 获取缓存状态
getCacheStats(): { size: number; count: number; hitRate: number } {
return {
size: this.cacheSize,
count: this.componentCache.size,
hitRate: this.calculateHitRate()
};
}
private calculateHitRate(): number {
// 实现命中率计算
return 0;
}
}
// 全局加载器实例
export const componentLoader = new AdvancedComponentLoader();
2. Vue 异步组件实现
2.1 高级异步组件工厂
// Vue 异步组件工厂
export function createAsyncComponent(config: ComponentConfig) {
const {
name,
path,
loadingComponent = 'LoadingSpinner',
errorComponent = 'ErrorDisplay',
timeout = 30000,
retryCount = 3
} = config;
return defineAsyncComponent({
// 加载函数
loader: () => componentLoader.loadComponent(config),
// 加载中组件
loadingComponent: defineComponent({
name: `${name}Loading`,
setup() {
return () => h(resolveComponent(loadingComponent));
}
}),
// 错误组件
errorComponent: defineComponent({
name: `${name}Error`,
props: {
error: {
type: Error,
required: true
},
retry: {
type: Function,
required: true
}
},
setup(props) {
const handleRetry = () => {
props.retry();
};
return () => h(resolveComponent(errorComponent), {
error: props.error,
onRetry: handleRetry
});
}
}),
// 延迟显示加载状态(避免闪烁)
delay: 200,
// 超时时间
timeout,
// 重试逻辑
onError(error, retry, fail, attempts) {
if (error.message.includes('Failed to fetch') && attempts <= retryCount) {
// 指数退避重试
retry();
} else {
fail();
}
}
});
}
2.2 组件注册管理器
class ComponentRegistry {
private registry: Map<string, ComponentConfig> = new Map();
private dependencies: Map<string, string[]> = new Map();
// 注册组件
registerComponent(config: ComponentConfig): void {
this.registry.set(config.name, config);
}
// 注册组件依赖关系
registerDependency(componentName: string, dependencies: string[]): void {
this.dependencies.set(componentName, dependencies);
}
// 获取组件配置
getComponentConfig(name: string): ComponentConfig | undefined {
return this.registry.get(name);
}
// 预加载组件及其依赖
async preloadComponentWithDependencies(name: string): Promise<void> {
const config = this.getComponentConfig(name);
if (!config) return;
// 预加载依赖
const deps = this.dependencies.get(name) || [];
await Promise.all(
deps.map(dep => this.preloadComponent(dep))
);
// 预加载组件本身
await this.preloadComponent(name);
}
// 智能预加载(基于路由和用户行为)
setupIntelligentPreloading(router: any): void {
// 路由变化时预加载相关组件
router.afterEach((to: any) => {
this.preloadRouteComponents(to);
});
// 鼠标悬停预加载
this.setupHoverPreloading();
}
private async preloadRouteComponents(route: any): Promise<void> {
const routeComponents = this.getComponentsForRoute(route);
// 立即预加载关键组件
const criticalComponents = routeComponents.slice(0, 2);
await Promise.all(
criticalComponents.map(comp => this.preloadComponent(comp))
);
// 延迟预加载非关键组件
setTimeout(() => {
const nonCriticalComponents = routeComponents.slice(2);
nonCriticalComponents.forEach(comp => this.preloadComponent(comp));
}, 1000);
}
private setupHoverPreloading(): void {
document.addEventListener('mouseover', this.handleHover.bind(this), {
capture: true,
passive: true
});
}
private handleHover(event: MouseEvent): void {
const target = event.target as HTMLElement;
const componentName = this.detectComponentFromElement(target);
if (componentName) {
componentLoader.preloadComponent(
this.getComponentConfig(componentName)!
);
}
}
private detectComponentFromElement(element: HTMLElement): string | null {
// 根据元素属性检测对应的组件
// 简化实现
return null;
}
private getComponentsForRoute(route: any): string[] {
// 根据路由获取需要加载的组件
// 简化实现
return [];
}
private async preloadComponent(name: string): Promise<void> {
const config = this.getComponentConfig(name);
if (config) {
componentLoader.preloadComponent(config);
}
}
}
export const componentRegistry = new ComponentRegistry();
3. React 异步组件实现
3.1 React Suspense 组件
// React 异步组件高阶函数
export function withAsyncComponent<T extends object>(
config: ComponentConfig
) {
const { name, path } = config;
const AsyncComponent = React.lazy(() =>
componentLoader.loadComponent(config).then(component => ({
default: component
}))
);
// 错误边界组件
class ErrorBoundary extends React.Component<
{ fallback: React.ReactNode; children: React.ReactNode },
{ hasError: boolean; error?: Error }
> {
constructor(props: any) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: any) {
console.error('组件加载错误:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
// 返回包装后的组件
return React.forwardRef<T, any>((props, ref) => (
<ErrorBoundary
fallback={
<div>
<h3>组件加载失败</h3>
<button onClick={() => window.location.reload()}>重试</button>
</div>
}
>
<React.Suspense
fallback={
<div className="loading-container">
<div className="loading-spinner" />
<span>加载中...</span>
</div>
}
>
<AsyncComponent {...props} ref={ref} />
</React.Suspense>
</ErrorBoundary>
));
}
3.2 React 预加载 Hook
// 预加载 Hook
export function useComponentPreload(componentName: string): {
preload: () => void;
isPreloaded: boolean;
} {
const [isPreloaded, setIsPreloaded] = useState(false);
const config = componentRegistry.getComponentConfig(componentName);
const preload = useCallback(() => {
if (!config || isPreloaded) return;
componentLoader
.loadComponent(config)
.then(() => {
setIsPreloaded(true);
})
.catch(() => {
// 静默处理错误
});
}, [config, isPreloaded]);
// 自动预加载(在空闲时)
useEffect(() => {
if (!config) return;
const handleIdle = () => {
if ('requestIdleCallback' in window) {
requestIdleCallback(preload);
} else {
// 回退方案
setTimeout(preload, 1000);
}
};
handleIdle();
}, [config, preload]);
return { preload, isPreloaded };
}
// 可视区域预加载 Hook
export function useVisiblePreload(
ref: React.RefObject<HTMLElement>,
componentName: string
): void {
const config = componentRegistry.getComponentConfig(componentName);
useEffect(() => {
if (!ref.current || !config) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
componentLoader.preloadComponent(config);
observer.disconnect();
}
},
{
rootMargin: '100px 0px',
threshold: 0.1
}
);
observer.observe(ref.current);
return () => {
observer.disconnect();
};
}, [ref, config]);
}
4. 实际应用示例
4.1 Vue 应用集成
<template>
<div class="app">
<nav>
<button @click="preloadDashboard">预加载仪表板</button>
<button @click="preloadUserProfile">预加载用户资料</button>
</nav>
<router-view v-slot="{ Component }">
<template v-if="Component">
<Suspense @resolve="onComponentLoaded">
<component :is="Component" />
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</template>
</router-view>
</div>
</template>
<script setup lang="ts">
import { onMounted, defineAsyncComponent } from 'vue';
import { useRouter } from 'vue-router';
import { componentRegistry, componentLoader } from '@/utils/componentLoader';
// 注册组件
componentRegistry.registerComponent({
name: 'UserDashboard',
path: 'dashboard/UserDashboard.vue',
preload: true,
prefetch: true
});
componentRegistry.registerComponent({
name: 'UserProfile',
path: 'profile/UserProfile.vue',
preload: false,
prefetch: true
});
// 创建异步组件
const UserDashboard = defineAsyncComponent(() =>
componentLoader.loadComponent({
name: 'UserDashboard',
path: 'dashboard/UserDashboard.vue'
})
);
const UserProfile = defineAsyncComponent(() =>
componentLoader.loadComponent({
name: 'UserProfile',
path: 'profile/UserProfile.vue'
})
);
const router = useRouter();
// 预加载函数
const preloadDashboard = () => {
componentLoader.preloadComponent({
name: 'UserDashboard',
path: 'dashboard/UserDashboard.vue'
});
};
const preloadUserProfile = () => {
componentLoader.preloadComponent({
name: 'UserProfile',
path: 'profile/UserProfile.vue'
});
};
const onComponentLoaded = () => {
console.log('组件加载完成');
};
// 初始化预加载策略
onMounted(() => {
componentRegistry.setupIntelligentPreloading(router);
});
</script>
4.2 React 应用集成
import React, { useRef, useEffect } from 'react';
import { withAsyncComponent, useVisiblePreload } from '@/utils/asyncComponents';
// 异步组件
const AsyncDashboard = withAsyncComponent({
name: 'UserDashboard',
path: 'dashboard/UserDashboard',
preload: true
});
const AsyncProfile = withAsyncComponent({
name: 'UserProfile',
path: 'profile/UserProfile',
preload: false
});
// 主应用组件
function App() {
const dashboardRef = useRef<HTMLDivElement>(null);
// 可视区域预加载
useVisiblePreload(dashboardRef, 'UserDashboard');
// 路由级别的预加载
useEffect(() => {
// 预加载可能访问的页面组件
const preloadPotentialComponents = () => {
componentRegistry.preloadComponentWithDependencies('UserProfile');
componentRegistry.preloadComponentWithDependencies('Settings');
};
// 用户空闲时预加载
if ('requestIdleCallback' in window) {
requestIdleCallback(preloadPotentialComponents);
} else {
setTimeout(preloadPotentialComponents, 5000);
}
}, []);
return (
<div className="app">
<nav>
<button onClick={() => componentLoader.preloadComponent({
name: 'UserProfile',
path: 'profile/UserProfile'
})}>
预加载用户资料
</button>
</nav>
<div ref={dashboardRef}>
<AsyncDashboard userId="123" />
</div>
</div>
);
}
export default App;
5. 性能监控和优化
5.1 性能监控系统
class ComponentPerformanceMonitor {
private metrics: Map<string, ComponentMetric[]> = new Map();
private observer: PerformanceObserver;
constructor() {
// 监控资源加载性能
this.observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
this.recordResourceMetric(entry);
});
});
this.observer.observe({ entryTypes: ['resource'] });
}
// 记录组件加载指标
recordComponentLoad(
componentName: string,
startTime: number,
endTime: number
): void {
const duration = endTime - startTime;
const metric: ComponentMetric = {
componentName,
duration,
timestamp: Date.now(),
type: 'load'
};
if (!this.metrics.has(componentName)) {
this.metrics.set(componentName, []);
}
this.metrics.get(componentName)!.push(metric);
// 上报到监控系统
this.reportMetric(metric);
}
// 获取组件性能报告
getComponentReport(componentName: string): ComponentReport {
const metrics = this.metrics.get(componentName) || [];
return {
componentName,
loadCount: metrics.length,
averageLoadTime: this.calculateAverage(metrics.map(m => m.duration)),
p95LoadTime: this.calculatePercentile(metrics.map(m => m.duration), 95),
failures: metrics.filter(m => m.type === 'error').length
};
}
private calculateAverage(numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
}
private calculatePercentile(numbers: number[], percentile: number): number {
const sorted = numbers.sort((a, b) => a - b);
const index = Math.ceil((percentile / 100) * sorted.length) - 1;
return sorted[index];
}
private recordResourceMetric(entry: PerformanceEntry): void {
// 记录资源加载指标
}
private reportMetric(metric: ComponentMetric): void {
// 上报到 APM 系统
if (navigator.sendBeacon) {
const data = JSON.stringify(metric);
navigator.sendBeacon('/api/performance', data);
}
}
}
interface ComponentMetric {
componentName: string;
duration: number;
timestamp: number;
type: string;
}
interface ComponentReport {
componentName: string;
loadCount: number;
averageLoadTime: number;
p95LoadTime: number;
failures: number;
}
这种组件级异步加载与预加载策略提供了:
- 按需加载:只在需要时加载组件
- 智能预加载:基于用户行为和路由预测
- 性能监控:详细的加载性能指标
- 错误处理:完善的错误处理和重试机制
- 缓存优化:智能缓存和淘汰策略
- 可视区域加载:基于 Intersection Observer 的优化
这些策略可以显著提升大型应用的加载性能和用户体验。
挣钱养家

浙公网安备 33010602011771号