设计思路
- 服务器端渲染阶段
- 预加载分析:在服务器渲染过程中,分析页面初始渲染所必需的组件。对于非关键组件,标记为可延迟加载。可以通过对业务逻辑和页面结构的理解,结合路由信息,确定哪些组件可以在初始渲染后再加载。例如,对于一个电商产品详情页,产品图片展示、基本信息等组件是关键组件,而相关推荐、用户评论等组件可考虑延迟加载。
- 加载顺序控制:确保关键组件优先加载和渲染。在服务器端生成HTML时,先构建关键组件的DOM结构,然后为延迟加载组件生成占位符。占位符可以包含一些元数据,如组件名称、加载路径等,以便客户端在合适的时候进行加载。
- 客户端水合及交互阶段
- 水合优化:客户端在接收到服务器渲染的HTML后,进行水合过程。在这个过程中,先处理关键组件的水合,确保页面基本结构和交互的稳定性。对于延迟加载组件,等待合适的时机进行加载和水合。例如,可以在页面关键组件水合完成且浏览器空闲时,触发延迟加载组件的加载。
- 交互驱动加载:结合客户端交互来触发延迟加载。比如,当用户滚动到页面某个位置,即将显示延迟加载组件时,才进行加载。这样可以确保只有在真正需要时才加载组件,提高性能。同时,在加载延迟组件时,可以采用渐进式加载,先加载组件的基本结构,再逐步填充数据,减少用户等待时间。
关键实现点
- Qwik组件封装
- 延迟加载组件封装:创建一个通用的延迟加载组件包装器。例如,定义一个
LazyLoadComponent
,接受组件路径、加载时机等参数。在组件内部,使用import()
动态导入实际的组件。例如:
import { component$, useTask$ } from '@builder.io/qwik';
export const LazyLoadComponent = component$(({ componentPath }) => {
const [Component, setComponent] = useState$(null);
useTask$(({ onMount }) => {
onMount(async () => {
const module = await import(componentPath);
setComponent(module.default);
});
});
return Component ? <Component /> : null;
});
- 服务器端渲染集成
- 标记延迟组件:在服务器端渲染的页面构建逻辑中,识别并标记延迟加载组件。可以通过自定义属性或特定的组件包装方式来实现。例如,在构建页面的JSX中,将延迟加载组件包装在
LazyLoadComponent
中,并传递组件路径等信息。
- 生成占位符:服务器端为延迟加载组件生成占位符。占位符可以是一个简单的HTML元素,带有特定的标识。例如:
<div data-lazy-component="related - products" data - component - path="./components/RelatedProducts.jsx"></div>
- 客户端水合与加载逻辑
- 水合钩子:在Qwik的水合钩子函数中,处理延迟加载组件的加载。在
onHydrationEnd
钩子中,根据占位符的元数据,触发延迟加载组件的加载。例如:
import { component$, onHydrationEnd } from '@builder.io/qwik';
export const MyPage = component$(() => {
onHydrationEnd(() => {
const lazyComponents = document.querySelectorAll('[data - lazy - component]');
lazyComponents.forEach((component) => {
const componentPath = component.dataset.componentPath;
// 触发延迟加载逻辑
});
});
return (
<div>
{/* 页面内容 */}
</div>
);
});
- **滚动及交互监听**:为实现交互驱动加载,在客户端添加滚动和其他交互事件的监听。例如,使用`IntersectionObserver`来监听延迟加载组件占位符的可见性,当占位符进入视口时,触发组件加载。
import { component$ } from '@builder.io/qwik';
export const MyPage = component$(() => {
useEffect$(() => {
const lazyComponents = document.querySelectorAll('[data - lazy - component]');
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const componentPath = entry.target.dataset.componentPath;
// 触发延迟加载逻辑
}
});
});
lazyComponents.forEach((component) => {
observer.observe(component);
});
return () => {
observer.disconnect();
};
});
return (
<div>
{/* 页面内容 */}
</div>
);
});