第十一篇:【React 与微前端】大型应用的模块化与微服务化架构

大型前端项目不再混乱!微前端架构让团队协作如丝般顺滑

各位 React 开发者,当你的项目从小型应用发展为大型企业级系统时,是否遇到过这些挑战:

  • 多团队并行开发时代码冲突频繁?
  • 庞大的代码库导致构建缓慢、加载性能下降?
  • 不同业务模块难以实现技术栈独立和版本隔离?
  • 大型应用难以实现增量升级和灰度发布?

今天,我们将深入探讨微前端架构,这一解决大型前端应用复杂性的革命性方案。从理论到实践,全方位掌握微前端技术!

1. 什么是微前端?为什么需要它?

微前端是将前端应用分解为更小、更易管理的部分的架构风格,允许多个团队并行开发,独立部署各自的业务模块:

scss 复制代码
┌─────────────────────────────────────────────────────────┐
│                    Container Application                  │
│                                                          │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐  │
│  │   Micro      │   │   Micro      │   │   Micro      │  │
│  │  Frontend 1  │   │  Frontend 2  │   │  Frontend 3  │  │
│  │  (Team A)    │   │  (Team B)    │   │  (Team C)    │  │
│  │  React 18    │   │  Vue 3       │   │  Angular     │  │
│  └──────────────┘   └──────────────┘   └──────────────┘  │
│                                                          │
│                  Shared Components & Services             │
└─────────────────────────────────────────────────────────┘

微前端的核心优势:

  1. 团队自治:不同团队可以独立开发、测试和部署
  2. 技术栈灵活:各微应用可以使用不同的框架和库
  3. 渐进式升级:系统可以按模块逐步更新,降低风险
  4. 代码隔离:避免全局状态和依赖冲突
  5. 独立部署:CI/CD 流程简化,可以实现频繁发布

让我们从最基础的实现开始,逐步构建完整的微前端架构。

2. 微前端的实现方式对比

css 复制代码
┌───────────────────────────────────────────────────────────────────┐
│ 微前端实现方式比较                                                   │
├────────────────┬────────────────────┬────────────────┬────────────┤
│                │        优点         │      缺点       │  适用场景   │
├────────────────┼────────────────────┼────────────────┼────────────┤
│ iframe         │ 完全隔离            │ SEO不友好       │ 内部系统   │
│                │ 简单易实现          │ 用户体验差      │            │
├────────────────┼────────────────────┼────────────────┼────────────┤
│ Web Components │ 浏览器原生支持      │ 学习曲线陡峭    │ 多技术栈   │
│                │ 良好的封装性        │ 生态不完善      │ 组件共享   │
├────────────────┼────────────────────┼────────────────┼────────────┤
│ Module         │ 运行时共享代码      │ 配置复杂        │ 大型系统   │
│ Federation     │ 独立构建与部署      │ 热更新支持有限  │ React生态  │
├────────────────┼────────────────────┼────────────────┼────────────┤
│ 客户端路由集成  │ 实现简单           │ 需整体部署      │ 单技术栈   │
│                │ 用户体验好          │ 有限的隔离性    │ 中小项目   │
└────────────────┴────────────────────┴────────────────┴────────────┘

Module Federation:React 微前端的理想选择

Webpack 5 的 Module Federation 是目前 React 生态中最成熟的微前端解决方案:

js 复制代码
// 主应用 webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  // 其他配置...
  plugins: [
    new ModuleFederationPlugin({
      name: "container",
      remotes: {
        dashboard: "dashboard@http://localhost:3001/remoteEntry.js",
        profile: "profile@http://localhost:3002/remoteEntry.js",
        orders: "orders@http://localhost:3003/remoteEntry.js",
      },
      shared: {
        react: { singleton: true, requiredVersion: "^18.0.0" },
        "react-dom": { singleton: true, requiredVersion: "^18.0.0" },
        "react-router-dom": { singleton: true, requiredVersion: "^6.0.0" },
      },
    }),
  ],
};
js 复制代码
// dashboard微应用 webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  // 其他配置...
  plugins: [
    new ModuleFederationPlugin({
      name: "dashboard",
      filename: "remoteEntry.js",
      exposes: {
        "./DashboardApp": "./src/App",
        "./SalesChart": "./src/components/SalesChart",
        "./InventoryWidget": "./src/components/InventoryWidget",
      },
      shared: {
        react: { singleton: true, requiredVersion: "^18.0.0" },
        "react-dom": { singleton: true, requiredVersion: "^18.0.0" },
        "react-router-dom": { singleton: true, requiredVersion: "^6.0.0" },
      },
    }),
  ],
};

3. 构建微前端架构的通用解决方案

让我们构建一个完整的企业级微前端架构样板:

项目结构

csharp 复制代码
micro-frontend-demo/
├── container/                # 主应用壳
│   ├── public/
│   ├── src/
│   │   ├── components/
│   │   ├── types/
│   │   ├── bootstrap.tsx     # 应用初始化
│   │   ├── App.tsx           # 主应用
│   │   ├── index.tsx         # 入口文件
│   │   └── router.tsx        # 路由配置
│   ├── package.json
│   └── webpack.config.js     # 主应用Webpack配置
│
├── dashboard/                # 仪表盘微应用
│   ├── src/
│   │   ├── bootstrap.tsx     # 微应用初始化
│   │   ├── App.tsx           # 微应用根组件
│   │   └── ...
│   ├── package.json
│   └── webpack.config.js     # 微应用Webpack配置
│
├── profile/                  # 用户资料微应用
│   ├── src/
│   │   ├── bootstrap.tsx
│   │   ├── App.tsx
│   │   └── ...
│   ├── package.json
│   └── webpack.config.js
│
├── orders/                   # 订单管理微应用
│   ├── src/
│   │   ├── bootstrap.tsx
│   │   ├── App.tsx
│   │   └── ...
│   ├── package.json
│   └── webpack.config.js
│
├── shared/                   # 共享库
│   ├── src/
│   │   ├── components/       # 共享UI组件
│   │   ├── utils/            # 通用工具函数
│   │   ├── hooks/            # 共享Hooks
│   │   └── ...
│   ├── package.json
│   └── webpack.config.js
│
├── package.json              # 根项目配置
└── lerna.json                # Lerna配置

主应用实现

tsx 复制代码
// container/src/bootstrap.tsx
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";

const container = document.getElementById("root");
const root = createRoot(container!);
root.render(<App />);
tsx 复制代码
// container/src/App.tsx
import React, { Suspense, useState, useEffect } from "react";
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
import { Helmet } from "react-helmet";
import { ThemeProvider } from "./components/ThemeProvider";
import { ErrorBoundary } from "./components/ErrorBoundary";
import { Navigation } from "./components/Navigation";
import { Loading } from "./components/Loading";
import { AuthProvider, useAuth } from "./hooks/useAuth";

// 懒加载微应用
const DashboardApp = React.lazy(() => import("dashboard/DashboardApp"));
const ProfileApp = React.lazy(() => import("profile/ProfileApp"));
const OrdersApp = React.lazy(() => import("orders/OrdersApp"));

// 内部页面
import Login from "./pages/Login";
import NotFound from "./pages/NotFound";

// 系统主题
const theme = {
  colors: {
    primary: "#1976d2",
    secondary: "#dc004e",
    background: "#f5f5f5",
    text: "#333333",
  },
  spacing: {
    small: "8px",
    medium: "16px",
    large: "24px",
  },
  borderRadius: "4px",
};

export default function App() {
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    // 模拟系统初始化
    const timer = setTimeout(() => {
      setIsLoading(false);
    }, 1000);

    return () => clearTimeout(timer);
  }, []);

  if (isLoading) {
    return <Loading message="初始化系统..." />;
  }

  return (
    <ThemeProvider theme={theme}>
      <Helmet>
        <title>企业管理系统</title>
        <meta name="description" content="基于微前端架构的企业管理系统" />
      </Helmet>

      <AuthProvider>
        <BrowserRouter>
          <AppContent />
        </BrowserRouter>
      </AuthProvider>
    </ThemeProvider>
  );
}

function AppContent() {
  const { isAuthenticated } = useAuth();

  if (!isAuthenticated) {
    return <Login />;
  }

  return (
    <div className="app-container">
      <Navigation />

      <main className="main-content">
        <ErrorBoundary>
          <Suspense fallback={<Loading message="加载模块..." />}>
            <Routes>
              <Route path="/" element={<DashboardApp />} />
              <Route path="/dashboard/*" element={<DashboardApp />} />
              <Route path="/profile/*" element={<ProfileApp />} />
              <Route path="/orders/*" element={<OrdersApp />} />
              <Route path="*" element={<NotFound />} />
            </Routes>
          </Suspense>
        </ErrorBoundary>
      </main>
    </div>
  );
}

微应用实现

tsx 复制代码
// dashboard/src/bootstrap.tsx - 两种入口模式
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";

// 独立部署模式入口
const mount = (el) => {
  const root = createRoot(el);
  root.render(<App />);

  // 提供卸载方法
  return {
    unmount: () => {
      root.unmount();
    },
  };
};

// 检测是否为独立运行环境
if (process.env.NODE_ENV === "development") {
  const devRoot = document.getElementById("_dashboard-root");

  if (devRoot) {
    mount(devRoot);
  }
}

// 导出挂载函数供主应用使用
export { mount };
tsx 复制代码
// dashboard/src/App.tsx
import React from "react";
import { Routes, Route, useNavigate } from "react-router-dom";
import SalesChart from "./components/SalesChart";
import InventoryWidget from "./components/InventoryWidget";
import RecentOrders from "./components/RecentOrders";
import SalesPerformance from "./components/SalesPerformance";

const DashboardHome = () => (
  <div className="dashboard-container">
    <h2>仪表盘</h2>

    <div className="dashboard-grid">
      <div className="dashboard-card large">
        <h3>销售趋势</h3>
        <SalesChart />
      </div>

      <div className="dashboard-card">
        <h3>库存状态</h3>
        <InventoryWidget />
      </div>

      <div className="dashboard-card">
        <h3>最近订单</h3>
        <RecentOrders />
      </div>

      <div className="dashboard-card">
        <h3>销售业绩</h3>
        <SalesPerformance />
      </div>
    </div>
  </div>
);

const SalesAnalytics = () => (
  <div className="dashboard-container">
    <h2>销售分析</h2>
    {/* 销售分析内容 */}
  </div>
);

const InventoryAnalytics = () => (
  <div className="dashboard-container">
    <h2>库存分析</h2>
    {/* 库存分析内容 */}
  </div>
);

export default function App() {
  return (
    <Routes>
      <Route path="/" element={<DashboardHome />} />
      <Route path="/sales" element={<SalesAnalytics />} />
      <Route path="/inventory" element={<InventoryAnalytics />} />
    </Routes>
  );
}

// 导出单个组件供其他微应用使用
export { SalesChart, InventoryWidget };

4. 微前端通信与状态共享

微前端应用间的通信是关键挑战之一,以下是几种主要通信方式的实现:

集成事件总线

tsx 复制代码
// shared/src/EventBus.ts
type EventCallback = (data?: any) => void;

interface EventMap {
  [eventName: string]: EventCallback[];
}

class EventBus {
  private events: EventMap = {};

  // 订阅事件
  on(eventName: string, callback: EventCallback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }

    this.events[eventName].push(callback);

    // 返回取消订阅函数
    return () => {
      this.events[eventName] = this.events[eventName].filter(
        (cb) => cb !== callback
      );
    };
  }

  // 发布事件
  emit(eventName: string, data?: any) {
    if (this.events[eventName]) {
      this.events[eventName].forEach((callback) => {
        callback(data);
      });
    }
  }

  // 清除事件
  off(eventName: string, callback?: EventCallback) {
    if (!callback) {
      delete this.events[eventName];
    } else {
      this.events[eventName] = this.events[eventName].filter(
        (cb) => cb !== callback
      );
    }
  }
}

// 创建全局事件总线
const eventBus = new EventBus();
export default eventBus;
tsx 复制代码
// container/src/hooks/useEventBus.ts
import { useEffect } from "react";
import eventBus from "shared/EventBus";

export function useEventBus(eventName: string, callback: (data?: any) => void) {
  useEffect(() => {
    // 订阅事件
    const unsubscribe = eventBus.on(eventName, callback);

    // 组件卸载时取消订阅
    return unsubscribe;
  }, [eventName, callback]);

  // 返回发布事件的函数
  return (data?: any) => {
    eventBus.emit(eventName, data);
  };
}

使用自定义钩子共享状态

tsx 复制代码
// shared/src/hooks/useSharedState.ts
import React, { useState, useEffect, useContext, createContext } from "react";
import eventBus from "../EventBus";

// 创建共享状态上下文
const SharedStateContext = createContext<{
  getState: (key: string) => any;
  setState: (key: string, value: any) => void;
}>({
  getState: () => null,
  setState: () => {},
});

// 共享状态提供者
export function SharedStateProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  const [state, setState] = useState<Record<string, any>>({});

  const handleSetState = (key: string, value: any) => {
    setState((prev) => {
      const newState = { ...prev, [key]: value };

      // 发布状态更新事件
      eventBus.emit(`state:${key}:changed`, value);

      return newState;
    });
  };

  const handleGetState = (key: string) => {
    return state[key];
  };

  const contextValue = {
    getState: handleGetState,
    setState: handleSetState,
  };

  return (
    <SharedStateContext.Provider value={contextValue}>
      {children}
    </SharedStateContext.Provider>
  );
}

// 共享状态Hook
export function useSharedState<T>(
  key: string,
  initialValue?: T
): [T, (value: T) => void] {
  const { getState, setState } = useContext(SharedStateContext);
  const [localState, setLocalState] = useState<T>(
    () => getState(key) || initialValue
  );

  // 监听状态变化
  useEffect(() => {
    const handleStateChange = (newValue: T) => {
      setLocalState(newValue);
    };

    // 订阅状态变化
    const unsubscribe = eventBus.on(`state:${key}:changed`, handleStateChange);

    // 初始同步
    const currentValue = getState(key);
    if (currentValue !== undefined && currentValue !== localState) {
      setLocalState(currentValue);
    } else if (initialValue !== undefined && currentValue === undefined) {
      setState(key, initialValue);
    }

    return unsubscribe;
  }, [key, getState, setState, initialValue, localState]);

  // 更新状态函数
  const updateState = (value: T) => {
    setState(key, value);
    setLocalState(value);
  };

  return [localState as T, updateState];
}

实例:购物车状态共享

tsx 复制代码
// orders/src/components/AddToCart.tsx
import React from "react";
import { useSharedState } from "shared/hooks/useSharedState";

interface Product {
  id: string;
  name: string;
  price: number;
  image: string;
}

interface CartItem {
  productId: string;
  name: string;
  price: number;
  quantity: number;
  image: string;
}

interface AddToCartProps {
  product: Product;
}

export default function AddToCart({ product }: AddToCartProps) {
  const [cart, setCart] = useSharedState<CartItem[]>("shopping-cart", []);

  const addToCart = () => {
    setCart((currentCart) => {
      // 检查商品是否已在购物车
      const existingItemIndex = currentCart.findIndex(
        (item) => item.productId === product.id
      );

      if (existingItemIndex >= 0) {
        // 更新数量
        const updatedCart = [...currentCart];
        updatedCart[existingItemIndex].quantity += 1;
        return updatedCart;
      } else {
        // 添加新商品
        return [
          ...currentCart,
          {
            productId: product.id,
            name: product.name,
            price: product.price,
            quantity: 1,
            image: product.image,
          },
        ];
      }
    });
  };

  return (
    <button className="add-to-cart-button" onClick={addToCart}>
      加入购物车
    </button>
  );
}
tsx 复制代码
// container/src/components/CartWidget.tsx
import React from "react";
import { useSharedState } from "shared/hooks/useSharedState";

interface CartItem {
  productId: string;
  name: string;
  price: number;
  quantity: number;
}

export function CartWidget() {
  const [cart] = useSharedState<CartItem[]>("shopping-cart", []);
  const [isOpen, setIsOpen] = React.useState(false);

  const totalItems = cart.reduce((total, item) => total + item.quantity, 0);
  const totalPrice = cart.reduce(
    (total, item) => total + item.price * item.quantity,
    0
  );

  return (
    <div className="cart-widget">
      <button className="cart-button" onClick={() => setIsOpen(!isOpen)}>
        <span className="cart-icon">🛒</span>
        <span className="cart-count">{totalItems}</span>
      </button>

      {isOpen && (
        <div className="cart-dropdown">
          <h3>购物车</h3>

          {cart.length === 0 ? (
            <p>购物车为空</p>
          ) : (
            <>
              <ul className="cart-items">
                {cart.map((item) => (
                  <li key={item.productId} className="cart-item">
                    <div className="item-info">
                      <span className="item-name">{item.name}</span>
                      <span className="item-quantity">x{item.quantity}</span>
                    </div>
                    <span className="item-price">
                      ¥{(item.price * item.quantity).toFixed(2)}
                    </span>
                  </li>
                ))}
              </ul>

              <div className="cart-total">
                <span>总计:</span>
                <span>¥{totalPrice.toFixed(2)}</span>
              </div>

              <button
                className="checkout-button"
                onClick={() => {
                  // 跳转到结账页面
                  window.location.href = "/orders/checkout";
                }}
              >
                结账
              </button>
            </>
          )}
        </div>
      )}
    </div>
  );
}

5. 微前端的 CI/CD 与部署策略

微前端的独立部署是其核心优势之一,以下是一种有效的部署策略:

yaml 复制代码
# 微应用CI/CD管道 (.gitlab-ci.yml)
stages:
  - build
  - test
  - deploy

variables:
  DOCKER_REGISTRY: registry.example.com
  NAMESPACE: micro-frontend
  CONTAINER_IMAGE: ${DOCKER_REGISTRY}/${NAMESPACE}/${CI_PROJECT_NAME}:${CI_COMMIT_SHA}
  CONTAINER_IMAGE_LATEST: ${DOCKER_REGISTRY}/${NAMESPACE}/${CI_PROJECT_NAME}:latest

# 构建微应用
build:
  stage: build
  image: node:18-alpine
  script:
    - npm ci
    - npm run build
    - npm run lint
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour

# 测试微应用
test:
  stage: test
  image: node:18-alpine
  script:
    - npm ci
    - npm test
  dependencies:
    - build

# 容器化部署
deploy:
  stage: deploy
  image: docker:20.10
  services:
    - docker:20.10-dind
  before_script:
    - docker login -u ${DOCKER_USER} -p ${DOCKER_PASSWORD} ${DOCKER_REGISTRY}
  script:
    - docker build -t ${CONTAINER_IMAGE} -t ${CONTAINER_IMAGE_LATEST} .
    - docker push ${CONTAINER_IMAGE}
    - docker push ${CONTAINER_IMAGE_LATEST}
    # 部署到Kubernetes
    - kubectl set image deployment/${CI_PROJECT_NAME} ${CI_PROJECT_NAME}=${CONTAINER_IMAGE} --namespace=${NAMESPACE}
    - kubectl rollout status deployment/${CI_PROJECT_NAME} --namespace=${NAMESPACE}
  only:
    - main
    - tags
dockerfile 复制代码
# 微应用容器化部署(Dockerfile)
# 构建阶段
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 生产阶段
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

# 支持环境变量替换
COPY ./env.sh /usr/share/nginx/html/env.sh
RUN chmod +x /usr/share/nginx/html/env.sh

# 启动nginx,同时支持运行时环境变量注入
CMD ["/bin/sh", "-c", "/usr/share/nginx/html/env.sh && nginx -g 'daemon off;'"]
bash 复制代码
#!/bin/sh
# env.sh - 运行时环境变量注入
# 替换配置文件中的环境变量
envsubst < /usr/share/nginx/html/config.template.js > /usr/share/nginx/html/config.js

6. 微前端性能优化策略

微前端架构带来了模块化的好处,但也引入了性能挑战,以下是关键优化策略:

共享依赖优化

js 复制代码
// 优化共享库加载 (container/webpack.config.js)
new ModuleFederationPlugin({
  // ...其他配置
  shared: {
    // 更精细的共享配置
    react: {
      singleton: true,
      requiredVersion: "^18.0.0",
      eager: true, // 预加载React
    },
    "react-dom": {
      singleton: true,
      requiredVersion: "^18.0.0",
      eager: true,
    },
    // 共享UI库
    "@mui/material": {
      singleton: true,
      requiredVersion: "^5.0.0",
    },
    // 共享工具库
    lodash: {
      singleton: true,
      requiredVersion: "^4.0.0",
    },
    // 状态管理
    zustand: {
      singleton: true,
    },
    // 路由
    "react-router-dom": {
      singleton: true,
      requiredVersion: "^6.0.0",
    },
    // 自定义共享库
    shared: {
      import: "./src/shared-libs/index.js",
      packageName: "shared",
      singleton: true,
    },
  },
});

预加载策略

jsx 复制代码
// container/src/components/Navigation.tsx
import React, { useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom';

export function Navigation() {
  const location = useLocation();

  // 预加载微应用
  useEffect(() => {
    // 获取当前路径
    const currentPath = location.pathname;

    // 预测用户可能访问的下一个微应用
    const potentialNextApps = [];

    if (currentPath.startsWith('/dashboard')) {
      // 用户在仪表盘,可能会查看订单
      potentialNextApps.push(import('orders/OrdersApp'));
    }

    if (currentPath.startsWith('/orders')) {
      // 用户在订单页面,可能会查看个人资料
      potentialNextApps.push(import('profile/ProfileApp'));
    }

    // 用低优先级预加载
    if ('requestIdleCallback' in window) {
      window.requestIdleCallback(() => {
        // 浏览器空闲时预加载
        potentialNextApps.forEach(app => app.catch(() => {}));
      });
    } else {
      // 回退方案
      setTimeout(() => {
        potentialNextApps.forEach(app => app.catch(() => {}));
      }, 1000);
    }
  }, [location]);

  return (
    <nav className="main-nav">
      <div className="logo">
        <img src="/logo.svg" alt="企业logo" />
        <span>企业管理系统</span>
      </div>

      <ul className="nav-links">
        <li className={location.pathname === '/' || location.pathname.startsWith('/dashboard') ? 'active' : ''}>
          <Link to="/">
            <i className="icon dashboard-icon"></i>
            <span>仪表盘</span>
          </Link>
        </li>
        <li className={location.pathname.startsWith('/profile') ? 'active' : ''}>
          <Link to="/profile">
            <i className="icon profile-icon"></i>
            <span>个人资料</span>
          </Link>
        </li>
        <li className={location.pathname.startsWith('/orders') ? 'active' : ''}>
          <Link to="/orders">
            <i className="icon orders-icon"></i>
            <span>订单管理</span>
          </Link>
        </li>
        <li className={location.pathname.startsWith('/settings') ? 'active' : ''}>
          <Link to="/settings">
            <i className="icon settings-icon"></i>
            <span>系统设置</span>
          </Link>
        </li>
      </ul>

      <div className="user-profile">
        <img src="/avatar.jpg" alt="用户头像" className="avatar" />
        <div className="user-info">
          <span className="user-name">张经理</span>
          <span className="user-role">系统管理员</span>
        </div>
      </div>
    </nav>
)

7. 高级性能优化技巧

监控与分析

tsx 复制代码
// container/src/utils/microAppMetrics.ts
import {
  performance as browserPerformance,
  PerformanceObserver,
} from "perf-hooks";

class MicroAppMetrics {
  private static instance: MicroAppMetrics;
  private metrics: Record<string, any[]> = {};

  private constructor() {
    this.setupPerformanceObserver();
  }

  public static getInstance(): MicroAppMetrics {
    if (!MicroAppMetrics.instance) {
      MicroAppMetrics.instance = new MicroAppMetrics();
    }
    return MicroAppMetrics.instance;
  }

  private setupPerformanceObserver() {
    // 监听资源加载性能
    const resourceObserver = new PerformanceObserver((list) => {
      const entries = list.getEntries();
      entries.forEach((entry) => {
        if (entry.name.includes("remoteEntry.js")) {
          const appName = this.extractAppName(entry.name);
          if (appName) {
            this.recordMetric(appName, "resourceLoad", {
              duration: entry.duration,
              startTime: entry.startTime,
              name: entry.name,
            });
          }
        }
      });
    });

    resourceObserver.observe({ entryTypes: ["resource"] });

    // 监听首次渲染性能
    const paintObserver = new PerformanceObserver((list) => {
      const entries = list.getEntries();
      entries.forEach((entry) => {
        this.recordMetric("container", entry.name, {
          startTime: entry.startTime,
        });
      });
    });

    paintObserver.observe({ entryTypes: ["paint"] });
  }

  private extractAppName(url: string): string | null {
    const remoteEntryRegex = /\/([^\/]+)@/;
    const match = url.match(remoteEntryRegex);
    return match ? match[1] : null;
  }

  public recordLoadStart(appName: string) {
    this.recordMetric(appName, "loadStart", {
      timestamp: Date.now(),
    });
  }

  public recordLoadComplete(appName: string) {
    this.recordMetric(appName, "loadComplete", {
      timestamp: Date.now(),
    });

    // 计算加载时间
    const metrics = this.getAppMetrics(appName);
    const loadStart = metrics.find((m) => m.type === "loadStart");
    const loadComplete = metrics.find((m) => m.type === "loadComplete");

    if (loadStart && loadComplete) {
      const loadTime = loadComplete.data.timestamp - loadStart.data.timestamp;

      this.recordMetric(appName, "loadTime", {
        duration: loadTime,
      });

      console.info(`微应用 ${appName} 加载耗时: ${loadTime}ms`);

      // 超过阈值发出警告
      if (loadTime > 1000) {
        console.warn(`微应用 ${appName} 加载时间超过1秒,可能需要优化`);
      }
    }
  }

  public recordMountStart(appName: string) {
    this.recordMetric(appName, "mountStart", {
      timestamp: Date.now(),
    });
  }

  public recordMountComplete(appName: string) {
    this.recordMetric(appName, "mountComplete", {
      timestamp: Date.now(),
    });

    // 计算挂载时间
    const metrics = this.getAppMetrics(appName);
    const mountStart = metrics.find((m) => m.type === "mountStart");
    const mountComplete = metrics.find((m) => m.type === "mountComplete");

    if (mountStart && mountComplete) {
      const mountTime =
        mountComplete.data.timestamp - mountStart.data.timestamp;

      this.recordMetric(appName, "mountTime", {
        duration: mountTime,
      });

      console.info(`微应用 ${appName} 挂载耗时: ${mountTime}ms`);
    }
  }

  private recordMetric(appName: string, type: string, data: any) {
    if (!this.metrics[appName]) {
      this.metrics[appName] = [];
    }

    this.metrics[appName].push({
      type,
      timestamp: Date.now(),
      data,
    });

    // 定期发送性能数据到后端
    this.scheduleMetricsUpload();
  }

  private getAppMetrics(appName: string) {
    return this.metrics[appName] || [];
  }

  private scheduleMetricsUpload() {
    // 使用防抖,避免频繁上传
    if (this.uploadTimeout) {
      clearTimeout(this.uploadTimeout);
    }

    this.uploadTimeout = setTimeout(() => {
      this.uploadMetrics();
    }, 5000);
  }

  private uploadTimeout: any = null;

  private uploadMetrics() {
    if (Object.keys(this.metrics).length === 0) return;

    // 克隆一份数据用于上传
    const metricsToUpload = JSON.parse(JSON.stringify(this.metrics));

    // 清空已收集的指标
    this.metrics = {};

    // 上传到分析服务
    fetch("/api/metrics", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        timestamp: Date.now(),
        metrics: metricsToUpload,
      }),
    }).catch((err) => {
      console.error("上传性能指标失败:", err);
    });
  }

  public getAllMetrics() {
    return this.metrics;
  }
}

export default MicroAppMetrics.getInstance();

智能长列表优化

tsx 复制代码
// shared/src/components/VirtualizedList.tsx
import React, { useRef, useState, useEffect } from "react";

interface VirtualizedListProps<T> {
  items: T[];
  height: number;
  rowHeight: number;
  renderItem: (item: T, index: number) => React.ReactNode;
  overscanCount?: number;
  onEndReached?: () => void;
  endReachedThreshold?: number;
}

export function VirtualizedList<T>({
  items,
  height,
  rowHeight,
  renderItem,
  overscanCount = 3,
  onEndReached,
  endReachedThreshold = 0.8,
}: VirtualizedListProps<T>) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [scrollTop, setScrollTop] = useState(0);

  const totalHeight = items.length * rowHeight;

  // 计算可见范围的起始索引和结束索引
  const startIndex = Math.max(
    0,
    Math.floor(scrollTop / rowHeight) - overscanCount
  );
  const visibleRowsCount = Math.ceil(height / rowHeight) + 2 * overscanCount;
  const endIndex = Math.min(items.length - 1, startIndex + visibleRowsCount);

  // 处理滚动事件
  const handleScroll = () => {
    if (containerRef.current) {
      const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
      setScrollTop(scrollTop);

      // 检测是否达到底部阈值
      if (
        onEndReached &&
        scrollTop + clientHeight >= scrollHeight * endReachedThreshold
      ) {
        onEndReached();
      }
    }
  };

  // 监听滚动事件
  useEffect(() => {
    const currentContainer = containerRef.current;
    if (currentContainer) {
      currentContainer.addEventListener("scroll", handleScroll);
      return () => {
        currentContainer.removeEventListener("scroll", handleScroll);
      };
    }
  }, []);

  // 只渲染可见范围内的项
  const visibleItems = items
    .slice(startIndex, endIndex + 1)
    .map((item, index) => {
      const actualIndex = startIndex + index;
      const top = actualIndex * rowHeight;

      return (
        <div
          key={actualIndex}
          style={{
            position: "absolute",
            top,
            height: rowHeight,
            left: 0,
            right: 0,
          }}
        >
          {renderItem(item, actualIndex)}
        </div>
      );
    });

  return (
    <div
      ref={containerRef}
      style={{
        height,
        overflow: "auto",
        position: "relative",
      }}
    >
      <div style={{ height: totalHeight, position: "relative" }}>
        {visibleItems}
      </div>
    </div>
  );
}

高级懒加载容器

tsx 复制代码
// container/src/components/MicroAppLoader.tsx
import React, { Suspense, useMemo, useRef, useState, useEffect } from "react";
import { ErrorBoundary } from "react-error-boundary";
import microAppMetrics from "../utils/microAppMetrics";

interface MicroAppLoaderProps {
  name: string;
  fallback?: React.ReactNode;
  errorFallback?: (
    error: Error,
    resetErrorBoundary: () => void
  ) => React.ReactNode;
  loadingDelay?: number;
  renderOnError?: boolean;
}

export const MicroAppLoader: React.FC<MicroAppLoaderProps> = ({
  name,
  fallback = <div>加载中...</div>,
  errorFallback,
  loadingDelay = 200,
  renderOnError = false,
  children,
}) => {
  const [showLoading, setShowLoading] = useState(false);
  const [hasError, setHasError] = useState(false);
  const timerRef = useRef<number | null>(null);

  // 延迟显示加载指示器,避免闪烁
  useEffect(() => {
    microAppMetrics.recordLoadStart(name);

    timerRef.current = window.setTimeout(() => {
      setShowLoading(true);
    }, loadingDelay);

    return () => {
      if (timerRef.current !== null) {
        clearTimeout(timerRef.current);
      }
    };
  }, [loadingDelay]);

  // 错误处理
  const handleError = (error: Error) => {
    console.error(`微应用 ${name} 加载失败:`, error);
    setHasError(true);

    // 记录错误指标
    microAppMetrics.recordMetric(name, "loadError", {
      error: error.message,
      stack: error.stack,
    });
  };

  // 渲染错误后备UI的函数
  const renderErrorFallback = useMemo(() => {
    return (props: { error: Error; resetErrorBoundary: () => void }) => {
      const { error, resetErrorBoundary } = props;

      // 使用自定义错误UI或默认错误UI
      if (errorFallback) {
        return errorFallback(error, resetErrorBoundary);
      }

      return (
        <div className="micro-app-error">
          <h3>应用加载失败</h3>
          <p>{error.message}</p>
          <button onClick={resetErrorBoundary}>重试</button>
        </div>
      );
    };
  }, [errorFallback]);

  // 加载完成后的处理
  const handleLoadComplete = () => {
    microAppMetrics.recordLoadComplete(name);

    if (timerRef.current !== null) {
      clearTimeout(timerRef.current);
      timerRef.current = null;
    }

    setShowLoading(false);
  };

  // 对于已知有错误的应用,可以选择不渲染
  if (hasError && !renderOnError) {
    return renderErrorFallback({
      error: new Error(`微应用 ${name} 加载失败`),
      resetErrorBoundary: () => setHasError(false),
    });
  }

  return (
    <ErrorBoundary fallbackRender={renderErrorFallback} onError={handleError}>
      <Suspense
        fallback={showLoading ? fallback : null}
        onErrorCapture={handleError}
      >
        <AppMountTracker name={name} onLoad={handleLoadComplete}>
          {children}
        </AppMountTracker>
      </Suspense>
    </ErrorBoundary>
  );
};

// 跟踪微应用挂载状态的组件
const AppMountTracker: React.FC<{
  name: string;
  onLoad: () => void;
  children: React.ReactNode;
}> = ({ name, onLoad, children }) => {
  // 记录挂载开始
  useEffect(() => {
    microAppMetrics.recordMountStart(name);
    onLoad();

    return () => {
      // 记录卸载
      microAppMetrics.recordMetric(name, "unmount", {
        timestamp: Date.now(),
      });
    };
  }, []);

  // 记录挂载完成
  useEffect(() => {
    // 使用requestAnimationFrame确保在DOM更新后记录
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        microAppMetrics.recordMountComplete(name);
      });
    });
  }, []);

  return <>{children}</>;
};

8. 实际案例:微前端电商系统

让我们来看一个完整的微前端架构实现案例,针对电商系统:

入口主应用

tsx 复制代码
// container/src/App.tsx
import React, { Suspense, useState, useEffect } from "react";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { ErrorBoundary } from "react-error-boundary";
import { MicroAppLoader } from "./components/MicroAppLoader";
import { Navigation } from "./components/Navigation";
import { CartWidget } from "./components/CartWidget";
import { ThemeProvider } from "./components/ThemeProvider";
import { AuthProvider, useAuth } from "./hooks/useAuth";
import { SharedStateProvider } from "shared/hooks/useSharedState";
import { Loading } from "./components/Loading";
import Login from "./pages/Login";

// 懒加载微应用
const ProductCatalog = React.lazy(() => import("catalog/ProductCatalogApp"));
const Orders = React.lazy(() => import("orders/OrdersApp"));
const UserProfile = React.lazy(() => import("profile/ProfileApp"));
const Analytics = React.lazy(() => import("analytics/AnalyticsApp"));

// 定制主题
const theme = {
  colors: {
    primary: "#e43",
    secondary: "#2196f3",
    text: "#333",
    background: "#fff",
    surface: "#f5f5f5",
  },
  fonts: {
    main: '"Roboto", "Helvetica", "Arial", sans-serif',
    heading: '"Poppins", "Roboto", sans-serif',
  },
};

export default function App() {
  return (
    <ThemeProvider theme={theme}>
      <SharedStateProvider>
        <AuthProvider>
          <BrowserRouter>
            <AppContent />
          </BrowserRouter>
        </AuthProvider>
      </SharedStateProvider>
    </ThemeProvider>
  );
}

function AppContent() {
  const { isAuthenticated, isLoading } = useAuth();
  const [isAppReady, setIsAppReady] = useState(false);

  // 系统初始化
  useEffect(() => {
    // 模拟应用初始化过程
    const timer = setTimeout(() => {
      setIsAppReady(true);
    }, 500);

    return () => clearTimeout(timer);
  }, []);

  if (isLoading || !isAppReady) {
    return <Loading message="系统初始化中..." />;
  }

  if (!isAuthenticated) {
    return <Login />;
  }

  return (
    <div className="app-container">
      <Navigation />
      <CartWidget />

      <main className="main-content">
        <ErrorBoundary
          fallbackRender={({ error, resetErrorBoundary }) => (
            <div className="error-container">
              <h2>应用出错了</h2>
              <p>{error.message}</p>
              <button onClick={resetErrorBoundary}>重试</button>
            </div>
          )}
        >
          <Routes>
            <Route path="/" element={<Navigate to="/products" replace />} />
            <Route
              path="/products/*"
              element={
                <MicroAppLoader
                  name="catalog"
                  fallback={<Loading message="加载商品目录..." />}
                >
                  <ProductCatalog />
                </MicroAppLoader>
              }
            />
            <Route
              path="/orders/*"
              element={
                <MicroAppLoader
                  name="orders"
                  fallback={<Loading message="加载订单管理..." />}
                >
                  <Orders />
                </MicroAppLoader>
              }
            />
            <Route
              path="/profile/*"
              element={
                <MicroAppLoader
                  name="profile"
                  fallback={<Loading message="加载用户资料..." />}
                >
                  <UserProfile />
                </MicroAppLoader>
              }
            />
            <Route
              path="/analytics/*"
              element={
                <MicroAppLoader
                  name="analytics"
                  fallback={<Loading message="加载数据分析..." />}
                >
                  <Analytics />
                </MicroAppLoader>
              }
            />
            <Route
              path="*"
              element={
                <div className="not-found">
                  <h2>404 - 页面未找到</h2>
                  <p>您访问的页面不存在</p>
                </div>
              }
            />
          </Routes>
        </ErrorBoundary>
      </main>
    </div>
  );
}

9. 微前端未来发展和实践总结

你应该采用微前端的情况

  1. 大型应用:页面数量超过 30 个,功能模块明确分离
  2. 多团队协作:3 个以上前端团队需要并行开发
  3. 渐进式升级:需要逐步将旧系统迁移到新框架
  4. 技术栈多样化:不同业务模块需要使用不同的前端技术栈
  5. 独立部署:模块需要单独发布、灰度测试而不影响整体系统

何时不推荐使用微前端

  1. 小型应用:页面数量少,功能简单
  2. 单一团队:只有一个前端团队负责开发
  3. 原型或 MVP:产品验证阶段,需要快速迭代
  4. 性能至上:对首屏加载性能有极高要求的应用
  5. 开发资源受限:团队规模小,无法承担微前端架构的维护成本

技术选型关键考量

  1. 模块联邦优势

    • TypeScript 友好,开发体验好
    • 构建时优化,运行时高效
    • 与 React 生态深度集成
    • 社区活跃,文档丰富
  2. 框架无关方案

    • Single-SPA:老牌微前端框架,适合技术栈多样化场景
    • qiankun/micro-app:阿里和京东方案,国内生态完善
    • Piral:适合快速搭建微前端架构的框架

实践经验总结

  1. 从设计阶段考虑微前端:应用架构、团队分工、技术栈选择
  2. 建立良好的团队协作机制:约定优于配置,明确接口规范
  3. 制定共享资源策略:依赖管理、组件库、设计系统
  4. 建立完整的持续集成部署流程:自动化测试、独立部署
  5. 实施有效的监控与性能优化:应用级监控、模块级性能分析

下一篇预告

下一篇文章将聚焦于更加前沿的主题:《【React + AI】深度实践:从 LLM 集成到智能 UI 构建》。我们将探索:

  1. 如何在 React 应用中集成现代 AI 模型
  2. 构建智能化的用户界面与交互体验
  3. 使用 AI 提升开发效率与用户体验
  4. 前端 AI 技术的未来发展方向

敬请期待!

关于作者

Hi,我是 hyy,一位热爱技术的全栈开发者:

  • 🚀 专注 TypeScript 全栈开发,偏前端技术栈
  • 💼 多元工作背景(跨国企业、技术外包、创业公司)
  • 📝 掘金活跃技术作者
  • 🎵 电子音乐爱好者
  • 🎮 游戏玩家
  • 💻 技术分享达人

加入我们

欢迎加入前端技术交流圈,与 10000+开发者一起:

  • 探讨前端最新技术趋势
  • 解决开发难题
  • 分享职场经验
  • 获取优质学习资源

添加方式:掘金摸鱼沸点 👈 扫码进群

相关推荐
魔云连洲43 分钟前
详细解释浏览器是如何渲染页面的?
前端·css·浏览器渲染
Kx…………1 小时前
Day2—3:前端项目uniapp壁纸实战
前端·css·学习·uni-app·html
培根芝士3 小时前
Electron打包支持多语言
前端·javascript·electron
mr_cmx3 小时前
Nodejs数据库单一连接模式和连接池模式的概述及写法
前端·数据库·node.js
东部欧安时4 小时前
研一自救指南 - 07. CSS面向面试学习
前端·css
涵信4 小时前
第十二节:原理深挖-React Fiber架构核心思想
前端·react.js·架构
ohMyGod_1234 小时前
React-useRef
前端·javascript·react.js
每一天,每一步4 小时前
AI语音助手 React 组件使用js-audio-recorder实现,将获取到的语音转成base64发送给后端,后端接口返回文本内容
前端·javascript·react.js
上趣工作室4 小时前
vue3专题1------父组件中更改子组件的属性
前端·javascript·vue.js
冯诺一没有曼4 小时前
无线网络入侵检测系统实战 | 基于React+Python的可视化安全平台开发详解
前端·安全·react.js