前端架构与设计模式
架构是「变化」的管理,设计模式是「常见问题」的复用解法。前端领域的架构演进本质是对「状态 → UI」映射关系的不断优化。
1. 架构模式
1.1 MVC vs MVP vs MVVM
MVC(Model-View-Controller)
┌─────┐ 通知变化 ┌──────┐ 用户操作 ┌──────────┐
│View │ ◄──────────── │Model │ ◄──────────── │Controller│
│ │ │ │ │ │
│ │ ────→触发───→ │ │ │ ┌──────┐ │
└─────┘ Controller └──────┘ │ │ 逻辑 │ │
└──────────┘
View 直接从 Model 获取数据(View ↔ Model 双向)
Controller 处理用户输入、更新 Model
View 监听 Model 变化自动更新
前端实现(Backbone.js):
// Model
const Todo = Backbone.Model.extend({
defaults: { title: '', completed: false },
});
// View
const TodoView = Backbone.View.extend({
initialize() {
this.model.on('change', this.render, this); // View 直接监听 Model
},
template: _.template('<%= title %>'),
render() { this.$el.html(this.template(this.model.toJSON())); },
events: { 'click .toggle': 'toggle' },
toggle() { this.model.set('completed', !this.model.get('completed')); },
});
问题:
- View 和 Model 直接耦合,难以单元测试
- Controller 逻辑容易膨胀(Massive ViewController)
- View 需要手动管理 Model 的事件订阅
MVP(Model-View-Presenter)
┌──────┐ 通信接口 ┌───────────┐ 更新 ┌───────┐
│ View │ ◄─────────── │Presenter │ ←────────── │ Model │
│(被动)│ (interface) │ │ │ │
│ │ ────→事件───→ │ 所有逻辑 │ ───→读取───→ │ │
└──────┘ └───────────┘ └───────┘
View 完全被动,不直接访问 Model
Presenter 持有 View 接口和 Model 引用
双向通过接口通信(测试友好)
// 接口定义
class TodoViewInterface {
render(todos) {}
showError(msg) {}
}
// Presenter 纯逻辑,可测试
class TodoPresenter {
constructor(view, model) {
this.view = view;
this.model = model;
}
async onSearch(query) {
try {
const todos = await this.model.search(query);
this.view.render(todos);
} catch (e) {
this.view.showError(e.message);
}
}
}
// 测试
const mockView = new MockTodoView();
const presenter = new TodoPresenter(mockView, mockModel);
前端适用场景:逻辑复杂的页面(表格/表单编排),测试是核心需求时。
MVVM(Model-View-ViewModel)
┌──────┐ Data Binding ┌───────────┐ 请求/通知 ┌───────┐
│ View │ ◄───────────── │ ViewModel │ ←────────── │ Model │
│ (DOM)│ 双向绑定/声明式 │ (状态) │ │ │
│ │ ────→用户操作──→ │ │ ────→变更───→│ │
└──────┘ └───────────┘ └───────┘
View = 模板 + 指令(HTML)
ViewModel = 响应式状态 + 计算属性 + 方法
Data Binding 层自动同步 View ↔ ViewModel
前端实现(Vue/React):
// Vue(MVVM 典型实现)
const vm = new Vue({
el: '#app',
data: { // ViewModel
todos: [],
loading: false,
},
computed: { // 计算属性
activeTodos() { return this.todos.filter(t => !t.done); },
},
methods: {
async fetchTodos() {
this.loading = true;
this.todos = await api.getTodos(); // 改变数据 → 自动驱动 View 更新
this.loading = false;
},
},
});
三者的核心区别:
| 模式 | View 状态 | View 是否引用 Model | 测试策略 | 前端典型 |
|---|---|---|---|---|
| MVC | 被动(从 Model 读取) | 是 | 测试 Model + Controller | Backbone |
| MVP | 被动(由 Presenter 填充) | 否(通过接口) | 测试 Presenter(Mock View) | GWT |
| MVVM | 自动同步(Data Binding) | 否 | 测试 ViewModel 纯函数 | Vue/React |
1.2 Flux / Redux 单向数据流
Flux 动机:MVC 的双向数据流在大型应用中不可预测(Model 更新 View → View 触发 Controller → Controller 更新另一个 Model → 连锁反应)。
Flux 架构:
┌──────┐ Action ┌──────────┐ ┌──────────┐ ┌──────┐
│ View │ ───────→ │ Dispatcher│ → │ Store │ → │ View │
│ │ ←────────│ (单例) │ │ (状态树) │ │ │
└──────┘ State └──────────┘ └──────────┘ └──────┘
Redux 三原则:
- 单一数据源:整个应用的状态存储在单⼀ Store 中
- State 是只读的:唯一改变状态的方式是触发 Action
- 纯函数修改:Reducer 是纯函数,
(prevState, action) => newState
// Redux 完整流程
// 1. Action
const ADD_TODO = 'ADD_TODO';
const addTodo = (text) => ({ type: ADD_TODO, payload: { text } });
// 2. Reducer(纯函数)
const todosReducer = (state = [], action) => {
switch (action.type) {
case ADD_TODO:
return [...state, { id: Date.now(), text: action.payload.text, done: false }];
default:
return state;
}
};
// 3. Store
const store = createStore(todosReducer);
// 4. Dispatch
store.dispatch(addTodo('Learn Redux'));
// 5. Subscribe
store.subscribe(() => console.log(store.getState()));
单向数据流的价值:
- 可预测:给定相同的 Action 序列,永远得到相同的最终状态
- 可追溯:DevTools 实现时间旅行(Actions 回放/撤销)
- 可测试:Reducer 是纯函数,不需要 Mock
- 并行开发:组件只关心 dispatch Action,Store 结构统一
1.3 洋葱架构(Clean Architecture)
┌──────────────┐
│ UI Layer │ ← 框架层(React/Vue 组件)
└──────┬───────┘
│ 调用
┌──────▼───────┐
│ Application │ ← 用例层(Use Case)
│ Services │
└──────┬───────┘
│ 调用
┌──────▼───────┐
│ Domain │ ← 核心业务逻辑(与框架无关)
│ Entities │
└──────┬───────┘
│ 接口
┌──────▼───────┐
│ Infrastructure│ ← 外部适配(DB/API/文件)
└──────────────┘
依赖规则:源码依赖只能从外向内,内部层不能知道外部层的存在。
// Domain 层(纯业务逻辑,无框架引用)
class OrderEntity {
constructor(items) {
this.items = items;
this.status = 'pending';
}
canCancel() {
return this.status === 'pending' && !this.isExpired();
}
isExpired() {
return Date.now() - this.createdAt > 30 * 60 * 1000; // 30min
}
}
// Application 层
class CancelOrderUseCase {
constructor(orderRepo, paymentGateway) {
this.orderRepo = orderRepo;
this.paymentGateway = paymentGateway;
}
async execute(orderId) {
const order = await this.orderRepo.findById(orderId);
if (!order.canCancel()) throw new Error('Cannot cancel order');
await this.paymentGateway.refund(order);
await this.orderRepo.save(order);
}
}
// Infrastructure 层(实现接口)
class PostgresOrderRepository {
async findById(id) {
const row = await db.query('SELECT * FROM orders WHERE id = $1', [id]);
return new OrderEntity(row.items); // 适配 Domain
}
}
1.4 六边形架构(端口适配器)
┌──────────┐
HTTP API ───→ │ Port │ ←─── Frontend
WebSocket ───→ │ (Inbound)│ ←─── CLI
└────┬─────┘
│
┌──────────▼──────────┐
│ Application Core │
│ (Domain Logic) │
└──────────┬──────────┘
│
┌──────────────────▼──────────────────┐
│ Port │
│ (Outbound) │
└────┬──────────┬──────────┬──────────┘
│ │ │
┌─────▼──┐ ┌────▼───┐ ┌───▼─────┐
│ MySQL │ │ Redis │ │ S3 │
│ Adapter │ │ Adapter │ │ Adapter │
└────────┘ └────────┘ └─────────┘
关键设计:
- 核心业务逻辑只依赖端口接口(抽象),不依赖任何具体适配器
- 端口适配器可以随时替换(MySQL → PostgreSQL,S3 → MinIO)
- 非常适合微服务和 BFF 层
2. 常见设计模式
2.1 创建型
单例(Singleton)
// ES Module 天然单例(模块只执行一次)
// config.js
export const config = {
apiUrl: 'https://api.example.com',
};
// 任何地方 import 得到的都是同一份引用
import { config } from './config.js';
// 手动实现(类式)
class Cache {
static #instance;
#store = new Map();
static getInstance() {
if (!Cache.#instance) {
Cache.#instance = new Cache();
}
return Cache.#instance;
}
get(key) { return this.#store.get(key); }
set(key, value) { this.#store.set(key, value); }
}
前端场景:全局状态 Store、缓存、日志、请求实例(axios)。
工厂模式
// 简单工厂
class ComponentFactory {
static create(type, props) {
switch (type) {
case 'button': return new Button(props);
case 'input': return new Input(props);
case 'select': return new Select(props);
default: throw new Error(`Unknown type: ${type}`);
}
}
}
// React 中的工厂模式应用
function createFieldRenderer(type) {
const fieldMap = {
text: TextField,
number: NumberField,
date: DatePicker,
select: SelectField,
};
return fieldMap[type] || FallbackField;
}
建造者模式
// 链式建造器
class QueryBuilder {
constructor() { this._query = {}; }
select(...fields) {
this._query.select = fields;
return this;
}
from(table) {
this._query.from = table;
return this;
}
where(condition) {
this._query.where = condition;
return this;
}
build() {
return this._query; // 或生成 SQL 字符串
}
}
const query = new QueryBuilder()
.select('id', 'name', 'email')
.from('users')
.where({ age: { $gt: 18 } })
.build();
2.2 结构型
适配器(Adapter)
// 旧接口
class LegacyAPI {
getUserXML(id) { return `<user><id>${id}</id></user>`; }
}
// 新接口期望
interface IUserAPI {
getUserJSON(id): { id: number };
}
// 适配器
class UserAPIAdapter {
constructor() { this.legacy = new LegacyAPI(); }
getUserJSON(id) {
const xml = this.legacy.getUserXML(id);
// 解析 XML → JSON
return this.parseXML(xml);
}
}
前端场景:统一不同后端 API 响应格式、微信/支付宝支付接口适配。
装饰器(Decorator)
// ES Stage 3 Decorator(TypeScript)
function log(target, propertyKey, descriptor) {
const original = descriptor.value;
descriptor.value = function(...args) {
console.log(`[${propertyKey}] called with`, args);
const result = original.apply(this, args);
console.log(`[${propertyKey}] returned`, result);
return result;
};
return descriptor;
}
class Calculator {
@log
add(a, b) { return a + b; }
}
// React HOC(高阶组件,装饰器模式的变体)
function withAuth(Component) {
return function AuthenticatedComponent(props) {
const user = useAuth();
if (!user) return <LoginRedirect />;
return <Component {...props} user={user} />;
};
}
const AdminPanel = withAuth(AdminDashboard);
代理(Proxy)
// ES Proxy(拦截对象操作)
const handler = {
get(target, prop) {
if (prop in target) {
console.log(`Accessing ${prop}`);
return target[prop];
}
console.warn(`Property ${prop} not found`);
return undefined;
},
set(target, prop, value) {
if (prop === 'age' && typeof value !== 'number') {
throw new TypeError('age must be a number');
}
target[prop] = value;
return true; // 表示设置成功
},
};
const user = new Proxy({ name: 'Alice' }, handler);
Vue3 响应式核心:
// Vue3 reactive 的实现简化
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key); // 依赖收集
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, key); // 触发更新
}
return result;
},
});
}
组合模式(Composite)
<!-- React 中的组合模式 -->
<Menu>
<MenuItem>
<SubMenu title="File">
<MenuItem>New</MenuItem>
<MenuItem>Save</MenuItem>
</SubMenu>
</MenuItem>
</Menu>
// 组合模式:树形结构统一处理
class TreeNode {
constructor(name) { this.name = name; this.children = []; }
add(child) { this.children.push(child); }
render() {
return `<li>${this.name}<ul>${this.children.map(c => c.render()).join('')}</ul></li>`;
}
}
2.3 行为型
观察者模式(发布订阅)
// EventEmitter 实现
class EventEmitter {
#events = new Map();
on(event, listener) {
if (!this.#events.has(event)) this.#events.set(event, []);
this.#events.get(event).push(listener);
return () => this.off(event, listener);
}
off(event, listener) {
const listeners = this.#events.get(event);
if (listeners) {
this.#events.set(event, listeners.filter(l => l !== listener));
}
}
emit(event, ...args) {
const listeners = this.#events.get(event);
listeners?.forEach(l => l(...args));
}
once(event, listener) {
const wrapper = (...args) => {
listener(...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
}
前端应用:
window.addEventListener/element.addEventListener- Vue
$emit/$on - React 自定义事件的跨组件通信
策略模式
// 策略模式:用函数/对象替代 if-else 链
const validators = {
required: (value) => value != null && value !== '' ? null : '必填项',
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? null : '邮箱格式错误',
minLength: (min) => (value) =>
value.length >= min ? null : `最少 ${min} 个字符`,
phone: (value) => /^1[3-9]\d{9}$/.test(value) ? null : '手机号格式错误',
};
// 使用
const rules = [validators.required, validators.email];
const errors = rules.map(rule => rule(input)).filter(Boolean);
策略模式 vs if-else:
- 策略模式将算法封装为独立单元,可单独测试和复用
- 新策略无需修改原有代码(开闭原则)
- if-else 修改需改动函数体,违反开闭原则
状态模式
// 有限状态机
class Order {
constructor() { this.state = new PendingState(this); }
next() { this.state.next(); }
cancel() { this.state.cancel(); }
getStatus() { return this.state.status; }
}
class PendingState {
constructor(order) { this.order = order; this.status = 'pending'; }
next() { this.order.state = new PaidState(this.order); }
cancel() { this.order.state = new CancelledState(this.order); }
}
class PaidState {
constructor(order) { this.order = order; this.status = 'paid'; }
next() { this.order.state = new ShippedState(this.order); }
cancel() { throw new Error('已付款订单不能取消'); }
}
class CancelledState {
constructor(order) { this.order = order; this.status = 'cancelled'; }
next() { throw new Error('已取消订单不能继续'); }
cancel() { throw new Error('已取消'); }
}
// 使用
const order = new Order();
order.next(); // pending → paid
order.next(); // paid → shipped
XState 状态图:工业级状态机库,支持嵌套状态、并行状态、守卫条件。
3. 前端特有模式
3.1 HOC / Render Props / 自定义 Hook
三种逻辑复用模式的演进:
// 1. HOC(高阶组件)- 类组件时代
function withLogging(WrappedComponent) {
return class extends React.Component {
componentDidMount() {
console.log(`Mounted ${WrappedComponent.name}`);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
// HOC 问题:层层嵌套 → 难以追踪(Wrapper Hell)
const EnhancedComponent = withAuth(withLogging(withTheme(MyComponent)));
// 2. Render Props(渲染属性)
class MouseTracker extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (e) => this.setState({ x: e.clientX, y: e.clientY });
render() {
return <div onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)} {/* 通过 render prop 传递状态 */}
</div>;
}
}
// 使用
<MouseTracker render={({ x, y }) => <h1>Mouse: {x}, {y}</h1>} />
// 3. 自定义 Hook(函数组件时代)- 最佳方案
function useMousePosition() {
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
const handler = (e) => setPos({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handler);
return () => window.removeEventListener('mousemove', handler);
}, []);
return pos;
}
// 使用(扁平,无嵌套)
function MyComponent() {
const { x, y } = useMousePosition();
return <h1>Mouse: {x}, {y}</h1>;
}
HOC 层层嵌套问题的本质:HOC 在 React 中返回新的类组件,嵌套时创建多层组件树,调试困难、props 隐式传递(withAuth(MyComponent) 传入的 user props 可能和其他 HOC 冲突)。Hook 通过扁平函数调用解决了这个问题。
3.2 容器组件 vs 展示组件
Container(聪明组件) Presentational(傻瓜组件)
┌────────────────────────┐ ┌──────────────────────────┐
│ - 管理状态和逻辑 │ │ - 只负责渲染 │
│ - 调用 API/Store │ props │ - 通过 props 接收数据和回调 │
│ - 提供数据和方法给子组件 │ ──────→ │ - 本身无状态或只有 UI 状态 │
│ - 可复用性低 │ │ - 可复用性高(纯 UI) │
└────────────────────────┘ └──────────────────────────┘
// Container
function TodoContainer() {
const [todos, setTodos] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchTodos().then(data => { setTodos(data); setLoading(false); });
}, []);
const toggle = async (id) => {
await toggleTodo(id);
setTodos(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t));
};
return <TodoList todos={todos} loading={loading} onToggle={toggle} />;
}
// Presentational(纯 UI,可脱离业务场景复用)
function TodoList({ todos, loading, onToggle }) {
if (loading) return <Skeleton />;
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<Checkbox checked={todo.done} onChange={() => onToggle(todo.id)} />
<span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
{todo.text}
</span>
</li>
))}
</ul>
);
}
3.3 Compound Components(复合组件)
// React 复合组件
import { createContext, useContext } from 'react';
const ToggleContext = createContext();
function Toggle({ children, defaultState = false }) {
const [on, setOn] = useState(defaultState);
const toggle = () => setOn(prev => !prev);
return (
<ToggleContext.Provider value={{ on, toggle }}>
{children}
</ToggleContext.Provider>
);
}
Toggle.On = function On({ children }) {
const { on } = useContext(ToggleContext);
return on ? children : null;
};
Toggle.Off = function Off({ children }) {
const { on } = useContext(ToggleContext);
return on ? null : children;
};
Toggle.Button = function Button() {
const { on, toggle } = useContext(ToggleContext);
return <button onClick={toggle}>{on ? 'ON' : 'OFF'}</button>;
};
// 使用
<Toggle>
<Toggle.On>The switch is on</Toggle.On>
<Toggle.Off>The switch is off</Toggle.Off>
<Toggle.Button />
</Toggle>
Vue 复合组件:
<template>
<Select v-model="selected">
<SelectOption value="a">Option A</SelectOption>
<SelectOption value="b">Option B</SelectOption>
</Select>
</template>
<script setup>
// Select.vue 使用 provide/inject 实现隐式状态共享
provide('selectValue', selected);
provide('selectOnChange', (val) => { selected.value = val; });
</script>
3.4 Provider 模式(依赖注入)
// React Context(前端依赖注入的标准实现)
const ThemeContext = createContext('light');
const UserContext = createContext(null);
const I18nContext = createContext({ locale: 'zh-CN' });
function App() {
return (
<ThemeContext.Provider value="dark">
<UserContext.Provider value={{ id: 1, name: 'Alice' }}>
<I18nContext.Provider value={{ locale: 'en' }}>
<MainLayout />
</I18nContext.Provider>
</UserContext.Provider>
</ThemeContext.Provider>
);
}
// 任意深度的子组件
function ProfileCard() {
const theme = useContext(ThemeContext); // "dark"
const user = useContext(UserContext); // { id: 1, name: 'Alice' }
// ...
}
Provider 联级:多个 Provider 嵌套时,useContext 获取最近的 Provider 值。
3.5 Slot 模式
<!-- Vue 插槽 -->
<template>
<Card>
<template #header>
<h2>Card Title</h2> <!-- 具名插槽 -->
</template>
<p>Main content here</p> <!-- 默认插槽 -->
<template #footer="{ user }">
<span>Created by {{ user.name }}</span> <!-- 作用域插槽,子传父 -->
</template>
</Card>
</template>
<!-- Card 组件 -->
<div class="card">
<div class="header"><slot name="header" /></div>
<div class="body"><slot /></div>
<div class="footer"><slot name="footer" :user="user" /></div>
</div>
React 等价:
function Card({ header, children, footer }) {
return (
<div className="card">
<div className="header">{header}</div>
<div className="body">{children}</div>
<div className="footer">{footer({ user })}</div>
</div>
);
}
4. 架构实践
4.1 组件分层
src/components/
├── base/ # 基础组件(Button, Input, Select, Modal)
│ 只依赖 UI 规范,无业务逻辑
├── business/ # 业务组件(UserCard, OrderList, PaymentForm)
│ 组装基础组件,引入领域模型
├── page/ # 页面组件(HomePage, CheckoutPage, UserCenter)
│ 页面级编排,路由入口
└── layout/ # 布局组件(Header, Sidebar, Footer)
应用框架布局
分层原则:
- 下层不依赖上层:
base/不能 importbusiness/的组件 - 同一层之间可以互相组合:
business/UserCard可以使用business/Avatar - 数据请求只出现在
page/层或自定义 Hook 中
4.2 逻辑复用策略
| 复用粒度 | 方案 | 适用场景 |
|---|---|---|
| 工具函数 | 纯函数 + 导出 | 日期格式化、数据转换 |
| 组件逻辑 | 自定义 Hook | 状态逻辑、副作用 |
| 组件 UI | 组件组合 / Slot | 通用的 UI 呈现 |
| 跨页面流程 | HOC / Provider | 鉴权、埋点、主题 |
| 跨应用 | Monorepo 共享包 | UI 库、API 客户端 |
4.3 目录结构设计
按功能组织(Feature-based):
src/
├── features/
│ ├── auth/
│ │ ├── components/ # 登录/注册组件
│ │ ├── hooks/ # useAuth, usePermission
│ │ ├── api.ts # auth API 调用
│ │ ├── types.ts # 类型定义
│ │ └── index.ts # 对外暴露
│ ├── order/
│ │ └── ...
│ └── payment/
│ └── ...
├── shared/ # 跨功能共享
│ ├── components/ # 基础 UI 组件
│ ├── utils/ # 工具函数
│ └── api/ # 通用请求封装
└── app/ # 应用入口、路由、全局状态
按类型组织(Type-based):
src/
├── components/ # 所有组件
├── hooks/ # 所有 Hook
├── services/ # 所有 API 调用
├── store/ # 所有状态管理
├── utils/ # 所有工具函数
└── types/ # 所有类型定义
| 组织方式 | 优点 | 缺点 |
|---|---|---|
| 按功能 | 内聚性高,功能移除时删除整个目录 | 跨功能复用需要抽出 shared |
| 按类型 | 简单直观,适合小项目 | 功能散落在多个目录,大型项目维护成本高 |
推荐:中大型项目按功能组织,小型项目按类型组织。
4.4 BFF(Backend For Frontend)层
解决的问题:
- 前端需要一个接口返回的字段和格式与后端微服务 API 不同
- 多个后端微服务的数据需要聚合
- 后端返回了大量前端不需要的字段(网络传输浪费)
- 协议转换(REST → GraphQL、gRPC → REST)
┌──────┐ HTTP/JSON ┌──────────┐ gRPC/Thrift ┌───────────┐
│ Frontend │ ──────→ │ BFF │ ─────────────→ │ Microservice A│
│ │ │ (Node.js)│ ├───────────┤
│ │ │ │ HTTP/REST │ Microservice B│
│ │ │ ┌──────┐ │ ─────────────→ ├───────────┤
│ │ │ │数据聚合│ │ │ Microservice C│
│ │ │ └──────┘ │ └───────────┘
└──────────┘ └──────────┘
// BFF 中间层示例(Node.js)
app.get('/api/user/profile', async (req, res) => {
const [user, orders, points] = await Promise.all([
userService.getById(req.userId), // gRPC → User Service
orderService.listByUser(req.userId), // gRPC → Order Service
pointsService.getBalance(req.userId), // REST → Points Service
]);
// 数据聚合 + 字段裁剪
res.json({
id: user.id,
name: user.name,
avatar: user.avatar,
orderCount: orders.length,
recentOrder: orders[0]?.productName,
points: points.balance,
vipLevel: user.vipLevel,
});
});
BFF 的好处:
- 前端无需知道后端微服务架构细节
- 按端定制(移动端 BFF 和桌面端 BFF 可不同)
- 减少前端网络请求数(N 个微服务请求 → 1 个 BFF 请求)
- 轻量聚合逻辑在 BFF 层完成
4.5 GraphQL
适用场景:当数据需求多样(不同页面需要不同的字段集合)、前后端频繁协商接口时。
# Schema 定义
type User {
id: ID!
name: String!
avatar: String
orders: [Order!]!
}
type Order {
id: ID!
productName: String!
amount: Float!
status: OrderStatus!
}
# 查询
query UserProfile($id: ID!) {
user(id: $id) {
name
avatar
recentOrders: orders(first: 3) {
productName
amount
}
}
}
GraphQL vs REST BFF:
| 维度 | REST BFF | GraphQL |
|---|---|---|
| 字段裁剪 | 每个端独立部署 BFF | 查询方自由选择字段 |
| 聚合逻辑 | BFF 代码写死 | Resolver 编排 |
| 缓存 | HTTP 缓存天然支持(URL 级别) | 需要额外实现(DataLoader + 持久化) |
| 学习曲线 | 低 | 中 |
| 性能开销 | 低 | 需要处理 N+1 问题(DataLoader 解决) |
| 类型安全 | 手动定义 | Schema + Codegen 自动推导 |
选型建议:
- 前端简单、API 接口稳定的项目 → REST + 简单 BFF 即可
- 多端(Web/iOS/Android)数据需求差异大 → GraphQL 优势明显
- 后端微服务众多且持续演进 → BFF 或 GraphQL 均可,取决于团队对 GraphQL 的掌握