一、各厂技术栈概览
| 公司 | 核心框架 | 微前端方案 | 跨端方案 | 状态管理 | 组件库 |
|---|---|---|---|---|---|
| 阿里巴巴/蚂蚁 | 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-window或react-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:
- 电商平台微前端改造:qiankun实战案例解析 - CSDN
- jx前端架构学习(京东京喜)- CSDN
- 微前端框架选择之阿里vs京东 - CSDN
- 美团技术栈(2024-2025)- CSDN文库
- 美团React2X跨容器研发框架 - 美团技术团队
- 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快速开发 / 大型企业迁移 |