基于智能预加载的组件动态加载架构
核心加载引擎设计
1.1 动态组件管理接口
// 组件状态定义
enum LoadState {
Idle = 'idle',
Pending = 'pending',
Success = 'success',
Failed = 'failed'
}
// 加载配置结构
interface LoadConfig {
id: string;
modulePath: string;
chunkName?: string;
shouldPreload?: boolean;
shouldPrefetch?: boolean;
fallbackComponent?: string;
errorComponent?: string;
timeoutMs?: number;
maxRetries?: number;
}
1.2 高级异步加载服务
class DynamicLoader {
private cache = new Map<string, { instance: any; timestamp: number }>();
private pendingRequests = new Map<string, Promise<any>>();
private preloadQueue = new Set<string>();
private observer: IntersectionObserver;
private maxSize = 50;
private currentSize = 0;
constructor() {
this.observer = new IntersectionObserver(
this.handleVisibility.bind(this),
{
rootMargin: '150px',
threshold: 0.05
}
);
}
async load(id: string, config: LoadConfig): Promise<any> {
// 缓存命中检查
if (this.cache.has(id)) {
const item = this.cache.get(id)!;
item.timestamp = Date.now();
return item.instance;
}
// 防止重复请求
if (this.pendingRequests.has(id)) {
return this.pendingRequests.get(id)!;
}
const requestPromise = this.attemptLoad(config)
.then(result => {
this.addToCache(id, result);
this.pendingRequests.delete(id);
return result;
})
.catch(err => {
this.pendingRequests.delete(id);
throw err;
});
this.pendingRequests.set(id, requestPromise);
return requestPromise;
}
private async attemptLoad(config: LoadConfig): Promise<any> {
const { modulePath, maxRetries = 3, timeoutMs = 25000 } = config;
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
const module = await import(
/* webpackChunkName: "[request]" */
/* webpackMode: "lazy" */
`@/components/${modulePath}`
);
clearTimeout(timeoutId);
return module.default || module;
} catch (error) {
lastError = error as Error;
if (attempt === maxRetries) break;
await this.wait(Math.pow(2, attempt) * 800);
}
}
throw lastError;
}
private addToCache(id: string, instance: any): void {
const sizeEstimate = JSON.stringify(instance).length;
if (this.currentSize + sizeEstimate > this.maxSize) {
this.evictOldest();
}
this.cache.set(id, {
instance,
timestamp: Date.now()
});
this.currentSize += sizeEstimate;
}
private evictOldest(): void {
const entries = Array.from(this.cache.entries());
const sorted = entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
const toRemove = sorted.slice(0, Math.floor(sorted.length * 0.15));
toRemove.forEach(([id]) => {
const item = this.cache.get(id)!;
this.currentSize -= JSON.stringify(item.instance).length;
this.cache.delete(id);
});
}
preload(id: string, config: LoadConfig): void {
if (this.cache.has(id) || this.pendingRequests.has(id)) return;
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
this.load(id, config).catch(() => {});
});
} else {
setTimeout(() => {
this.load(id, config).catch(() => {});
}, 2000);
}
}
observeElement(element: HTMLElement, config: LoadConfig): void {
this.observer.observe(element);
this.preloadQueue.add(config.id);
}
private handleVisibility(entries: IntersectionObserverEntry[]): void {
entries.forEach(entry => {
if (entry.isIntersecting) {
const target = entry.target as HTMLElement;
this.observer.unobserve(target);
// 触发预加载逻辑
this.preloadQueue.forEach(id => {
// 实际实现需关联元素与组件映射
});
}
});
}
private wait(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
clearCache(): void {
this.cache.clear();
this.pendingRequests.clear();
this.preloadQueue.clear();
this.currentSize = 0;
}
}
export const loader = new DynamicLoader();
框架集成方案
2.1 Vue 异步组件封装
export function createLazyComponent(config: LoadConfig) {
const { id, modulePath, fallbackComponent = 'LoadingSpinner', errorComponent = 'ErrorFallback' } = config;
return defineAsyncComponent({
loader: () => loader.load(id, config),
loadingComponent: defineComponent({
setup() {
return () => h(resolveComponent(fallbackComponent));
}
}),
errorComponent: defineComponent({
props: { error: Object },
setup(props) {
const retry = () => {
// 触发重试逻辑
};
return () => h(resolveComponent(errorComponent), { error: props.error, onRetry: retry });
}
}),
delay: 300,
timeout: 30000,
onError: (err, retry, fail, attempts) => {
if (err.message.includes('timeout') && attempts < 3) {
retry();
} else {
fail();
}
}
});
}
2.2 React 预加载工具
export function usePreload(id: string, config: LoadConfig) {
const [isLoaded, setIsLoaded] = useState(false);
const triggerPreload = useCallback(() => {
if (isLoaded) return;
loader.load(id, config).then(() => setIsLoaded(true)).catch(() => {});
}, [id, isLoaded]);
useEffect(() => {
if (!config.shouldPreload) return;
const idleCallback = () => {
if ('requestIdleCallback' in window) {
requestIdleCallback(triggerPreload);
} else {
setTimeout(triggerPreload, 3000);
}
};
idleCallback();
}, [config, triggerPreload]);
return { triggerPreload, isLoaded };
}
export function useVisibilityPreload(ref: React.RefObject<HTMLElement>, id: string, config: LoadConfig) {
useEffect(() => {
if (!ref.current) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
loader.preload(id, config);
observer.disconnect();
}
},
{ rootMargin: '200px', threshold: 0.1 }
);
observer.observe(ref.current);
return () => observer.disconnect();
}, [ref, id, config]);
}
实际应用示例
3.1 Vue 项目集成
<template>
<div class="container">
<button @click="preloadDashboard">预加载仪表盘</button>
<Suspense :timeout="5000">
<component :is="currentView" />
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { createLazyComponent } from '@/utils/loader';
const currentView = ref(null);
const Dashboard = createLazyComponent({
id: 'dashboard',
modulePath: 'views/Dashboard.vue',
shouldPreload: true
});
const Profile = createLazyComponent({
id: 'profile',
modulePath: 'views/Profile.vue',
shouldPrefetch: true
});
const preloadDashboard = () => {
loader.preload('dashboard', {
id: 'dashboard',
modulePath: 'views/Dashboard.vue',
shouldPreload: true
});
};
onMounted(() => {
// 自动化预加载策略
document.addEventListener('mousemove', () => {
loader.preload('profile', {
id: 'profile',
modulePath: 'views/Profile.vue'
});
});
});
</script>
3.2 React 项目集成
import React, { useRef } from 'react';
import { usePreload, useVisibilityPreload } from '@/utils/preload';
const AsyncDashboard = React.lazy(() =>
loader.load('dashboard', {
id: 'dashboard',
modulePath: 'views/Dashboard.tsx',
shouldPreload: true
}).then(m => ({ default: m }))
);
function App() {
const dashboardRef = useRef<HTMLDivElement>(null);
useVisibilityPreload(dashboardRef, 'dashboard', {
id: 'dashboard',
modulePath: 'views/Dashboard.tsx',
shouldPreload: true
});
usePreload('profile', {
id: 'profile',
modulePath: 'views/Profile.tsx',
shouldPreload: false
});
return (
<div className="app">
<button onClick={() => loader.preload('profile', { id: 'profile', modulePath: 'views/Profile.tsx' })}>
预加载资料页
</button>
<div ref={dashboardRef}>
<React.Suspense fallback={<div>加载中...</div>}>
<AsyncDashboard />
</React.Suspense>
</div>
</div>
);
}
export default App;
性能监控体系
class PerformanceTracker {
private metrics = new Map<string, Array<{ duration: number; timestamp: number }>>();
constructor() {
const observer = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
if (entry.entryType === 'resource') {
this.recordResource(entry.name, entry.duration);
}
});
});
observer.observe({ entryTypes: ['resource'] });
}
recordResource(url: string, duration: number): void {
const key = url.split('/').pop() || url;
const metric = { duration, timestamp: Date.now() };
if (!this.metrics.has(key)) {
this.metrics.set(key, []);
}
this.metrics.get(key)!.push(metric);
}
getReport(componentName: string): {
avgTime: number;
p95: number;
totalAttempts: number;
failureCount: number;
} {
const data = this.metrics.get(componentName) || [];
const durations = data.map(d => d.duration);
const avg = durations.reduce((a, b) => a + b, 0) / durations.length;
const sorted = [...durations].sort((a, b) => a - b);
const p95Index = Math.ceil(0.95 * sorted.length) - 1;
const p95 = sorted[p95Index];
return {
avgTime: avg,
p95: p95,
totalAttempts: data.length,
failureCount: data.filter(d => d.duration > 5000).length
};
}
}
export const tracker = new PerformanceTracker();
该架构通过以下机制提升系统表现:
- 按需分发:仅在需要时触发模块加载
- 预测性预取:结合用户交互和视口监测提前准备资源
- 自适应缓存:基于访问频率自动淘汰低频组件
- 容错恢复:具备指数退避的重试逻辑
- 运行时监控:实时采集加载指标用于持续优化
- 多框架兼容:支持 Vue 3 与 React 18 无缝集成
整体方案显著降低首屏加载延迟,改善长尾性能体验。