关于 lazy-loading
lazy-loading 是一项开发技能,指导按需加载数据和页面内容。它提供基于交互、滚动、路由和分页的模式,以减少初始加载中的无用工作。
主要功能
- 01在用户交互时加载数据
- 02滚动到区域时再加载
- 03按路由延迟加载页面代码
- 04使用分页或无限滚动
- 05识别过早加载的警示信号
适用场景
- 01加快包含多类数据的仪表盘
- 02避免请求隐藏标签页的数据
- 03逐步显示大型列表
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ yanko-belov/ code-craft --skill lazy-loading安装后在 Claude Code 中输入/lazy-loading确认已加载。
详细介绍
延迟加载
概述
需要时再加载数据,而不是提前加载。不要获取可能用不到的数据。
提前加载会浪费带宽、内存和时间。按需加载可以提升性能和用户体验。
适用场景
- 仪表板一次性加载所有数据
- 页面获取隐藏标签页的数据
- 加载“以防万一”可能用到的数据
- 初始加载缓慢
铁律
NEVER load data until it's actually needed.
没有例外:
- 不能因为“可能会用到”
- 不能因为“并行更快”
- 不能因为“加载全部数据更简单”
- 不能因为“数据不算多”
检测:急切加载过多的迹象
如果你提前加载所有内容,请停止:
// ❌ VIOLATION: Load everything upfront
async function loadDashboard(userId: string) {
const [
profile,
preferences,
notifications, // User might not check
recentActivity, // Collapsed by default
analytics, // Expensive, rarely viewed
recommendations, // Below the fold
fullHistory // Paginated anyway
] = await Promise.all([
fetchProfile(userId),
fetchPreferences(userId),
fetchNotifications(userId),
fetchRecentActivity(userId),
fetchAnalytics(userId), // Takes 2 seconds!
fetchRecommendations(userId),
fetchFullHistory(userId) // 10MB of data!
]);
return { profile, preferences, notifications, ... };
}
问题:
- 最慢的数据获取会阻塞所有内容
- 未使用的数据会浪费资源
- 感知性能较差
正确模式:按需加载
// ✅ CORRECT: Load critical data first, rest on demand
// Initial load - only what's immediately visible
async function loadDashboard(userId: string) {
const [profile, preferences] = await Promise.all([
fetchProfile(userId),
fetchPreferences(userId)
]);
return { profile, preferences };
}
// React component with lazy loading
function Dashboard({ userId }) {
// Critical data loaded immediately
const { profile, preferences } = useInitialData(userId);
// Notifications: load when header mounts
const notifications = useLazyQuery(
() => fetchNotifications(userId),
{ loadOn: 'mount' }
);
// Analytics: load when tab is selected
const [analyticsTab, setAnalyticsTab] = useState(false);
const analytics = useLazyQuery(
() => fetchAnalytics(userId),
{ loadOn: analyticsTab }
);
// History: load when scrolled into view
const historyRef = useRef();
const history = useLazyQuery(
() => fetchHistory(userId),
{ loadOn: useIntersectionObserver(historyRef) }
);
return (
<div>
<Header profile={profile} notifications={notifications} />
<Tabs>
<Tab label="Overview">...</Tab>
<Tab label="Analytics" onSelect={() => setAnalyticsTab(true)}>
{analytics.loading ? <Skeleton /> : <AnalyticsChart data={analytics.data} />}
</Tab>
</Tabs>
<div ref={historyRef}>
{history.data && <HistoryList items={history.data} />}
</div>
</div>
);
}
延迟加载技巧
1. 交互时加载
// Load when user clicks tab
const [showDetails, setShowDetails] = useState(false);
const details = useQuery(fetchDetails, { enabled: showDetails });
<Tab onClick={() => setShowDetails(true)}>
{details.data ?? <Skeleton />}
</Tab>
2. 滚动时加载(Intersection Observer)
function LazySection({ loadFn, children }) {
const ref = useRef();
const [loaded, setLoaded] = useState(false);
const data = useQuery(loadFn, { enabled: loaded });
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => entry.isIntersecting && setLoaded(true)
);
observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return <div ref={ref}>{data.data ? children(data.data) : <Skeleton />}</div>;
}
3. 路由切换时加载
// Next.js / React Router
const AnalyticsPage = lazy(() => import('./AnalyticsPage'));
<Route path="/analytics" element={
<Suspense fallback={<Loading />}>
<AnalyticsPage />
</Suspense>
} />
4. 分页 / 无限滚动
function OrderList() {
const [page, setPage] = useState(1);
const { data, hasMore } = useOrders({ page, limit: 20 });
return (
<>
{data.map(order => <OrderRow key={order.id} order={order} />)}
{hasMore && <button onClick={() => setPage(p => p + 1)}>Load More</button>}
</>
);
}
抵御压力的原则
1. “可能会用到”
压力:“现在就加载,以防用户需要”
回应:“可能”意味着大概率用不到。等用户确实需要时再加载。
**行动:**按需加载,不要凭猜测加载。
2. “并行更快”
压力:“并行加载所有内容比顺序加载更快”
**回应:**并行加载不需要的数据,比不加载更慢。
**行动:**并行加载需要的数据。可能用到的数据则延迟加载。
3. “加载全部内容更简单”
压力:“一个获取函数更简单”
**回应:**速度慢、浪费资源的简单代码并不简单。
**行动:**结构化的延迟加载易于维护,性能也更好。
危险信号——停止并重新考虑
- 页面加载时使用
Promise.all获取 5 项或更多数据 - 获取折叠或隐藏区域的数据
- 一次性加载完整列表,而不是分页
- 页面初始加载缓慢
- “正在加载……”持续超过 1-2 秒
以上任何情况都意味着:实施延迟加载。
快速参考
| 急切加载(通常不好) | 延迟加载(通常更好) |
|---|---|
| 挂载时全部加载 | 优先加载可见内容 |
| 获取隐藏标签页的数据 | 选中标签页时再获取 |
| 一次性加载完整列表 | 分页 / 无限滚动 |
| 首屏以下的内容 | Intersection observer |
常见借口(都不成立)
| 借口 | 事实 |
|---|---|
| “可能会用到” | 真正需要时再加载。 |
| “并行更快” | 不加载才是最快的。 |
| “更简单” | 慢并不简单。 |
| “数据不算多” | 积少成多。带宽有成本。 |
| “提前准备好体验更好” | 加载缓慢会让体验更差。 |
总结
加载可见内容。其余内容延后加载。大型列表使用分页。
初始加载 = 仅加载关键数据。其他内容都在交互、滚动或导航时加载。不要让用户等待他们看不到的数据。
评论
支持 Markdown