在大型应用中,合理的按需加载策略可以显著提升应用性能。本节将详细介绍如何在使用 Tailwind CSS 的项目中实现高效的按需加载。

样式按需加载

基础配置

  1. // tailwind.config.js
  2. module.exports = {
  3. // 配置 JIT 模式
  4. mode: 'jit',
  5. // 精确配置需要处理的文件
  6. content: [
  7. './src/pages/**/*.{js,jsx,ts,tsx}',
  8. './src/components/**/*.{js,jsx,ts,tsx}',
  9. './src/features/**/*.{js,jsx,ts,tsx}',
  10. ],
  11. // 禁用未使用的功能
  12. corePlugins: {
  13. float: false,
  14. clear: false,
  15. objectFit: false,
  16. },
  17. }

动态样式加载

  1. // utils/styleLoader.ts
  2. export const loadStyles = async (stylePath: string) => {
  3. try {
  4. const styleModule = await import(
  5. /* webpackChunkName: "styles/[request]" */
  6. `../styles/${stylePath}.css`
  7. );
  8. return styleModule.default;
  9. } catch (error) {
  10. console.error('Failed to load styles:', error);
  11. return null;
  12. }
  13. };
  14. // 使用示例
  15. const Component = () => {
  16. useEffect(() => {
  17. loadStyles('features/dashboard');
  18. }, []);
  19. return <div>Dashboard Component</div>;
  20. };

组件按需加载

路由级别加载

  1. // routes/index.tsx
  2. import { lazy, Suspense } from 'react';
  3. const Dashboard = lazy(() => import('./pages/Dashboard'));
  4. const Settings = lazy(() => import('./pages/Settings'));
  5. const Routes = () => (
  6. <Suspense fallback={<LoadingSpinner />}>
  7. <Switch>
  8. <Route path="/dashboard">
  9. <Dashboard />
  10. </Route>
  11. <Route path="/settings">
  12. <Settings />
  13. </Route>
  14. </Switch>
  15. </Suspense>
  16. );

组件级别加载

  1. // components/LazyComponent.tsx
  2. import { lazy, Suspense } from 'react';
  3. const DataTable = lazy(() => {
  4. // 同时加载组件和样式
  5. return Promise.all([
  6. import('./DataTable'),
  7. import('./DataTable.css')
  8. ]).then(([componentModule]) => componentModule);
  9. });
  10. const LazyDataTable = (props) => (
  11. <Suspense
  12. fallback={
  13. <div className="animate-pulse bg-gray-200 h-64 rounded-lg" />
  14. }
  15. >
  16. <DataTable {...props} />
  17. </Suspense>
  18. );

主题按需加载

主题配置

  1. // styles/themes/index.ts
  2. export const themes = {
  3. light: () => import('./light.css'),
  4. dark: () => import('./dark.css'),
  5. custom: () => import('./custom.css'),
  6. };
  7. // hooks/useTheme.ts
  8. import { useState, useEffect } from 'react';
  9. import { themes } from '../styles/themes';
  10. export const useTheme = (initialTheme = 'light') => {
  11. const [theme, setTheme] = useState(initialTheme);
  12. useEffect(() => {
  13. const loadTheme = async () => {
  14. const themeModule = await themes[theme]();
  15. // 应用主题样式
  16. };
  17. loadTheme();
  18. }, [theme]);
  19. return { theme, setTheme };
  20. };

图标按需加载

图标组件

  1. // components/Icon/index.tsx
  2. import { lazy, Suspense } from 'react';
  3. const iconComponents = {
  4. home: () => import('./icons/Home'),
  5. settings: () => import('./icons/Settings'),
  6. user: () => import('./icons/User'),
  7. };
  8. interface IconProps {
  9. name: keyof typeof iconComponents;
  10. size?: number;
  11. className?: string;
  12. }
  13. const Icon: React.FC<IconProps> = ({ name, ...props }) => {
  14. const LazyIcon = lazy(iconComponents[name]);
  15. return (
  16. <Suspense fallback={<div className="w-6 h-6 bg-gray-200 rounded" />}>
  17. <LazyIcon {...props} />
  18. </Suspense>
  19. );
  20. };
  21. // 使用示例
  22. const Header = () => (
  23. <header>
  24. <Icon name="home" className="w-6 h-6 text-gray-600" />
  25. </header>
  26. );

性能优化

预加载策略

  1. // utils/preload.ts
  2. const preloadComponent = (componentPath: string) => {
  3. const component = import(
  4. /* webpackPrefetch: true */
  5. /* webpackChunkName: "[request]" */
  6. `../components/${componentPath}`
  7. );
  8. return component;
  9. };
  10. // 路由配置中使用
  11. const routes = [
  12. {
  13. path: '/dashboard',
  14. component: lazy(() => preloadComponent('Dashboard')),
  15. preload: () => preloadComponent('Dashboard'),
  16. },
  17. ];
  18. // 导航守卫中使用
  19. const RouterGuard = ({ children }) => {
  20. const location = useLocation();
  21. useEffect(() => {
  22. const route = routes.find(r => r.path === location.pathname);
  23. if (route?.preload) {
  24. route.preload();
  25. }
  26. }, [location]);
  27. return children;
  28. };

加载优化

  1. // hooks/useAsyncComponent.ts
  2. import { useState, useEffect } from 'react';
  3. export const useAsyncComponent = (
  4. loader: () => Promise<any>,
  5. placeholder: React.ReactNode
  6. ) => {
  7. const [Component, setComponent] = useState<React.ComponentType | null>(null);
  8. const [error, setError] = useState<Error | null>(null);
  9. useEffect(() => {
  10. let mounted = true;
  11. loader()
  12. .then(module => {
  13. if (mounted) {
  14. setComponent(() => module.default);
  15. }
  16. })
  17. .catch(err => {
  18. if (mounted) {
  19. setError(err);
  20. console.error('Failed to load component:', err);
  21. }
  22. });
  23. return () => {
  24. mounted = false;
  25. };
  26. }, [loader]);
  27. if (error) {
  28. return <div>Error loading component</div>;
  29. }
  30. return Component ? <Component /> : placeholder;
  31. };
  32. // 使用示例
  33. const AsyncFeature = () => {
  34. return useAsyncComponent(
  35. () => import('./Feature'),
  36. <div className="animate-pulse bg-gray-200 h-32" />
  37. );
  38. };

缓存策略

模块缓存

  1. // utils/moduleCache.ts
  2. const moduleCache = new Map<string, any>();
  3. export const loadCachedModule = async (key: string, loader: () => Promise<any>) => {
  4. if (moduleCache.has(key)) {
  5. return moduleCache.get(key);
  6. }
  7. const module = await loader();
  8. moduleCache.set(key, module);
  9. return module;
  10. };
  11. // 使用示例
  12. const loadComponent = async (name: string) => {
  13. return loadCachedModule(
  14. `component:${name}`,
  15. () => import(`../components/${name}`)
  16. );
  17. };

最佳实践

  1. 加载策略

    • 配置精确的内容扫描
    • 实现合理的预加载
    • 使用适当的加载指示器
  2. 性能优化

    • 实施代码分割
    • 优化加载顺序
    • 合理使用缓存
  3. 开发建议

    • 模块化样式组织
    • 组件粒度控制
    • 合理使用占位符
  4. 监控分析

    • 跟踪加载性能
    • 分析资源使用
    • 优化加载策略