mcpField
L
lazy-loading

lazy-loading

github.com/yanko-belov开发1372 次安装40 周前更新
代码仓库

关于 lazy-loading

lazy-loading 是一项开发技能,指导按需加载数据和页面内容。它提供基于交互、滚动、路由和分页的模式,以减少初始加载中的无用工作。

主要功能

  1. 01在用户交互时加载数据
  2. 02滚动到区域时再加载
  3. 03按路由延迟加载页面代码
  4. 04使用分页或无限滚动
  5. 05识别过早加载的警示信号

适用场景

  1. 01加快包含多类数据的仪表盘
  2. 02避免请求隐藏标签页的数据
  3. 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