类比:就像一个小盒子,可以存东西,也可以拿出来
const [数据, 改数据的函数] = useState(初始值)const [input, setInput] = useState("")
input = 盒子里现在装的是什么(当前值)setInput = 改变盒子里东西的函数"" = 盒子一开始是空的为什么需要?
类比:就像"当...的时候,执行..."
useEffect(() => { 做什么 }, [监听谁])[] = 空数组,意思是"只在页面第一次加载时执行一次"[某个数据] = 当这个数据改变时执行为什么需要?
类比:就像书签,可以指向页面上某个元素
const ref = useRef(null)ref.current = 指向的那个元素为什么需要?
类比:就像"等外卖"
async = 这是一个需要等待的函数await = 在这里等,等到了才继续fetch = 发送网络请求(就像点外卖)为什么需要?
类比:就像地图导航,告诉浏览器"我要去哪里"
const router = useRouter() = 获取路由对象router.push("/路径") = 跳转到指定页面router.replace("/路径") = 跳转并替换历史记录(不能返回)为什么需要?
类比:就像对讲机,可以实时双向通信
WSClient = WebSocket 客户端类,封装连接逻辑wsClient.connect() = 建立连接wsClient.disconnect() = 断开连接onMessage = 收到消息时的回调函数为什么需要?
类比:就像管理多个盒子,每个盒子存不同的数据
const [conversations, setConversations] = useState([])const [selectedConversationId, setSelectedConversationId] = useState(null)const [messages, setMessages] = useState([])const [input, setInput] = useState("")为什么需要?
| 英文 | 中文意思 | 记忆技巧 |
|---|---|---|
| state | 状态 | 记住:state = 状态(盒子里的数据状态) |
| effect | 效果/影响 | 记住:effect = 当...的时候产生的效果 |
| ref | 引用/指针 | 记住:ref = reference(引用),指向某个东西 |
| async | 异步的 | 记住:async = asynchronized(异步),需要等待 |
| await | 等待 | 记住:await = wait(等待) |
| fetch | 获取 | 记住:fetch = 去拿(去后端拿数据) |
| try | 尝试 | 记住:try = 尝试(试试看能不能成功) |
| catch | 抓住 | 记住:catch = 抓住(如果出错了,抓住错误) |
| finally | 最终 | 记住:finally = 最终(最后一定要做的事) |
| preventDefault | 阻止默认 | 记住:prevent(阻止)+ default(默认)= 阻止默认行为 |
| router | 路由 | 记住:router = 路由器(导航到不同页面) |
| push | 推送/跳转 | 记住:push = 推(推送到新页面) |
| replace | 替换 | 记住:replace = 替换(替换当前页面) |
| params | 参数 | 记住:params = parameters(参数),URL 中的参数 |
| websocket | WebSocket | 记住:websocket = 实时通信协议(双向通信) |
| client | 客户端 | 记住:client = 客户端(WebSocket 客户端) |
| connect | 连接 | 记住:connect = 连接(建立 WebSocket 连接) |
| disconnect | 断开 | 记住:disconnect = 断开(断开 WebSocket 连接) |
| callback | 回调 | 记住:callback = 回调(收到消息时执行的函数) |
| layout | 布局 | 记住:layout = 布局(页面布局结构) |
| dashboard | 仪表盘 | 记住:dashboard = 仪表盘(工作台页面) |
| hook | 钩子 | 记住:hook = React 自定义逻辑的钩子函数 |
| service | 服务 | 记住:service = 统一封装接口请求的模块 |
| module | 模块 | 记住:module = 一组功能组成的模块化单元 |
页面加载
有了访客ID后
有了对话ID后
用户发送消息
每次消息更新
登录页面加载
登录请求
登录成功
对话列表页面
进入聊天页面
/agent/chat/[conversationId]发送消息
sender_is_agent: true登录页面加载
登录请求
登录成功
/agent/dashboard(四栏布局工作台)工作台页面加载
选择对话
selectedConversationId 状态发送消息
sender_is_agent: true实时接收消息
拆分后的代码更像乐高积木,每一块负责自己的事情,组合起来就是完整的客服工作台。
页面层(app/agent/dashboard/page.tsx)
<DashboardShell />组件层(components/dashboard/)
DashboardShell:整合左中右三栏 + 顶部导航NavigationSidebar / ConversationSidebar / MessageList / VisitorDetailPanel:界面分块清晰,可复用VisitorDetailPanel 内置联系人信息编辑弹窗,点击“+ Add / 编辑”即可修改邮箱、电话、备注MessageList 通过 currentUserIsAgent 参数兼容客服/访客视角Hook 层(features/agent/hooks/)
useAuth:登录信息获取 + 退出登录useConversations:对话列表、防抖搜索、未读数更新useMessages:消息拉取、已读状态、WebSocket 回调,新增 updateContactInfo 用于保存邮箱/电话/备注useWebSocket:封装连接/断开/错误处理Service 层(features/agent/services/)
fetch 请求集中在这里,例如 conversationApi.ts、messageApi.ts、authApi.tsupdateConversationContact 方法,调用 PUT /conversations/:id/contact 更新访客联系信息工具层(utils/)
format.ts:统一时间和消息预览格式highlight.tsx:关键词高亮组件化storage.ts:localStorage 读写统一封装小结:页面调用 Hook,Hook 使用 Service,Service 请求后端;UI 部分由组件层独立负责。以后要换样式或替换数据源,都有明确位置可以下手。
| 层级 | 目录 / 文件 | 作用说明 |
|---|---|---|
| 页面入口 | app/page.tsx |
客服登录页,登录成功后跳转工作台 |
| 页面入口 | app/agent/dashboard/page.tsx |
工作台入口,渲染 DashboardShell |
| 页面入口 | app/agent/chat/[conversationId]/page.tsx |
旧单聊页面,复用新版组件与 Hook |
| 页面入口 | app/chat/page.tsx |
访客端聊天页面,复用统一组件/服务 |
| 组件层 | components/dashboard/* |
DashboardShell、导航栏、会话列表、消息列表、访客详情、输入框等 UI 组件 |
| Hook 层 | features/agent/hooks/useAuth |
处理登录态:读取/清理 localStorage,提供退出方法 |
| Hook 层 | features/agent/hooks/useConversations |
统一管理会话列表、搜索、防抖、排序、选中会话 |
| Hook 层 | features/agent/hooks/useMessages |
统一管理消息、已读状态、详情数据、WebSocket 回调 |
| Hook 层 | features/agent/hooks/useWebSocket |
对 WSClient 的通用封装,负责连接/断开 |
| Service | features/agent/services/conversationApi |
会话相关接口封装:列表、搜索、详情、更新访客联系信息 |
| Service | features/agent/services/messageApi |
消息接口封装:拉取、发送、已读 |
| Service | features/agent/services/authApi |
登录态接口(目前仅登出) |
| Service | features/visitor/services/conversationApi |
访客端对话初始化接口(收集 UA、语言等) |
| 类型定义 | features/agent/types.ts |
会话、消息、用户、WebSocket 负载等公共类型 |
| 工具 | utils/format.ts |
时间格式化、消息预览截断 |
| 工具 | utils/highlight.tsx |
关键词高亮渲染(返回 <mark>) |
| 工具 | utils/storage.ts |
localStorage 读写封装(获取/设置/清理客服账号) |
| 工具 | lib/websocket.ts |
WSClient:负责连接、自动重连、消息广播 |
访客与客服共用同一套消息组件/服务:通过
currentUserIsAgent、senderIsAgent等参数切换左右气泡与已读对勾。
忘记写 await
fetch(...) 后面直接用 .json()await fetch(...) 然后 await res.json()useEffect 依赖写错
[] 写成了 [data],导致无限循环忘记检查数据是否存在
data.id,如果 data 是 null 就报错if (data) 再使用忘记检查登录状态
路由跳转问题
window.location.href 跳转(会刷新页面)router.push() 跳转(不会刷新,更流畅)WebSocket 连接问题
useEffect 中创建连接,在清理函数中断开连接状态管理问题
对话切换问题
记住:编程就像学开车,一开始慢,多练习就熟了!🚀