WebSocket 双向通信实战:用声明式 UI 串起数据、操作和结果

发布时间:2026/8/25 23:17:30
WebSocket 双向通信实战:用声明式 UI 串起数据、操作和结果 实时消息频道页面连接状态、消息回显与滚动会话的完整分析一、先看页面到底解决了什么问题一个消息页面最容易让人误解的地方是标题写着“实时”或“WebSocket”读者便自然会把它理解成已经连上了服务器、可以和远端持续收发数据。但一个页面的文字标题、状态徽标和按钮名称并不能单独证明网络能力已经存在。判断一个演示是否真的完成了通信必须看它是否创建连接、是否有远端地址、是否处理连接成功与失败、是否接收异步消息以及断开时是否清理资源。眼前这个页面更适合被理解为一个“消息频道交互演示”。它用浅蓝色背景承托一块深蓝色顶部卡片顶部显示频道名称和当前状态中间是一块可以滚动的消息区域连接建立后页面底部才出现输入框和发送按钮最下面始终保留一个用于连接或断开的按钮。用户能够完整走完“未连接—连接—输入—发送—看到回显—断开”的可见流程。这个流程有真实的界面变化也有很清楚的状态边界但它并没有接入实际的 WebSocket 服务。所谓连接是页面内部切换一个布尔状态所谓服务端回复是发送时由页面拼出一条文字所谓心跳是成功发送消息后增长的计数。这样的定位并不影响页面的学习价值反而让它非常适合讲清楚声明式 UI 如何把状态、事件和视觉结果串在一起。读者可以先把它当成一个小型会话模拟器观察每一个操作如何改变屏幕再理解如果将来接入真实通信还需要补充哪些能力。二、第一次打开时页面给出的信息初始状态下顶部卡片左侧显示“实时消息频道”下面是一句“WebSocket 尚未连接”。右侧状态徽标写着“OFFLINE”使用灰色背景与深蓝色的标题卡片形成明显对比。这个组合并没有让用户猜测按钮应该做什么页面底部按钮会显示“连接频道”它是当前唯一的主操作入口。消息区域初始只有一条“系统频道尚未连接”。这条文字不是装饰它告诉用户当前会话还没有开始也让空白区域不至于显得像加载失败。消息区是一块白色圆角卡片内部内容沿垂直方向排列。区域本身可以滚动后续消息增加时不会把整个页面无限向下撑开。在未连接状态下输入框和发送按钮不会出现。这个选择很重要页面没有把“输入消息”伪装成任何时候都可用的功能而是用连接状态控制编辑区域的可见性。用户只能先连接频道之后才有输入入口。对于消息页面来说这比让发送按钮一直显示、然后在点击时再弹出“请先连接”的提示更直接因为页面结构本身已经表达了操作顺序。底部的连接按钮占满内容宽度使用与顶部卡片相近的蓝色。按钮与背景之间留有间距触控区域清晰。根布局采用纵向排列各区块之间保持一致的空隙整体内容有内边距最外层使用淡蓝色背景。这样的布局让标题、消息记录和操作区分别承担不同任务读者一眼就能分辨“状态在哪里”“记录在哪里”“下一步做什么”。三、连接按钮改变了哪些可见内容点击“连接频道”后页面并没有打开新的页面也没有弹出复杂对话框而是同步改变现有布局中的多个位置。顶部副标题从“WebSocket 尚未连接”变成“连接已建立 · 心跳 0”右侧徽标从“OFFLINE”变成绿色的“ONLINE”底部按钮文字变成“断开连接”输入框和发送按钮也随之出现。消息区域追加“系统连接成功”这让连接动作在会话记录里留下明确痕迹。这里可以观察到一个很典型的状态驱动关系。连接状态只有一个核心开关但它同时影响顶部说明、顶部徽标、底部按钮颜色、底部按钮文字以及输入区域是否显示。页面并没有分别保存“标题是否连接”“按钮是否可用”“输入框是否可见”这些重复信息而是由同一个状态决定它们各自应该呈现什么样子。这样做能够避免界面各处互相矛盾例如标题已经显示在线、底部却仍写着连接频道的情况。连接后顶部的心跳数字从零开始。它不是系统自动计时器也不是网络层定时发送的保活包数量。当前页面只在发送有效消息时让这个数字加一所以刚连接时一定是零点击连接本身不会让数字增加如果只打开输入框而不发送数字也保持不变。把这个细节说清楚才能避免把一个用于演示状态变化的数字误解成真实网络指标。连接按钮的颜色也会变化。未连接时使用蓝色表示可以开始会话连接后切换为红色文字改为断开连接表示当前操作会结束演示中的在线状态。这种颜色和文字的同步变化给按钮增加了语义而不是单纯通过颜色传递信息。即使用户不看颜色只看文字也能明白下一次点击会做什么。四、消息区域为什么采用滚动容器消息列表是这个页面最有“会话感”的部分。每新增一条记录列表就多出一个文本气泡。气泡的浅灰或蓝色背景、左对齐或右对齐并不是根据“系统”“我”或“服务端”这些前缀判断的而是根据消息在数组中的位置决定偶数位置使用浅灰背景并靠左奇数位置使用蓝色背景并靠右。因此初始系统消息位于第 0 个位置在左侧显示点击连接后追加的“系统连接成功”位于第 1 个位置反而会显示在右侧。连接后第一次发送时用户消息位于第 2 个位置服务端回显位于第 3 个位置二者分别落在左侧和右侧。这种规则很简单却让消息方向呈现出稳定的交替节奏但它不保证同一种消息来源永远在同一侧。系统消息“频道尚未连接”在第 0 个位置靠左“连接成功”在第 1 个位置靠右第一次有效发送追加的“我……”在第 2 个位置靠左紧随其后的“服务端已收到……”在第 3 个位置靠右。对于一个学习型页面来说气泡的视觉差异可以帮助读者把数组排列和页面结果联系起来同时也提醒我们不能仅凭颜色或位置判断消息来源。每条消息都有内边距和圆角文字字号保持一致消息之间用较小的垂直间隔分开。列表外层是白色圆角区域消息本身与区域背景形成层次。滚动容器占据页面中间的主要剩余空间并带有内边距因此消息不会贴着白色卡片的边缘。随着记录增加用户可以在这块区域中上下查看先前内容标题和底部操作不会被消息挤走。滚动区域还有一个重要作用它把“会话记录”与“当前操作”分隔开。输入框只负责接收新的文字发送按钮只负责触发一次发送历史消息则留在中间区域。即使用户连续发送多条内容页面仍保持相同的结构。当前实现没有额外提供清空消息、删除单条记录或跳转到底部的按钮这些能力不应被描述成已经存在用户能做的事情就是查看滚动区域中的记录。五、输入框的行为与空消息边界连接成功后输入框显示“输入消息”的占位提示。用户开始输入文字时输入内容会随输入变化保存到当前页面状态中。输入框宽度与消息区域、按钮保持一致位于滚动区域下方。它不会在未连接时出现所以用户不会在离线状态下输入一段最终无法发送的内容。发送按钮的处理有一个明确边界只有输入内容长度大于零时点击才会追加消息。也就是说空输入不会产生一条空白气泡不会增加心跳数字也不会改变输入框内容。这个判断虽然朴素却是消息交互中很必要的保护。没有它用户连续点击按钮就可能得到一串没有内容的记录列表会被无意义的气泡占满。当前判断的是字符串长度大于零并没有额外做前后空格清理也没有限制最大长度。按照页面现状一串空格在技术上仍然属于非空字符串较长文本也会被按原样拼入消息记录。这里不需要替页面添加不存在的校验规则只需把真实行为说明白页面拥有“空字符串不发送”的最小边界没有实现更复杂的内容审核、长度提示或发送失败提示。当输入有效文字并点击发送消息区会一次追加两条记录。第一条以“我”开头后面接用户输入的内容第二条以“服务端已收到「”开头再嵌入同一段内容最后以右引号结束。随后输入框被清空心跳数字增加一。用户能够看到自己的消息和页面构造出来的确认消息输入框回到等待下一次输入的状态。这个顺序形成了一个小闭环输入内容进入页面状态点击发送触发判断消息列表增加两条输入状态复位计数发生变化顶部副标题随计数刷新。没有任何一步需要用户手动刷新页面。声明式界面的特点就在这里体现出来只要状态改变依赖状态的文本和列表就会自动呈现新的结果。六、所谓“服务端回复”到底是什么页面上出现“服务端已收到……”很容易让人误以为真的存在远端服务。实际上这句话只是当前点击事件根据输入内容拼出的第二条字符串。页面没有建立真正的网络套接字没有填写服务器地址也没有等待异步回调它只是在本地数组中先放入用户消息再放入一条模拟回复。因此这条回复的出现速度是同步的内容始终遵循固定格式也不存在网络延迟、超时、丢包或服务端拒绝。把它称为“回显”或“本地模拟回复”更准确。它的学习价值是展示两条相邻记录如何在界面上形成对话感发送内容和确认内容会按追加顺序连续出现但左右位置由数组下标决定并不严格代表发送方和接收方。读者可以通过输入不同内容观察两条记录如何保持对应关系。它并不等于完成了真实 WebSocket 接入也不应据此推断应用能够连接聊天服务器。真实通信通常还需要连接地址、协议握手、连接成功回调、连接失败回调、远端消息接收回调、发送异常、断开原因、重连策略和页面销毁时的资源释放。当前页面没有这些内容所以它不负责处理服务器变化也没有“等待回复”的中间态。文章中只讨论已经能在页面上看到的本地状态变化读者就能正确理解它的范围。这也是学习演示与生产功能之间的边界。一个演示页面可以先把“消息发送后出现回显”这件事做得清楚帮助开发者理解状态和布局真正接入服务时再把本地追加回复替换为异步网络事件并增加失败分支。两者可以在交互形态上相似但在可靠性和数据来源上完全不同。七、消息数组如何保留历史记录页面初始消息列表只有一条系统提示。连接时采用追加方式保留原有提示并加入“系统连接成功”。发送有效消息时同样在旧记录之后追加两条新记录。断开时再追加“系统连接已断开”。整个过程中没有清空列表的动作因此一次会话留下的痕迹会继续显示在屏幕上。追加而不是覆盖带来两个直接结果。第一用户可以按顺序看到自己做过哪些操作第二滚动区域中的上下文不会因为下一次点击而消失。例如用户连接、发送“你好”、再发送“第二条”列表会依次保留连接成功、我发出的第一条、模拟回复、我发出的第二条、模拟回复。这样的顺序能帮助用户判断页面是否按照预期处理了连续操作。列表更新使用新的数组承载旧内容和新增内容。对使用者来说结果就是旧消息还在新增消息出现在末尾。页面没有做分页也没有限制消息数量文章不能把它宣传成具备历史持久化或长期存储能力。重新创建页面后是否保留内容也没有在当前交互中体现不能推断出本地数据库或文件保存。消息位置还会影响外观。因为排列规则依据位置奇偶来决定左右追加两条消息会让用户消息和模拟回复形成一左一右的组合但具体哪一条在左、哪一条在右取决于追加前列表已有多少条记录。追加系统断开提示时它落在下一位置可能呈现出与前一条不同的对齐方式它的标签虽然是“系统”位置却不会因此被强制放到左侧。这里的重点不是建立完整聊天协议而是让数据列表的每一个元素都能直接映射为屏幕上的一个气泡。八、断开连接以后页面会怎样连接状态下点击“断开连接”页面会把在线状态切换为离线状态并在消息列表末尾加入“系统连接已断开”。顶部副标题回到“WebSocket 尚未连接”状态徽标变成灰色“OFFLINE”底部按钮恢复为蓝色的“连接频道”。输入框和发送按钮因为连接状态不再满足显示条件而消失。断开不会清空历史消息也不会把心跳数字归零。已经显示过的会话记录仍然留在滚动区域中用户可以回看刚才的发送结果心跳数字也保留在上一次成功发送后的数值直到下一次连接并发送新消息。这个行为说明“当前是否连接”和“曾经发生过什么”是两个不同维度前者改变操作入口后者由消息列表保留。在离线状态下用户无法通过页面底部输入新的消息因为输入区域已经被隐藏。若再次点击“连接频道”页面会重新进入在线状态并在历史记录最后继续追加“系统连接成功”。这不是清空后重新开始而是在同一个界面会话中开启下一段本地演示。多次连接和断开可以反复进行消息记录会按操作顺序继续增长。这里同样没有真实连接关闭动作。因为页面从未创建网络连接所以断开只是状态翻转和文字追加。真实 WebSocket 关闭还需要处理关闭码、关闭原因、未发送消息、重连按钮以及连接对象释放等问题当前页面不展示这些内容。把“断开连接”理解成页面演示中的状态切换才能和实际表现保持一致。九、四个状态分别承担什么职责连接状态决定页面处于在线还是离线。它影响顶部描述、右侧徽标、输入区显示条件以及底部按钮的文字和颜色是最外层的交互开关。连接状态变化时页面多个位置一起改变用户不需要记住额外的步骤。输入状态保存输入框当前的文字。它随着用户输入变化发送成功后清空。这个状态不会决定消息区已有内容也不会直接改变顶部在线徽标它只服务于“正在准备发送什么”这一局部过程。把准备中的文字和已经发送的记录分开能够避免输入框内容和历史消息互相覆盖。消息列表保存页面已经展示过的系统提示、用户文字和模拟回复。它是一个按时间顺序排列的会话记录。连接、发送和断开都会追加内容只有空输入不会追加。列表状态一旦变化滚动区域就能看到新的气泡原有消息继续存在。心跳计数是页面顶部的数字反馈。它初始为零连接时不变每次有效发送后增加一。这个数字与消息发送次数相关而不是与真实网络保活相关。它让用户能看到某个操作产生了可量化变化也可以帮助初学者观察发送事件与状态更新的联系。这四类状态的组合构成了一个小而完整的交互模型在线状态决定是否能操作输入状态承载待发送内容消息列表承载已发生记录心跳计数展示发送次数。它们并不是四个孤立的数字或文字而是分别对应“能不能发”“准备发什么”“发过什么”“成功发了几次”的不同问题。十、从用户操作到页面结果的完整路线第一条路线是打开页面。此时标题显示离线徽标为 OFFLINE消息区只有频道尚未连接底部按钮邀请用户连接。页面没有自动连接也没有延迟等待因此打开即能看到明确的初始状态。第二条路线是连接频道。点击按钮后在线状态改变消息区加入连接成功标题显示在线和心跳零输入框以及发送按钮出现底部按钮变为断开连接。一次点击带来的变化分布在顶部、中部和底部但它们共同表达同一件事现在可以输入消息了。第三条路线是发送空消息。用户不填写内容直接点击发送消息区不会增加新记录心跳数字不会变化输入框仍保持空白。页面没有弹出错误提示这个边界表现为“没有产生发送结果”。如果产品需要提示原因当前页面还没有提供相应文字不能把沉默的忽略改写成“显示了校验提示”。第四条路线是发送有效消息。输入一段文字并点击按钮列表末尾出现用户消息和模拟回复输入框清空心跳数字加一。重复这个过程每次都会再追加两条并按照追加后的数组位置显示左右气泡如果前面已有消息条数为偶数新增的两条会先左后右如果前面已有消息条数为奇数交替顺序也会随之变化。文字中的中文、英文或数字都只是被当作字符串显示页面没有对消息类型进行解析。第五条路线是断开。连接状态变为离线系统断开提示追加到列表输入区隐藏按钮恢复连接频道。之后再次连接旧记录不丢失新连接提示继续排在末尾。沿着这五条路线操作读者可以看到页面在每一个状态节点的稳定形态。十一、页面视觉为什么能帮助理解状态顶部卡片采用深蓝色背景和白色标题承担“当前频道”的视觉锚点。标题字号较大副标题字号较小二者上下排列用户先看到频道名称再看到是否在线和计数。右侧徽标使用紧凑的圆角块在线时为绿色离线时为灰色和文本形成第二重提示。消息卡片使用白色背景与最外层浅蓝色背景拉开层次。消息气泡采用圆角和内边距偶数位置使用浅灰色并靠左奇数位置使用蓝色并靠右。内容前缀可以帮助读者辨认“系统”“我”或“服务端”颜色和位置则只表达列表中的排列位置不能单独作为消息来源判断。即使列表中连续出现多条内容视觉上的交替节奏仍然容易跟踪。按钮颜色跟随动作语义。连接时使用蓝色给人开始操作的感觉在线后切换红色提醒用户点击会结束当前演示状态。按钮文字始终直接说明下一步动作而不是固定写成“操作”。输入框与发送按钮只有在线时出现使页面在离线状态下更简洁在线状态下才展开完整控制区。根容器、标题卡片、消息卡片和气泡都使用圆角但圆角大小和层级不同。最外层是宽松的背景标题和消息区是较大的卡片单条消息是更紧凑的气泡。圆角并不是为了装饰而堆叠而是帮助用户理解页面由几个独立区域组成频道状态、会话记录、当前输入和连接操作。十二、这个演示清楚地告诉了我们什么首先条件显示能够让操作顺序变得自然。输入框在连接后才出现用户不必先学习错误提示断开后它自动消失页面不会保留一个看起来可用但实际没有作用的发送入口。其次消息列表是状态变化的可视化日志。每一次连接、发送和断开都留下文字用户可以从上到下还原操作历史。对于调试和教学来说能在屏幕上看到状态事件往往比只看到一个按钮颜色变化更容易理解。再次同一次操作可以影响多个区域。发送消息不仅增加列表内容还清空输入框并增加顶部计数连接不仅改变按钮还改变标题、徽标和底部输入区。这些变化不是手动寻找控件后逐个修改而是页面根据状态重新呈现。最后演示功能必须有边界意识。标题出现 WebSocket并不代表网络连接已经完成“服务端已收到”并不代表远端响应真实存在“心跳”数字也不代表有定时保活。把这些边界写清楚文章才不会让读者拿着页面文案去推断不存在的能力。十三、如果要继续扩展应该从哪里开始当前页面已经把本地交互链路讲清楚下一步扩展的前提是先确定真实服务端协议。需要明确服务器地址、连接参数、消息格式以及连接成功后首屏是否要拉取历史内容。不能只把模拟回复文字替换成一个模糊的“调用接口”因为真实通信会引入异步时序。连接过程至少需要区分连接中、连接成功和连接失败。连接中时按钮是否禁用、标题如何提示、用户能否编辑输入都需要设计。失败时应告诉用户失败发生在哪里并给出重新连接的入口。当前页面没有连接中状态也没有错误提示因此这些内容属于后续设计不属于现有功能。接收消息时需要处理远端主动推送。推送消息可能在用户没有点击发送时到来仍然应该追加到会话列表并保持正确的消息样式。消息格式不合法时需要避免页面崩溃。当前的模拟回复始终是固定字符串所以没有解析和异常分支。发送过程还需要考虑网络尚未准备好、发送失败、重复点击和离开页面等情况。真实应用可能需要发送中状态、失败重试、待发送队列或失败消息样式。当前页面点击有效消息后立即完成本地追加因此不能写成已经具备这些机制。断线重连同样需要明确策略。是立即重试、延迟重试还是等待用户手动点击重连后是否恢复历史心跳是否由定时器驱动页面退出时如何停止定时器和释放连接这些问题都与真实连接生命周期相关而当前页面只用一个布尔值表达在线与离线。把两种层次分开才能在扩展时不破坏现有的界面逻辑。十四、适合初学者的观察方法第一次体验时不要急着连续点击。先观察离线首屏记住三个位置顶部状态、消息区初始文字、底部连接按钮。然后只点击一次连接查看哪些区域一起变化。接着输入短文字发送再对照顶部计数、输入框和消息区确认一次发送产生了哪些结果。第二轮可以测试空输入。连接后不填写内容直接点击发送观察页面没有新增消息也没有计数变化。这个测试能帮助理解“按钮存在”与“按钮一定产生结果”之间的区别。第三轮连续发送两三条不同文字观察消息数组的顺序和气泡左右变化再滚动查看列表中的旧记录。第四轮点击断开确认输入区隐藏、按钮文字恢复、历史记录仍在。再次连接后不要假设列表被重置观察新的连接提示是否追加在旧记录后面。这个过程能把当前页面的保留策略看得很清楚连接状态会切换消息记录不会被清空。如果要判断它是否真的联网可以从可见行为反推页面没有地址输入没有加载等待没有网络错误没有远端不可控的返回内容回复文字格式始终由输入内容决定计数只随发送按钮增加。因此更准确的结论是这是一个展示实时消息交互外观和状态流转的本地页面而不是已经接入服务器的聊天客户端。十五、总结这个实时消息频道页面用很少的元素完成了一个完整的交互闭环离线时显示频道尚未连接点击连接后进入在线状态并出现输入区发送有效消息后同时展示用户消息和本地构造的确认文字输入框清空心跳数字增加点击断开后恢复离线界面同时保留历史记录。它的价值不在于假装完成了网络通信而在于把几个常见的 UI 问题放在同一个屏幕中布尔状态如何控制条件显示字符串如何从输入框流入消息列表数组追加如何形成滚动会话计数如何为操作提供可见反馈按钮文字与颜色如何表达下一步动作。每一条规则都可以通过实际点击观察到页面不会要求读者先理解复杂的服务端协议。理解这些行为后再去接入真实 WebSocket 会更稳妥。真实连接需要异步生命周期、异常处理、消息解析、心跳机制和重连策略但这些能力应该建立在清楚的界面状态模型之上。当前演示已经把“页面应该如何回应用户操作”表达出来后续只需把数据来源和连接生命周期逐步替换而不是把标题中的名词直接当成已完成的网络功能。只要记住三个判断标准就不会误读这个页面看到 ONLINE 代表本地在线状态已切换不代表远端握手成功看到服务端回显代表本地消息列表追加了一条模拟文字不代表服务器真的回复看到心跳数字增加代表有效发送发生不代表有自动保活任务。围绕这三个边界阅读和体验才能准确理解这个消息频道页面的实现范围与学习意义。十六、把一次完整会话拆成几个可观察的瞬间把会话按时间拆开能更准确地理解每个状态的作用。第一个瞬间发生在页面刚出现的时候。此时没有任何用户输入也没有连接操作消息区先承担说明职责告诉用户频道尚未连接。顶部的灰色徽标和底部的蓝色按钮互相呼应一个说明当前结果一个暗示下一步动作。这个瞬间的价值是建立基准后续所有变化都可以与它比较。第二个瞬间是点击连接后的立即结果。连接成功文字进入消息列表顶部文字出现心跳零徽标变成绿色输入区域展开。注意这些结果没有先后延迟页面把一次点击视作一次完整的本地状态变化。用户不需要等待也不会看到“正在连接”的中间文字。这个事实进一步说明它没有模拟异步网络过程连接体验只是为了观察条件显示和列表追加。第三个瞬间是输入但尚未发送。用户在输入框中逐字输入时消息列表和心跳数字都不动只有输入框内部的文字变化。这个阶段很容易被忽略但它恰好体现了“待处理内容”和“已完成事件”的区分。输入中的文字还没有进入会话记录用户可以继续修改也可以保持为空只有点击发送并通过非空判断后它才会成为历史消息的一部分。第四个瞬间是发送完成。用户消息和确认文字一前一后进入列表输入框清空心跳加一。这里的“前后”不是网络先后而是页面追加数组时的固定顺序。它保证读者每次都能先看到发送内容再看到对应回显。若连续发送新的两条会继续排在末尾不会插入旧消息中间也不会替换上一轮内容。第五个瞬间是断开后的静止状态。断开文字被保留下来操作区缩回离线形态历史消息仍在滚动区域里。页面没有自动删除刚才的消息也没有把计数重置为零。这个静止状态适合观察“控制当前交互的状态”和“记录过去事件的状态”之间的差别前者决定现在能做什么后者负责告诉用户刚刚发生过什么。十七、连续操作时最容易看错的地方连续点击连接和断开时消息记录会持续追加对应的系统文字。用户可能会把列表中的多条“连接成功”理解成建立了多个网络连接但页面表现只能说明按钮被多次点击、布尔状态在两种值之间来回切换。它没有连接编号也没有服务器返回的会话标识因此不能从记录条数推断真实连接数量。连续发送时每条有效输入都会产生两条记录心跳数字每轮增加一。消息条数和心跳数字并不是一比一关系一次发送增加两条文字但只增加一个计数。这个差异是页面设计的一部分计数更像是有效发送次数而不是消息列表长度。理解这一点后看到四条新增消息却只增加两点就不会误认为页面漏掉了消息。在同一条消息中输入很长的内容时文本会在气泡内部换行气泡高度随文字增加。滚动区域仍然负责承载整体内容底部按钮和输入框的位置不会因为历史消息变长而失去结构。页面没有显示字数上限也没有专门的长文本提示所以长消息的表现主要由文本组件和滚动区域共同承担。英文、数字、标点混合输入也会按原字符串显示不会自动转成结构化卡片。输入只有空格时当前非空判断仍可能把它当作有效文字。这个行为提醒我们字符串长度判断和“有意义的内容”不是同一件事。如果将来要把页面用于正式聊天需要决定是否去除首尾空白、是否禁止只有空白的消息、是否显示原因当前演示没有这些额外规则文章只记录页面实际做出的最小判断。断开后再连接消息区不会回到首屏状态。旧消息和旧计数会继续存在新的连接提示接着添加。用户可以利用这一点观察一次页面生命周期内的累积效果但不能据此得出数据会跨页面重启保存的结论。页面没有展示持久化读取也没有提供清空历史的动作能确认的范围只到当前界面中的数组记录。十八、从页面结构理解声明式交互这个页面没有把每一次点击都写成“找到某个控件再修改它的文字和颜色”。相反页面按照当前状态描述应该显示什么离线时显示离线徽标和连接按钮在线时显示在线徽标、输入框、发送按钮和断开按钮消息列表则根据当前数组中的每个元素生成对应气泡。用户改变状态后屏幕自然呈现新的组合。这种写法的直接好处是可读性。只要看到顶部文案使用在线状态作为条件就能理解为什么连接后会显示心跳只要看到输入区处于连接条件内部就能理解为什么断开后它会消失只要看到消息区遍历列表就能理解为什么每次追加都会出现新的气泡。页面行为和视觉结果之间没有隐藏的手工刷新步骤。状态之间的关系也比较容易画成一条线。连接状态是入口它决定用户能否进入发送阶段输入状态在发送阶段暂存内容发送成功后列表和计数同时发生变化断开则结束发送阶段但不抹去列表。这个顺序比单纯罗列四个变量更有帮助因为它说明了状态什么时候变化、为什么变化以及变化后哪个区域会受到影响。页面还通过固定的文字和颜色增强可观察性。“OFFLINE”和“ONLINE”位于顶部右侧用户无需滚动就能确认当前状态“系统”“我”“服务端”三个前缀让消息来源可读“连接频道”“断开连接”“发送消息”三个按钮文字分别对应明确动作。消息气泡的颜色和位置来自数组下标因此要结合前缀阅读不能把右侧蓝色直接等同于用户消息也不能把左侧灰色直接等同于服务端消息。技术演示如果没有这些反馈状态变化即使发生了用户也很难验证当前页面把反馈放在了最容易看到的位置。十九、体验这类页面时应保持的判断边界不要把标题当成能力清单。标题使用了实时消息和 WebSocket 这些词只能说明页面想展示的交互主题不能证明真正的网络对象已经存在。判断能力时应看能不能输入地址、能不能观察远端不可预测的结果、能不能看到连接失败、能不能在网络变化后表现不同。当前页面的所有结果都能由本地状态和固定字符串解释因此应归类为本地模拟。不要把绿色徽标当成网络监控结果。它只是连接状态为真时的视觉反馈点击连接就会出现点击断开就会消失。它不会根据网络信号、服务器健康状态或远端主动关闭而变化。这样的徽标足够演示状态切换但不足以作为真实连接质量指标。不要把心跳数字当成时间。数字不会随着秒数自动增长也不会在连接后周期性更新只有发送有效消息时才增加。因此它更适合作为一次发送动作的可见计数。文章如果把它描述成后台定时心跳就会把当前页面没有的能力强行加入读者运行时也无法得到对应结果。不要把模拟回复当成消息服务。回复文字始终包含刚刚输入的内容格式固定且立即出现没有加载中的空白、没有服务器可能返回的不同字段也没有失败消息。它的作用是让列表按追加顺序呈现“一发一回”的文字关系而不是替代真正的服务端协议气泡的左右位置仍由数组下标决定不能据此判断远端消息方向。不要把滚动列表当成历史数据库。列表只在当前页面状态中不断追加能看到的就是这次界面会话里已经发生的提示和文字。没有搜索、分页、持久化、清空和删除入口。把滚动区说成消息记录展示是准确的把它说成完整聊天历史管理则超出了页面实际范围。二十、结语小页面也要把事实说清楚实时消息频道页面的结构并不复杂却完整展示了一个状态驱动界面的基本闭环。初始离线状态负责说明当前条件连接操作负责打开输入入口输入状态负责承载待发送文字消息列表负责按照顺序展示事件计数负责提供额外的可见反馈断开操作负责收起发送区域并记录结束动作。更重要的是这个页面没有必要被包装成远端通信产品才能体现价值。它用本地状态模拟了连接、发送、回显和断开读者可以在很短的操作路径中看到状态的变化和布局的响应。只要把模拟部分与真实能力明确区分页面就不会误导初学者也能成为以后接入网络服务时的界面基础。真正扩展时应该先保留当前界面中已经清楚的几个概念再逐步加入真实连接的异步状态、异常反馈、远端推送、发送失败和断线处理。每增加一种能力都要让用户在顶部状态、消息记录或操作按钮上看到对应结果。这样做出来的页面既不会把演示文字当成网络事实也不会让真实通信的复杂度被一个简单按钮掩盖。