小星前端实战录头像

第 11 章 项目最佳实践与进阶

📌 本章目标:总结项目中的设计模式和最佳实践,帮你把知识系统化

⭐⭐⭐⭐ 难度:进阶 | ⏱️ 预计时间:1.5 小时


11.1 项目中用到的设计模式

设计模式不是「八股文」,而是解决特定问题的「套路」。这个项目里用到了很多经典的设计模式,我们来盘点一下。

门面模式(Facade Pattern)

是什么:对外提供一个统一的大接口,内部封装复杂的子系统。

用在哪里:网关层(Gateway)

上层代码
    │
    ▼
┌─────────────┐
│   Gateway   │  ← 统一的大接口
│  getAgents  │
│  sendMessage│
│  ...        │
└──────┬──────┘
       │
  ┌────┴────┐
  ▼         ▼
 HTTP      WS

为什么好:

  • 上层不用关心底层是 HTTP 还是 WS
  • 换底层实现不影响上层
  • 所有数据格式转换集中在网关层做

工厂模式(Factory Pattern)

是什么:用一个工厂函数/类来创建对象,调用者不用知道具体实现。

用在哪里:createGateway() 工厂函数

function createGateway(): WorkbenchGateway {
  if (useMock) return new MockGateway();
  return new HttpGateway();
}

为什么好:

  • 创建逻辑集中,要改只改一个地方
  • 调用者只依赖接口,不依赖具体类
  • 方便加新的实现

观察者模式(Observer Pattern)

是什么:一个对象变化了,通知所有订阅了它的对象。

用在哪里:MobX 的 observable + reaction、WebSocket 的事件监听

observable 数据变了
    │
    ▼
所有用到它的组件自动更新

为什么好:

  • 数据和视图解耦
  • 不用手动调「刷新」方法
  • 细粒度更新,性能好

策略模式(Strategy Pattern)

是什么:定义一族算法,把每个算法封装起来,使它们可以互相替换。

用在哪里:双 WS 通道策略(chat / work-order)

function getPrimaryWs() {
  if (transport === 'chat') return chatWs;
  return workOrderWs;
}

为什么好:

  • 新增策略不用改原有逻辑
  • 运行时可以动态切换
  • 每个策略独立,互不干扰

状态模式(State Pattern)

是什么:对象的行为取决于它的状态,状态变了,行为也变。

用在哪里:消息生命周期(pending → sending → sent / error)、流式回复阶段

用户消息的状态:
pending → sending → sent
                ↘ error

为什么好:

  • 状态转换逻辑清晰
  • 每个状态下能干什么不能干什么一目了然
  • 避免「这个状态下居然能点这个按钮」的 bug

备忘录模式(Memento Pattern)

是什么:保存对象的状态,以后可以恢复。

用在哪里:执行日志缓存(切换会话时恢复最后一次的执行面板)

// 缓存
runCache.set(runId, events);

// 恢复
events = runCache.get(runId);

为什么好:

  • 用户切换回来时,之前的状态还在
  • 不用每次都重新加载
  • 体验更连贯

11.2 MobX 使用最佳实践

✅ 推荐做法

1. 用 makeAutoObservable

最省事、最不容易出错。99% 的场景用它就够了。

class Store {
  count = 0;
  constructor() {
    makeAutoObservable(this); // 自动识别
  }
}
2. 细粒度 observable

数据拆分细一点,更新的时候只有变化的部分触发渲染。

// ✅ 好:每个消息独立,改一个只重渲染一个
messages: Message[] = [];

// ❌ 不好:整个对象替换,所有消息都重渲染
setMessages(newMessages);
3. 多用 computed

能从现有数据算出来的,就用 computed,不要手动维护。

// ✅ 好:自动计算、自动缓存
get completedCount() {
  return this.todos.filter(t => t.done).length;
}

// ❌ 不好:每次改 todos 都要手动更新这个数
completedCount = 0;
4. 异步操作包 runInAction

异步回调里改状态,用 runInAction 包一下。

async loadData() {
  const data = await api.fetch();
  runInAction(() => {
    this.data = data;
    this.loading = false;
  });
}

❌ 避免的做法

1. 不要在组件里直接改 observable
// ❌ 不好:业务逻辑散落在组件里
<button onClick={() => store.count++}>

// ✅ 好:通过 action 方法改
<button onClick={() => store.increment()}>
2. 不要把所有东西塞一个 Store

一个 Store 超过 500 行就要考虑拆分了。

3. 不要滥用 reaction/autorun

大部分场景用 computed + observer 就够了。reaction 是应急手段,不是常规武器。


11.3 实时通信最佳实践

WebSocket 必做的 5 件事

功能为什么要做怎么做
自动重连网络波动是常态,不能断了就断了指数退避,最多 N 次
心跳保活检测连接是不是假死定时发 ping,超时就断了重连
消息队列连接没建立好的时候发的消息不能丢存队列里,连上再发
重连后同步断连期间的消息要补上重连后拉取最新状态
错误提示断了要告诉用户Toast 提示 + 重连中状态

流式回复的经验

1. 本地回显一定要做

发完消息立即显示,别等后端返回。慢个 100ms 体验差很多。

2. 始终以最终内容为准

流式增量可能会有误差(比如后端做了修改),end 事件里的最终内容才是准的。

// delta 阶段:追加
appendAssistantDelta(messageId, delta);

// end 阶段:以最终内容为准
setAssistantFinalContent(messageId, finalContent);
3. 后台会话不渲染

用户没在看的会话,只更新未读数,不加载消息内容。不然几十上百个会话一起流式更新,页面会卡。

if (currentConversationId !== event.conversationId) {
  // 后台会话:只标记未读
  markUnread(event.conversationId);
  return;
}
// 前台会话:更新消息
appendMessage(event.message);

11.4 组件设计最佳实践

容器组件 vs 展示组件

类型职责接触什么
容器组件数据获取、业务逻辑Store、网关
展示组件只负责渲染和交互Props、本地 state
// 容器组件:管数据
const MessageListContainer = observer(() => {
  const { sessionStore } = useStore();
  return <MessageList messages={sessionStore.messages} />;
});

// 展示组件:只管渲染
function MessageList({ messages }) {
  return messages.map(msg => <MessageBubble msg={msg} />);
}

💡 小贴士:小项目不用分这么细,但大项目分清楚了维护起来省心。

组件粒度怎么把握

情况建议
一个组件超过 400 行考虑拆分子组件
一段 JSX 有自己的逻辑和状态拆成独立组件
两个地方用到了同样的 UI抽成公共组件
拆了之后 props 要传七八个可能拆太细了,合回去

一句话:拆到「每个组件做一件事,而且名字能准确描述它做什么」就差不多了。

优先复用 UI 库组件

能用 Ant Design 的组件就别自己写:

  • 按钮、弹窗、表单、表格… 直接用
  • 样式不满足?用 className 覆盖或 theme 定制
  • 功能不满足?先看看 Pro 组件有没有

自己写一个「看起来差不多」的组件,后期维护成本很高。


11.5 性能优化要点

前端性能优化的优先级

先做见效快的,再做费力的。

优先级优化手段难度收益
🔴 高后台会话不渲染消息低高
🔴 高代码分割 + 按需加载中高
🟡 中MobX 细粒度更新低中
🟡 中长列表虚拟滚动中中
🟢 低图片懒加载低低
🟢 低防抖节流低低

本项目的关键优化点

1. 流式消息的性能

流式消息是最频繁的更新场景,一定要优化好:

  • 每条消息独立的 observable,改一条只重渲染一条
  • 用 observer 包裹消息项组件
  • 避免在消息渲染里做复杂计算
2. 切换会话的性能

切换会话是常见操作:

  • 切换时清空旧会话的大对象,防止内存泄漏
  • 执行日志按 run 缓存,不要全量存
  • 图片和文件预览懒加载
3. 首屏加载性能
  • 重组件(代码编辑器、文件预览)动态 import
  • 路由懒加载
  • 字体和图标按需加载

11.6 代码质量保障

规范三件套

工具管什么怎么用
Prettier格式(缩进、引号、换行)保存时自动格式化
ESLint代码质量(变量用没用到、潜在 bug)提交前检查
TypeScript类型安全写代码的时候就有提示

💡 经验:Prettier 管「长什么样」,ESLint 管「写得对不对」,TypeScript 管「类型对不对」。三者配合,代码质量有基本保障。

测试策略

测试类型测什么优先级
单元测试工具函数、Store 逻辑高
组件测试组件渲染和交互中
E2E 测试完整流程走一遍低(成本高)

建议:核心逻辑(Store、工具函数)一定要有单测。UI 组件可以少写点,因为 UI 变化快,测试维护成本高。

Code Review 重点

  1. 有没有越级调用(比如组件直接调 axios)
  2. 数据改对了吗(有没有竞态条件、状态不一致)
  3. 边界情况处理了吗(空数据、加载中、错误态)
  4. 命名清晰吗(变量名、函数名能不能看懂)
  5. 有没有重复代码(能不能抽成公共的)

11.7 学习路线建议

学完这个教程之后,怎么继续提升?

阶段一:打基础(1-2 个月)

  • React Hooks 深入理解(useEffect、useMemo、useCallback)
  • TypeScript 高级类型(泛型、条件类型、映射类型)
  • MobX 官方文档通读一遍

阶段二:提能力(2-4 个月)

  • 学一个状态管理库(Redux / Zustand,和 MobX 对比着学)
  • 学 WebSocket 和 SSE 的原理和区别
  • 学 React 性能优化(memo、useMemo、虚拟列表)
  • 做一个完整的项目练手

阶段三:进阶级(4-6 个月)

  • 微前端架构
  • 低代码 / 可视化搭建
  • 前端工程化(构建原理、CI/CD)
  • Node.js 全栈开发

💡 学习心得:别光看,要动手。跟着做一遍比看十遍都管用。遇到问题先自己想、自己查,实在解决不了再问别人。


11.8 全书总结

恭喜你学完了整个教程!🎉

我们从项目概述开始,一路走过了:

  • 架构设计(五层架构)
  • 工程化(Vite、Less、ESLint)
  • 状态管理(MobX、根 Store + 子 Store)
  • 网关层(统一通信抽象)
  • 实时通信(WebSocket、流式回复)
  • 聊天模块(消息列表、输入框)
  • 执行面板(工具调用展示)
  • 审批机制(安全保障)
  • 更多业务模块(定时任务、记忆、设置…)

三句最重要的话

  1. 分层解耦——每一层只管自己的事,上层不操心下层怎么实现
  2. 实时优先——AI 产品的核心体验是「实时感」,所有设计围绕这个展开
  3. 观测并重——不仅能指挥 AI 干活,还要能看清它干了啥、为什么这么干

送给你的话

做项目就像搭积木——先搭骨架(架构),再填血肉(业务模块),最后打磨细节(体验优化)。

一开始不用追求完美,先跑起来,再慢慢迭代。每一个优秀的项目,都是从一个「能跑的简陋版本」开始的。

加油,你可以的!💪


感谢阅读本教程。如果觉得有帮助,记得动手实践——代码是写出来的,不是看出来的。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/qq_56236582/article/details/168109026

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--