电商场景React技术栈应用

一、各厂技术栈概览

公司 核心框架 微前端方案 跨端方案 状态管理 组件库
阿里巴巴/蚂蚁 React(主力) qiankun(蚂蚁开源) Rax Redux / Umi内置 @alifd/next (Fusion Design)
京东 React(占比70%+)2 MicroApp(京东开源)/ jd-micro Taro(京东开源) Redux Toolkit / MobX JD Design
美团 React 为主,Vue 为辅4 内部微前端方案 React2X(美团外卖自研) Redux Toolkit / MobX 内部组件库
拼多多 React(主力) 内部微前端方案 内部跨端方案 Redux / Context 内部组件库

二、电商前端架构模式

2.1 微前端架构

中国大厂普遍采用微前端解决复杂电商系统的开发效率问题。阿里开源 qiankun (基于 single-spa),京东开源 MicroApp(基于 WebComponent 思想)3

qiankun 主应用注册(阿里系常用):

javascript 复制代码
// master/src/index.js
import { registerMicroApps, start } from 'qiankun';

// 电商场景:按业务域拆分子应用
registerMicroApps([
  {
    name: 'product-list',
    entry: '//cdn.example.com/product-list/',
    container: '#subapp-container',
    activeRule: '/products',
  },
  {
    name: 'cart',
    entry: '//cdn.example.com/cart/',
    container: '#subapp-container',
    activeRule: '/cart',
  },
  {
    name: 'order',
    entry: '//cdn.example.com/order/',
    container: '#subapp-container',
    activeRule: '/order',
  },
]);

start({
  prefetch: 'all', // 空闲时预加载所有子应用
  sandbox: { strictStyleIsolation: true },
});

MicroApp 方式(京东系常用):

html 复制代码
<!-- 主应用 HTML -->
<micro-app
  name="product-list"
  url="https://cdn.example.com/product-list/"
  base-route="/products"
  keep-alive
></micro-app>
javascript 复制代码
import microApp from '@micro-zoe/micro-app';
microApp.start();

关键对比 3

  • qiankun:~30KB gzip,Proxy JS 沙箱 + Shadow DOM,生命周期钩子更丰富(bootstrap/mount/unmount/update)
  • MicroApp:~14KB gzip,WebComponent 原生隔离,零侵入接入,keep-alive 自动缓存子应用状态

京东京喜微前端实践 2

  • 主应用负责子应用注册、路由分发、全局状态管理(用户登录态、购物车数据)、公共资源提供(JD Design 组件库)
  • 子应用按业务域拆分(订单子应用、拼购子应用、商家子应用),独立开发、构建、部署
  • 通信机制:全局状态池 + 事件总线,实现跨应用数据通信
  • 效果:团队协作效率提升 30%+,部署周期从全量发布变为子应用独立发布

2.2 SSR / CSR 混合渲染

阿里系广泛使用 Umi 框架(蚂蚁集团开发),内置 SSR 能力 6;京东采用自研 SSR 方案。

混合渲染策略(电商最佳实践):

复制代码
┌─────────────────────────────────────────────┐
│              电商混合渲染策略                    │
├──────────┬──────────┬──────────┬────────────┤
│  首页SSR  │ 商品列表  │ 商品详情  │  购物车/订单  │
│  (SEO关键) │ CSR+骨架屏│ SSR首屏  │  纯CSR       │
│  服务端渲染│ +无限滚动 │ CSR交互  │  (需登录态)   │
└──────────┴──────────┴──────────┴────────────┘
typescript 复制代码
// Umi 配置 - 混合渲染
// .umirc.ts
export default {
  ssr: {
    // 首页和商品详情走 SSR(SEO + 首屏性能)
    include: ['/'],
    exclude: ['/cart/*', '/order/*', '/user/*'],
    // 流式 SSR
    mode: 'stream',
  },
  routes: [
    { path: '/', component: '@/pages/Home', title: '首页' },
    { path: '/product/:id', component: '@/pages/ProductDetail', title: '商品详情' },
    { path: '/cart', component: '@/pages/Cart' },           // 纯 CSR
    { path: '/order', component: '@/pages/Order' },         // 纯 CSR
    { path: '/search', component: '@/pages/Search' },       // CSR + 骨架屏
  ],
};

2.3 跨端同构(美团 React2X)

美团外卖自研 React2X(R2X) 框架,实现"一次研发,多终端容器复用" 5

  • 覆盖容器:MRN(React Native)、WebView、小程序、MTFlutter、Mach、小游戏、PC 浏览器
  • 核心设计:编译时插件化架构(基于 Tapable),容器插件 + 功能插件
  • 统一组件库 @r2x/components 和统一 API @r2x/r2x
  • 性能:小程序 Benchmark 中领先 Remax 和 Taro-MP

三、商品列表/详情页组件设计

3.1 虚拟滚动列表

电商商品列表动辄数千条数据,虚拟滚动是标配 4

tsx 复制代码
import { useRef, useState, useEffect, useCallback } from 'react';

interface VirtualScrollProps<T> {
  items: T[];
  itemHeight: number;
  containerHeight: number;
  overscan?: number;
  renderItem: (item: T, index: number) => React.ReactNode;
}

function VirtualList<T>({
  items,
  itemHeight,
  containerHeight,
  overscan = 5,
  renderItem,
}: VirtualScrollProps<T>) {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef<HTMLDivElement>(null);

  // 计算可见范围
  const totalHeight = items.length * itemHeight;
  const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
  const endIndex = Math.min(
    items.length - 1,
    Math.ceil((scrollTop + containerHeight) / itemHeight) + overscan
  );
  const visibleItems = items.slice(startIndex, endIndex + 1);
  const offsetY = startIndex * itemHeight;

  const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
    // 使用 requestAnimationFrame 节流
    requestAnimationFrame(() => {
      setScrollTop(e.currentTarget.scrollTop);
    });
  }, []);

  return (
    <div
      ref={containerRef}
      onScroll={handleScroll}
      style={{ height: containerHeight, overflow: 'auto', position: 'relative' }}
    >
      {/* 占位容器,撑起总高度 */}
      <div style={{ height: totalHeight, position: 'relative' }}>
        {/* 只渲染可见区域的商品 */}
        <div style={{ position: 'absolute', top: offsetY, width: '100%' }}>
          {visibleItems.map((item, i) => (
            <div key={startIndex + i} style={{ height: itemHeight }}>
              {renderItem(item, startIndex + i)}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

实际生产中,大厂通常使用 react-windowreact-virtuoso,并配合 CDN 图片优化。

3.2 图片懒加载

tsx 复制代码
import { useRef, useState, useEffect } from 'react';

interface LazyImageProps {
  src: string;
  alt: string;
  placeholder?: string;
  className?: string;
  width?: number;
  height?: number;
}

function LazyImage({
  src,
  alt,
  placeholder = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2YwZjBmMCIvPjwvc3ZnPg==',
  className,
  width,
  height,
}: LazyImageProps) {
  const imgRef = useRef<HTMLImageElement>(null);
  const [isVisible, setIsVisible] = useState(false);
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.disconnect();
        }
      },
      { rootMargin: '200px' } // 提前 200px 开始加载
    );

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return (
    <div
      ref={imgRef}
      className={className}
      style={{
        width,
        height,
        backgroundImage: loaded ? 'none' : `url(${placeholder})`,
        backgroundSize: 'cover',
        backgroundPosition: 'center',
      }}
    >
      {isVisible && (
        <img
          src={src}
          alt={alt}
          loading="lazy" // 原生懒加载兜底
          onLoad={() => setLoaded(true)}
          style={{
            width: '100%',
            height: '100%',
            objectFit: 'cover',
            opacity: loaded ? 1 : 0,
            transition: 'opacity 0.3s',
          }}
        />
      )}
    </div>
  );
}

3.3 无限滚动

tsx 复制代码
import { useEffect, useCallback, useRef } from 'react';

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

function ProductList() {
  const [products, setProducts] = useState<Product[]>([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);
  const sentinelRef = useRef<HTMLDivElement>(null);

  const fetchProducts = useCallback(async (pageNum: number) => {
    setLoading(true);
    try {
      const res = await fetch(`/api/products?page=${pageNum}&size=20`);
      const data = await res.json();
      setProducts((prev) =>
        pageNum === 1 ? data.list : [...prev, ...data.list]
      );
      setHasMore(data.list.length === 20);
    } finally {
      setLoading(false);
    }
  }, []);

  // 初始加载
  useEffect(() => {
    fetchProducts(1);
  }, [fetchProducts]);

  // IntersectionObserver 触发加载更多
  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting && hasMore && !loading) {
          setPage((prev) => prev + 1);
        }
      },
      { rootMargin: '300px' }
    );

    if (sentinelRef.current) {
      observer.observe(sentinelRef.current);
    }

    return () => observer.disconnect();
  }, [hasMore, loading]);

  // page 变化时请求
  useEffect(() => {
    if (page > 1) fetchProducts(page);
  }, [page, fetchProducts]);

  return (
    <div>
      <VirtualList
        items={products}
        itemHeight={300}
        containerHeight={window.innerHeight}
        renderItem={(product) => (
          <ProductCard key={product.id} product={product} />
        )}
      />
      {/* 哨兵元素 */}
      <div ref={sentinelRef} style={{ height: 1 }} />
      {loading && <LoadingSpinner />}
    </div>
  );
}

四、购物车状态管理方案

4.1 选型对比

方案 适用场景 大厂使用情况
Redux Toolkit 大型电商,复杂状态流转 京东(集团推荐)2、美团(主力)4
Zustand 中小型电商,轻量需求 新项目趋势选择
Jotai 原子化状态,细粒度更新 部分创新业务
React Context 简单购物车,原型验证 小型项目

4.2 Redux Toolkit 方案(京东/美团主流)

typescript 复制代码
// store/cartSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { createSelector } from '@reduxjs/toolkit';

export interface CartItem {
  productId: string;
  title: string;
  price: number;
  quantity: number;
  image: string;
  checked: boolean; // 是否选中
}

interface CartState {
  items: CartItem[];
  loading: boolean;
  error: string | null;
}

const initialState: CartState = {
  items: [],
  loading: false,
  error: null,
};

const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    addToCart(state, action: PayloadAction<Omit<CartItem, 'quantity' | 'checked'>>) {
      const existing = state.items.find(
        (item) => item.productId === action.payload.productId
      );
      if (existing) {
        existing.quantity += 1;
      } else {
        state.items.push({
          ...action.payload,
          quantity: 1,
          checked: true,
        });
      }
    },
    removeFromCart(state, action: PayloadAction<string>) {
      state.items = state.items.filter(
        (item) => item.productId !== action.payload
      );
    },
    updateQuantity(
      state,
      action: PayloadAction<{ productId: string; quantity: number }>
    ) {
      const item = state.items.find(
        (i) => i.productId === action.payload.productId
      );
      if (item) {
        item.quantity = Math.max(1, action.payload.quantity);
      }
    },
    toggleCheck(state, action: PayloadAction<string>) {
      const item = state.items.find((i) => i.productId === action.payload);
      if (item) item.checked = !item.checked;
    },
    toggleCheckAll(state) {
      const allChecked = state.items.every((i) => i.checked);
      state.items.forEach((i) => (i.checked = !allChecked));
    },
  },
});

// 派生选择器(memoized)
const selectCartItems = (state: RootState) => state.cart.items;
const selectCheckedItems = createSelector(
  [selectCartItems],
  (items) => items.filter((i) => i.checked)
);
const selectTotalPrice = createSelector([selectCheckedItems], (items) =>
  items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
const selectTotalCount = createSelector([selectCheckedItems], (items) =>
  items.reduce((sum, item) => sum + item.quantity, 0)
);

export const {
  addToCart,
  removeFromCart,
  updateQuantity,
  toggleCheck,
  toggleCheckAll,
} = cartSlice.actions;
export { selectCartItems, selectCheckedItems, selectTotalPrice, selectTotalCount };
export default cartSlice.reducer;

4.3 Zustand 方案(轻量替代)

typescript 复制代码
// store/cartStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface CartItem {
  productId: string;
  title: string;
  price: number;
  quantity: number;
  image: string;
  checked: boolean;
}

interface CartStore {
  items: CartItem[];
  addToCart: (product: Omit<CartItem, 'quantity' | 'checked'>) => void;
  removeFromCart: (productId: string) => void;
  updateQuantity: (productId: string, quantity: number) => void;
  toggleCheck: (productId: string) => void;
  toggleCheckAll: () => void;
  // 派生状态
  getCheckedItems: () => CartItem[];
  getTotalPrice: () => number;
  getTotalCount: () => number;
}

export const useCartStore = create<CartStore>()(
  persist(
    (set, get) => ({
      items: [],

      addToCart: (product) =>
        set((state) => {
          const existing = state.items.find(
            (i) => i.productId === product.productId
          );
          if (existing) {
            return {
              items: state.items.map((i) =>
                i.productId === product.productId
                  ? { ...i, quantity: i.quantity + 1 }
                  : i
              ),
            };
          }
          return {
            items: [...state.items, { ...product, quantity: 1, checked: true }],
          };
        }),

      removeFromCart: (productId) =>
        set((state) => ({
          items: state.items.filter((i) => i.productId !== productId),
        })),

      updateQuantity: (productId, quantity) =>
        set((state) => ({
          items: state.items.map((i) =>
            i.productId === productId ? { ...i, quantity: Math.max(1, quantity) } : i
          ),
        })),

      toggleCheck: (productId) =>
        set((state) => ({
          items: state.items.map((i) =>
            i.productId === productId ? { ...i, checked: !i.checked } : i
          ),
        })),

      toggleCheckAll: () =>
        set((state) => {
          const allChecked = state.items.every((i) => i.checked);
          return {
            items: state.items.map((i) => ({ ...i, checked: !allChecked })),
          };
        }),

      getCheckedItems: () => get().items.filter((i) => i.checked),
      getTotalPrice: () =>
        get()
          .items.filter((i) => i.checked)
          .reduce((sum, i) => sum + i.price * i.quantity, 0),
      getTotalCount: () =>
        get()
          .items.filter((i) => i.checked)
          .reduce((sum, i) => sum + i.quantity, 0),
    }),
    {
      name: 'cart-storage', // localStorage key,购物车数据持久化
      partialize: (state) => ({ items: state.items }), // 只持久化 items
    }
  )
);

Zustand 的优势:无需 Provider 包裹、天然支持 SSR、persist 中间件实现 localStorage 持久化、TypeScript 友好 15


五、搜索与筛选组件

5.1 防抖搜索

typescript 复制代码
// hooks/useDebounce.ts
import { useState, useEffect } from 'react';

export function useDebounce<T>(value: T, delay: number = 300): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

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

  return debouncedValue;
}
tsx 复制代码
// components/SearchBar.tsx
import { useState, useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useDebounce } from '@/hooks/useDebounce';

function SearchBar() {
  const [searchParams, setSearchParams] = useSearchParams();
  const [inputValue, setInputValue] = useState(searchParams.get('q') || '');

  // 防抖后的搜索词
  const debouncedQuery = useDebounce(inputValue, 300);

  // 防抖值变化时同步到 URL
  useEffect(() => {
    if (debouncedQuery !== searchParams.get('q')) {
      setSearchParams(
        (prev) => {
          prev.set('q', debouncedQuery);
          prev.set('page', '1'); // 搜索词变化时重置页码
          return prev;
        },
        { replace: true }
      );
    }
  }, [debouncedQuery]);

  return (
    <div className="search-bar">
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        placeholder="搜索商品..."
      />
      {inputValue !== debouncedQuery && <SearchIndicator />}
    </div>
  );
}

5.2 URL 状态同步(筛选条件)

tsx 复制代码
// components/FilterPanel.tsx
import { useSearchParams, useNavigate } from 'react-router-dom';
import { useCallback, useMemo } from 'react';

interface FilterState {
  category: string;
  priceRange: [number, number];
  sort: string;
  page: number;
}

function FilterPanel() {
  const [searchParams, setSearchParams] = useSearchParams();

  // 从 URL 解析筛选状态
  const filters: FilterState = useMemo(() => ({
    category: searchParams.get('category') || 'all',
    priceRange: [
      Number(searchParams.get('minPrice')) || 0,
      Number(searchParams.get('maxPrice')) || Infinity,
    ],
    sort: searchParams.get('sort') || 'default',
    page: Number(searchParams.get('page')) || 1,
  }), [searchParams]);

  // 更新筛选条件并同步到 URL
  const updateFilter = useCallback(
    (key: keyof FilterState, value: string | number) => {
      setSearchParams(
        (prev) => {
          if (value === 'default' || value === 'all' || value === 1) {
            prev.delete(String(key));
          } else {
            prev.set(String(key), String(value));
          }
          // 筛选条件变化时重置页码
          if (key !== 'page') {
            prev.set('page', '1');
          }
          return prev;
        },
        { replace: true }
      );
    },
    [setSearchParams]
  );

  return (
    <div className="filter-panel">
      {/* 分类筛选 */}
      <select
        value={filters.category}
        onChange={(e) => updateFilter('category', e.target.value)}
      >
        <option value="all">全部分类</option>
        <option value="electronics">数码电子</option>
        <option value="clothing">服装鞋帽</option>
        <option value="food">食品生鲜</option>
      </select>

      {/* 排序 */}
      <div className="sort-options">
        {[
          { key: 'default', label: '综合' },
          { key: 'price_asc', label: '价格升序' },
          { key: 'price_desc', label: '价格降序' },
          { key: 'sales', label: '销量优先' },
        ].map(({ key, label }) => (
          <button
            key={key}
            className={filters.sort === key ? 'active' : ''}
            onClick={() => updateFilter('sort', key)}
          >
            {label}
          </button>
        ))}
      </div>
    </div>
  );
}

核心设计原则:所有筛选状态都同步到 URL,支持分享链接、浏览器前进后退、SEO 友好。


六、订单与支付流程组件

tsx 复制代码
// components/checkout/CheckoutFlow.tsx
import { useState } from 'react';
import { Steps, Card, Button, message } from 'antd';

// 步骤式订单流程
const CHECKOUT_STEPS = [
  { key: 'confirm', title: '确认订单' },
  { key: 'address', title: '收货地址' },
  { key: 'payment', title: '选择支付' },
  { key: 'result', title: '支付结果' },
];

function CheckoutFlow() {
  const [currentStep, setCurrentStep] = useState(0);
  const [orderId, setOrderId] = useState<string | null>(null);

  const handleCreateOrder = async () => {
    // 1. 创建订单
    const res = await fetch('/api/orders', { method: 'POST' });
    const data = await res.json();
    setOrderId(data.orderId);
    setCurrentStep(1);
  };

  const handleConfirmAddress = () => {
    setCurrentStep(2);
  };

  const handlePay = async (paymentMethod: string) => {
    try {
      // 2. 发起支付
      const res = await fetch('/api/pay', {
        method: 'POST',
        body: JSON.stringify({ orderId, paymentMethod }),
      });
      const data = await res.json();

      // 3. 调起支付SDK
      if (data.payUrl) {
        window.location.href = data.payUrl; // 支付宝/微信跳转支付
      } else if (data.payParams) {
        // 微信JSAPI支付
        WeixinJSBridge.invoke('getBrandWCPayRequest', data.payParams);
      }

      setCurrentStep(3);
    } catch (err) {
      message.error('支付失败,请重试');
    }
  };

  return (
    <div className="checkout-flow">
      <Steps current={currentStep} items={CHECKOUT_STEPS} />

      <div className="step-content">
        {currentStep === 0 && (
          <OrderConfirm onSubmit={handleCreateOrder} />
        )}
        {currentStep === 1 && (
          <AddressSelector
            onSelect={handleConfirmAddress}
            onBack={() => setCurrentStep(0)}
          />
        )}
        {currentStep === 2 && (
          <PaymentSelect
            onPay={handlePay}
            amount={9990} // 单位:分
            onBack={() => setCurrentStep(1)}
          />
        )}
        {currentStep === 3 && (
          <PaymentResult orderId={orderId!} />
        )}
      </div>
    </div>
  );
}

支付组件(懒加载):

tsx 复制代码
// 懒加载支付组件 - 仅在支付页面加载支付SDK
import { lazy, Suspense } from 'react';

const PaymentPage = lazy(() => import('./PaymentPage'));

function CheckoutRouter() {
  return (
    <Routes>
      <Route path="/checkout/confirm" element={<OrderConfirm />} />
      <Route
        path="/checkout/payment"
        element={
          <Suspense fallback={<PaymentSkeleton />}>
            <PaymentPage />
          </Suspense>
        }
      />
      <Route path="/checkout/result" element={<PaymentResult />} />
    </Routes>
  );
}

七、性能优化策略

7.1 代码分割与懒加载

tsx 复制代码
// 路由级代码分割
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// 每个路由独立 chunk
const Home = lazy(() => import('@/pages/Home'));
const ProductList = lazy(() => import('@/pages/ProductList'));
const ProductDetail = lazy(() => import('@/pages/ProductDetail'));
const Cart = lazy(() => import('@/pages/Cart'));
const Order = lazy(() => import('@/pages/Order'));
const Checkout = lazy(() => import('@/pages/Checkout'));
const UserCenter = lazy(() => import('@/pages/UserCenter'));

// 统一的 Suspense fallback
function PageLoading() {
  return (
    <div className="page-loading">
      <Skeleton active paragraph={{ rows: 8 }} />
    </div>
  );
}

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<PageLoading />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/products" element={<ProductList />} />
          <Route path="/product/:id" element={<ProductDetail />} />
          <Route path="/cart" element={<Cart />} />
          <Route path="/order" element={<Order />} />
          <Route path="/checkout" element={<Checkout />} />
          <Route path="/user/*" element={<UserCenter />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

7.2 组件级懒加载

tsx 复制代码
// 商品详情页中的非核心模块懒加载
import { lazy, Suspense } from 'react';

// 评论区 - 用户滚动到才加载
const CommentSection = lazy(() =>
  import('@/components/CommentSection')
);
// 推荐商品 - 底部模块延迟加载
const RecommendProducts = lazy(() =>
  import('@/components/RecommendProducts')
);
// 店铺信息 - 折叠面板内懒加载
const ShopInfo = lazy(() => import('@/components/ShopInfo'));

function ProductDetail() {
  return (
    <div>
      {/* 核心信息:同步加载 */}
      <ProductImages />
      <ProductTitle />
      <PriceInfo />
      <SkuSelector />

      {/* 非核心模块:懒加载 */}
      <Suspense fallback={<Skeleton paragraph={{ rows: 4 }} />}>
        <ShopInfo shopId={shopId} />
      </Suspense>

      <Suspense fallback={<Skeleton paragraph={{ rows: 6 }} />}>
        <CommentSection productId={productId} />
      </Suspense>

      <Suspense fallback={<Skeleton paragraph={{ rows: 3 }} />}>
        <RecommendProducts categoryId={categoryId} />
      </Suspense>
    </div>
  );
}

7.3 缓存策略(京东实践)

typescript 复制代码
// utils/requestCache.ts - 请求级缓存
const cache = new Map<string, { data: unknown; timestamp: number }>();
const CACHE_TTL = 5 * 60 * 1000; // 5分钟缓存

export async function cachedFetch<T>(
  url: string,
  options?: RequestInit
): Promise<T> {
  const cacheKey = `${url}_${JSON.stringify(options?.body || '')}`;

  // 检查缓存
  const cached = cache.get(cacheKey);
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data as T;
  }

  const res = await fetch(url, options);
  const data = await res.json();

  // 写入缓存(GET 请求才缓存)
  if (options?.method === undefined || options?.method === 'GET') {
    cache.set(cacheKey, { data, timestamp: Date.now() });
  }

  return data;
}

7.4 微前端共享依赖优化

javascript 复制代码
// qiankun 主应用配置 - 共享 React 依赖,避免重复加载
// 主应用 webpack.config.js
module.exports = {
  output: {
    library: `main-app`,
    libraryTarget: 'umd',
    publicPath: '//cdn.example.com/main/',
  },
  externals: {
    react: 'React',
    'react-dom': 'ReactDOM',
  },
};

// 子应用 webpack.config.js - 不打包 React,使用主应用提供的
module.exports = {
  output: {
    library: `product-list`,
    libraryTarget: 'umd',
    publicPath: '//cdn.example.com/product-list/',
  },
  externals: {
    react: 'React',
    'react-dom': 'ReactDOM',
  },
};

优化效果:子应用 A(2MB)+ 子应用 B(2MB),共享 React 后可节省约 1MB 重复资源 1


八、常用自定义 Hooks 封装

8.1 useDebounce(防抖)

typescript 复制代码
// hooks/useDebounce.ts
import { useState, useEffect } from 'react';

export function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

8.2 useInfiniteScroll(无限滚动)

typescript 复制代码
// hooks/useInfiniteScroll.ts
import { useState, useEffect, useCallback, useRef } from 'react';

interface UseInfiniteScrollOptions<T> {
  fetchFn: (page: number) => Promise<{ list: T[]; hasMore: boolean }>;
  pageSize?: number;
  threshold?: number; // 触底阈值(px)
}

export function useInfiniteScroll<T>({
  fetchFn,
  pageSize = 20,
  threshold = 200,
}: UseInfiniteScrollOptions<T>) {
  const [items, setItems] = useState<T[]>([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);
  const sentinelRef = useRef<HTMLDivElement>(null);

  const loadMore = useCallback(async () => {
    if (loading || !hasMore) return;
    setLoading(true);
    try {
      const result = await fetchFn(page);
      setItems((prev) => (page === 1 ? result.list : [...prev, ...result.list]));
      setHasMore(result.hasMore);
    } finally {
      setLoading(false);
    }
  }, [fetchFn, page, loading, hasMore]);

  // 初始加载
  useEffect(() => {
    loadMore();
  }, []); // eslint-disable-line

  // IntersectionObserver
  useEffect(() => {
    const el = sentinelRef.current;
    if (!el) return;

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting && hasMore && !loading) {
          setPage((p) => p + 1);
        }
      },
      { rootMargin: `0px 0px ${threshold}px 0px` }
    );

    observer.observe(el);
    return () => observer.disconnect();
  }, [hasMore, loading, threshold]);

  // page 变化触发加载
  useEffect(() => {
    if (page > 1) loadMore();
  }, [page]); // eslint-disable-line

  return { items, loading, hasMore, sentinelRef, refresh: () => { setPage(1); setItems([]); setHasMore(true); } };
}

8.3 useCart(购物车 Hook)

typescript 复制代码
// hooks/useCart.ts
import { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '@/store';
import {
  addToCart,
  removeFromCart,
  updateQuantity,
  toggleCheck,
  toggleCheckAll,
  selectCartItems,
  selectTotalPrice,
  selectTotalCount,
} from '@/store/cartSlice';

export function useCart() {
  const dispatch = useDispatch();
  const items = useSelector(selectCartItems);
  const totalPrice = useSelector(selectTotalPrice);
  const totalCount = useSelector(selectTotalCount);

  const add = useCallback(
    (product: { productId: string; title: string; price: number; image: string }) =>
      dispatch(addToCart(product)),
    [dispatch]
  );

  const remove = useCallback(
    (productId: string) => dispatch(removeFromCart(productId)),
    [dispatch]
  );

  const updateQty = useCallback(
    (productId: string, quantity: number) =>
      dispatch(updateQuantity({ productId, quantity })),
    [dispatch]
  );

  const toggle = useCallback(
    (productId: string) => dispatch(toggleCheck(productId)),
    [dispatch]
  );

  const toggleAll = useCallback(() => dispatch(toggleCheckAll()), [dispatch]);

  return {
    items,
    totalPrice,
    totalCount,
    add,
    remove,
    updateQty,
    toggle,
    toggleAll,
  };
}

8.4 useAuth(认证 Hook)

typescript 复制代码
// hooks/useAuth.ts
import { useState, useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';

interface UserInfo {
  userId: string;
  nickname: string;
  avatar: string;
  phone: string;
}

export function useAuth() {
  const [user, setUser] = useState<UserInfo | null>(null);
  const [loading, setLoading] = useState(true);
  const navigate = useNavigate();

  // 初始化:检查登录态
  useEffect(() => {
    checkAuth();
  }, []);

  const checkAuth = async () => {
    try {
      const res = await fetch('/api/auth/check', { credentials: 'include' });
      if (res.ok) {
        const data = await res.json();
        setUser(data.user);
      }
    } catch {
      setUser(null);
    } finally {
      setLoading(false);
    }
  };

  const login = useCallback(async (phone: string, code: string) => {
    const res = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ phone, code }),
      credentials: 'include',
    });
    if (res.ok) {
      const data = await res.json();
      setUser(data.user);
      return true;
    }
    return false;
  }, []);

  const logout = useCallback(async () => {
    await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' });
    setUser(null);
    navigate('/');
  }, [navigate]);

  const requireAuth = useCallback(() => {
    if (!user) {
      navigate('/login?redirect=' + encodeURIComponent(location.pathname));
      return false;
    }
    return true;
  }, [user, navigate]);

  return { user, loading, login, logout, requireAuth, isAuthenticated: !!user };
}

8.5 useSearchParams(搜索参数同步 Hook)

typescript 复制代码
// hooks/useFilterSearchParams.ts
import { useCallback, useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';

interface FilterParams {
  q?: string;
  category?: string;
  minPrice?: number;
  maxPrice?: number;
  sort?: string;
  page?: number;
}

export function useFilterSearchParams() {
  const [searchParams, setSearchParams] = useSearchParams();

  const filters: FilterParams = useMemo(
    () => ({
      q: searchParams.get('q') || undefined,
      category: searchParams.get('category') || undefined,
      minPrice: searchParams.get('minPrice')
        ? Number(searchParams.get('minPrice'))
        : undefined,
      maxPrice: searchParams.get('maxPrice')
        ? Number(searchParams.get('maxPrice'))
        : undefined,
      sort: searchParams.get('sort') || undefined,
      page: Number(searchParams.get('page')) || 1,
    }),
    [searchParams]
  );

  const setFilter = useCallback(
    (updates: Partial<FilterParams>) => {
      setSearchParams(
        (prev) => {
          Object.entries(updates).forEach(([key, value]) => {
            if (value === undefined || value === '' || value === 1) {
              prev.delete(key);
            } else {
              prev.set(key, String(value));
            }
          });
          // 非页码变化时重置页码
          if (!('page' in updates)) {
            prev.set('page', '1');
          }
          return prev;
        },
        { replace: true }
      );
    },
    [setSearchParams]
  );

  const resetFilters = useCallback(() => {
    setSearchParams({}, { replace: true });
  }, [setSearchParams]);

  return { filters, setFilter, resetFilters, searchParams };
}

九、总结:架构选型建议

场景 推荐方案 理由
微前端框架 qiankun(阿里系)/ MicroApp(京东系) 成熟稳定,大厂生产验证
SSR 方案 Umi SSR(阿里)/ Next.js / 自研 SEO + 首屏性能
状态管理 Redux Toolkit(大型)/ Zustand(中小型) 生态完善 vs 轻量灵活
跨端方案 Taro(京东)/ React2X(美团) 一套代码多端运行
列表渲染 react-window + IntersectionObserver 虚拟滚动 + 无限加载
代码分割 React.lazy + Suspense + 路由级分割 减少首屏包体积
缓存策略 SWR / React Query + 请求级缓存 自动缓存重验证

Sources:

  1. 电商平台微前端改造:qiankun实战案例解析 - CSDN
  2. jx前端架构学习(京东京喜)- CSDN
  3. 微前端框架选择之阿里vs京东 - CSDN
  4. 美团技术栈(2024-2025)- CSDN文库
  5. 美团React2X跨容器研发框架 - 美团技术团队
  6. Umi框架深度解析 - CSDN(https://blog.csdn.net

Now let me generate the hero image for the blog, then write the full HTML content.

Now let me write the complete blog HTML:

博客已完成,共 12 个章节,涵盖 React 19 全部核心 Hooks、新增 API、自定义 Hooks 封装,以及阿里/京东/美团/拼多多的电商架构实战。所有代码示例均附带电商场景说明,包含 15 个权威来源引用。

查看博客

内容概览:

章节 内容
核心 Hooks useState/useEffect/useContext/useReducer/useRef/useMemo/useCallback + 电商场景代码
React 19 新增 useActionState/useFormStatus/useOptimistic/useTransition/useDeferredValue + 对比表
新增 API use() Hook、Server Components、Server Actions、ref 作为 prop
自定义 Hooks useDebounce/useLocalStorage/useInfiniteScroll/useAuth 完整实现
大厂架构 阿里 qiankun / 京东 MicroApp / 美团 React2X + 混合渲染策略表
电商实战 虚拟滚动、图片懒加载、购物车(RTK vs Zustand)、搜索防抖+URL同步、订单支付
性能优化 路由级/组件级代码分割、请求缓存、微前端共享依赖
工具链选型 状态管理/数据请求/UI组件库/SSR/构建工具/测试 全面对比表
推荐方案 三套技术栈:SSR全栈 / SPA快速开发 / 大型企业迁移