《互联网产品的客户端架构·端形态与体验基础》前言

四卷规划、贯穿案例「回声」、实验工程 echo-client-lab 与 L1 到 L3 的三级可运行性,都在这篇前言里交代清楚。另说明代码块的三行头、数字与版本口径、三种读法,以及本书不承诺的真实浏览器与真机性能边界,便于读者判断哪些结论可以复现、哪些必须换成自己产品的参数。

为什么单独写一篇前言

序回答的是“为什么写这套书”。前言回答的是另一组更具体的问题:这套书怎么组织,案例从哪里来,代码怎么跑,数字是什么口径,读者应该怎么读。

这些问题不放在正文里,是因为它们适用于整卷,而不是某一章。把它们集中交代,读者可以在开始之前就知道:哪些结论可以直接拿去用,哪些必须先换成自己产品的参数;哪些代码能在本地复现,哪些只能作为模型参考。

如果你只想知道卷一讲什么,目录已经写得很清楚。前言更适合这样的读者:准备认真读一段时间,或者准备把书里的方法带进团队。

四卷规划与卷一的位置

《互联网产品的客户端架构》计划四卷。四卷不是四个专题合集,而是一条从地基到演进的线。

卷主题主要问题
卷一端形态与体验基础端上约束如何决定分层、状态、路由、数据与渲染
卷二内容、社交与实时互动Feed、媒体、评论、IM、长连接、推送与离线同步
卷三交易、增长与平台化商品、购物车、支付、营销、实验、埋点与合规
卷四多端、性能与架构演进多端复用、微前端、性能体系、发布工程与组织协作

卷一先把地基打牢。它讨论的是所有客户端都绕不开的问题:端上有什么独有约束,端形态如何选择,体验怎么定义与预算,代码怎么分层,状态归谁,路由怎么表达,数据层怎么处理离线与冲突,组件契约怎么定,产物怎么构建,渲染路径怎么选,错误怎么兜底,观测怎么做。

这些问题如果没定清楚,后面三卷讨论的复杂场景只会把问题放大。比如卷二要讨论长连接与离线同步,如果卷一的同步队列与冲突策略没讲明白,长连接只会变成“多了一条容易断的通道”;卷三要讨论支付与实验,如果卷一的状态归属与错误恢复没打好,支付状态就会在端上出现两份,实验分组也会被缓存污染。

所以卷一刻意不追热点。它不讨论某个框架的新特性,也不预测明年的技术趋势。它讨论的是那些在框架换过几轮之后仍然成立的判断。

卷一 45 篇,由三部分组成:前置 3 篇,正文 39 篇,附录 3 篇。正文 13 章,每章 3 节。第 13 章不再引入新概念,只把前面十二章的模块串成一条端到端基线,作为进入卷二前的检查点。

四卷之间的依赖

四卷可以分开读,但后一卷会假设前一卷的某些结论已经成立。

卷一输出的是底座:四个约束、端形态选择、质量预算、分层与依赖方向、状态归属、路由语义、数据层边界、组件契约、构建与渲染的默认值、错误恢复与观测口径。

卷二在这个底座上讨论内容、社交与实时互动。Feed 的分页与缓存依赖卷一的请求编排;IM 的发送队列依赖卷一的离线队列与冲突策略;推送与长连接依赖卷一的错误分类与恢复机制。

卷三讨论交易、增长与平台化。购物车与支付状态依赖卷一的状态归属;营销与实验依赖卷一的缓存与版本策略;埋点与合规依赖卷一的观测口径与 PII 边界。

卷四讨论多端、性能与架构演进。多端复用会重新打开卷一第 2 章的端形态选择;性能体系会回到卷一第 3 章的质量预算;发布工程会回到卷一第 9、10 章的构建与渲染。它不是全新的主题,而是把卷一的判断放到更大的组织与时间尺度上检验。

这也是为什么卷一不急着讲“高级场景”。底座没打牢,高级场景只会让问题更隐蔽。先把状态、路由、数据层与错误恢复讲清楚,后面三卷才有共同的参照系。

贯穿案例「回声」

整套书使用同一个贯穿案例:「回声」。它是一个同时有内容流、私信与站内下单的产品。

这个设定不是为了显得复杂,而是为了让不同章节的结论能互相验证。一个只有内容流的产品,讨论不到离线写入与冲突合并;一个只有私信的产品,讨论不到下单页的草稿与金额校验;一个只有下单的产品,讨论不到内容流的缓存与分页。把三类场景放进同一个产品,才能看到它们共享同一套状态、路由与数据层时会发生什么。

上一套《互联网产品的服务端架构》从服务器看「回声」,这一套从端上看「回声」。同一个产品,两套视角,读者可以把两边对照着读。

卷一里,「回声」客户端的第一版只有三个页面:

  • 发现页:内容流列表,支持分页与下拉刷新。
  • 会话页:与某个用户的私信,支持发送文本与查看历史。
  • 下单页:商品确认、地址填写、提交订单。

三个页面看起来简单,却刚好覆盖了客户端状态的四层:界面状态、页面状态、草稿与队列、服务端状态。发现页的列表是服务端状态,端上只做缓存;会话页的消息是混合状态,已收发的消息可以重新拉取,未发送的文本与发送队列不能丢;下单页的地址与订单确认信息是草稿,金额与库存是必须校验的服务端状态。

第一个决定因此变得具体:不是“要不要用全局状态”,而是“这份状态丢了会怎样”。答案决定了它放在组件、路由、本地存储还是服务端。

后面每一卷都会在「回声」上增加新的场景。卷二加内容、社交与实时互动,卷三加交易、增长与平台化,卷四加多端、性能与架构演进。案例会变复杂,但底座不变;如果底座在第 1 章就定错了,后面的复杂只会让错误更难发现。

实验工程 echo-client-lab

卷一的所有可运行代码放在实验工程 echo-client-lab 里。它使用 TypeScript 与 Node 26 的原生类型剥离,不引入第三方依赖。

选择这条技术路线,有三个考虑。

第一,可复现。客户端生态的框架、构建器与测试库更新很快,书里的版本号一过时,示例就可能跑不起来。把可运行部分收敛到语言内置的能力上,能让内容在更长时间里保持可复现。

第二,可审计。零第三方依赖意味着读者不需要先配一套复杂的环境,也不需要信任作者的某个私有配置。拿到工程,装好 Node,就能跑。

第三,边界清楚。Node 能验证逻辑、流程与本地时序,但验证不了真实浏览器渲染与真机性能。把工程限定在它能验证的范围内,反而让书的承诺更可信。

实验工程按章分目录,按节分源文件与测试文件。大致结构如下:

目录对应内容
src/core约束分类、客户端模型、分级器
src/state状态容器与状态机
src/router路由解析与页面栈
src/data请求编排、缓存、离线队列
src/view组件契约与受控状态
src/build产物预算与分包检查
src/obs指标口径与埋点校验
test/与 src/ 对应的测试

每一章对应一个目录,三个源文件对应三节,三个测试文件验证三节的核心断言。第 13 章会把所有模块串成一条端到端流程,作为卷一的收口。

这些代码不是生产框架,而是判断的夹具。它们的价值不在于“可以直接复制到项目里”,而在于把“这个判断在什么输入下成立”固定下来。产品代码还需要协议、持久化、监控、压测与真实设备适配,那些不在本书的承诺范围内。

三级可运行性

卷一使用三级可运行性标注。分级不是为了给章节贴标签,而是为了约束写作:每一节动手前先判定级别,级别决定这段代码要不要真实运行、运行结果能不能写进正文、哪些结论只能写成“预期”或“案例假设值”。

级别判定验证方式本书承诺
L1纯逻辑,无外部进程与真实时序node --test代码可运行、断言可复现
L2进程内端到端,需要串起多个模块一个脚本跑完整流程流程可复现、状态可断言
L3需要本地 HTTP/WebSocket 服务或真实时序起本地服务 + 固定场景时序与重连逻辑可复现
不承诺真实浏览器渲染、真机性能、系统调度无只给模型与判断方法,不写实测数字

L1:纯逻辑

L1 验证不依赖运行环境的判断逻辑:状态机、缓存策略、请求去重、冲突合并、指标计算。这类代码的输入输出完全由参数决定,同样的调用顺序得到同样的结果。

L1 的门禁是 node --test 全绿。它不能证明真机上不会卡,但能证明“给定这些输入,判断是对的”。卷一大量使用 L1,因为架构决策的大部分错误发生在逻辑层,而不是渲染层。

L2:进程内端到端

L2 把多个 L1 模块串起来,验证一条完整流程:打开页面、加载数据、失败重试、写入本地、恢复快照。它仍然不启动外部进程,但会使用内存适配器模拟存储与网络。

L2 的门禁是一个脚本跑完整流程并断言关键状态。它比 L1 更容易暴露“模块各自正确、组合起来不对”的问题,比如缓存写入了但页面没更新,或者恢复时页面栈顺序错了。

L3:本地服务

L3 需要真实的 HTTP 或 WebSocket 服务,用来验证长连接、重连、离线补拉、请求超时这类与时序相关的行为。实验工程用 Node 内置的 http 模块起一个本地服务,不引入第三方框架。

L3 的门禁是固定场景脚本:启动服务、制造断连、观察重连与补拉、断言最终状态。它仍然不是“真机弱网”,但能把“重连逻辑有没有写对”从猜测变成验证。

不承诺的部分

真实浏览器渲染、真机启动耗时、帧率、内存占用、系统后台回收策略,这些都需要真实设备与浏览器环境。卷一不写“在某某手机上实测多少秒”这类无法复现的数字,只给模型、口径与检查方法。

如果某一节确实需要浏览器行为,会明确标注“需在浏览器环境验证”,并给出可复现的检查步骤,而不是伪造一个运行输出。这条边界会在 1.3 节展开。

分级也不是一次定终身。模块组合方式变了,级别可能要上调。一个看起来是纯函数的重连状态机,一旦依赖真实时序,就必须按 L3 验证。

一次 L2 才抓到的错误

卷一早期版本里,请求去重是当成纯逻辑写的。测试断言同一 key 只调用一次 fetcher,跑得很顺。把它放进发现页的滚动流程之后,去重却失效了。

原因不复杂:每次滚动都创建了一个新的客户端实例,去重用的 inflight 表跟着实例一起重建。旧实例的请求还没回来,新实例已经发了一份新的。单看每个实例,行为都正确;放在页面生命周期里,结论不成立。

这个 bug 在 L1 里抓不到,因为 L1 只验证单个实例;它也不是 L3 的问题,因为不需要真实网络。它属于 L2:多个模块组合之后的生命周期。后来补了一条 L2 用例,模拟“页面重新挂载但共享同一个数据层”,才把这个错误固定下来。

这件事说明分级不是形式主义。级别判错了,测试会给虚假的安全感:断言都过了,结论却不成立。判断标准不是“这段代码看起来简不简单”,而是“结论依赖哪些运行时事实”。

卷一里类似的例子还有几个。它们大多不是语法错误,而是生命周期、共享状态、时序假设出了问题。这也是为什么书中反复强调:先判定级别,再写测试;先问结论依赖什么,再问代码怎么写。

代码块的三行头

书稿里的代码块不是独立片段,而是实验工程文件的副本。每个代码块开头有三行标记,分别说明文件路径、可运行级别与验证命令。

标记含义读者怎么用
file这段代码在实验工程里的路径到工程里找同一份文件,逐字节对照
tier这一节的可运行级别判断这段结论验证到哪一级
verify作者实际执行过的验证命令在本地复现同一组断言

写作流程是:先在实验工程里写文件并跑通测试,再把文件内容原样贴进正文。校验脚本反向工作:从正文提取每个代码块,按 file 路径与工程文件逐字节比对;不一致就报错。

同一路径在书中出现多次时,每次内容必须一致。校验脚本会记录第一次出现的内容,后续出现不一致直接失败。这条规则看起来严格,但它保证了读者按任意一节复现,拿到的都是同一份代码。

三行头还有一个作用:让读者知道这段代码的边界。看到 L1,就知道它只验证逻辑;看到 L3,就知道它需要本地服务或时序;看到“需在浏览器环境验证”,就知道书里不会给实测数字。

数字与版本的口径

书中的数字分三类。

案例假设值:用来说明数量级或取舍关系,例如“用户每小时被打断几次”“缓存上限设为多少条”。这些数字不是行业标准,也不是真实产品的实测,读者必须按自己的产品事实替换。

设计目标:团队承诺的预算,例如“首屏在某个网络条件下不超过多少”“包体不超过多少”。设计目标是用来对齐团队的,不是用来证明某个方案更优的。

本机运行输出:有命令、环境和原始文本,只对当时的软硬件成立。书中标注“本机运行”的耗时与测试输出,读者可以按同样的命令复现;耗时数字随机器变化,不作为性能结论。

版本口径:Node 26,本机验证过的版本是 v26.5.0;TypeScript 语法按 Node 原生类型剥离支持的范围。本书不绑定具体框架版本,涉及框架的部分只讨论判断方法。如果你的 Node 版本更高,绝大多数示例应当照常工作;出现差异时以本机错误信息为准。

书里不会出现“业界通常”“大多数产品都”这类没有出处的断言。如果某个数字没有标注来源,它就应该被当成案例假设值,而不是事实。

卷一的边界

卷一不是性能优化手册。它会在第 3 章定义体验指标,在第 9 章讨论包体预算,在第 12 章讨论观测口径,但不会给出一套“照着做就能提速多少”的清单。性能优化的前提是知道哪些路径关键、哪些状态不能丢、哪些模块可以独立加载;这些答案要先从架构判断里来。

卷一也不是框架对比。书里会提到框架与状态库,但只把它们当作实现手段。哪个框架更好、哪个状态库更流行,不是这套书要回答的问题。框架会换,判断不该跟着换。

卷一不覆盖所有端形态的全部细节。Web、原生、小程序与桌面各有自己的生态,第 2 章只讨论与架构判断相关的差异。具体平台的 API、生命周期回调、打包工具链,需要读者结合官方文档补齐。

卷一不能替代真实设备测试。实验工程验证的是逻辑、流程与本地时序,真实浏览器渲染、低端机卡顿、系统后台回收策略,必须在真实环境里验证。书里给的是模型与检查方法,不是实测结论。

卷一也不讨论产品策略。它不判断某个功能该不该做,只讨论如果要做,端上应该怎么组织。产品判断需要产品事实、用户研究与商业约束,那些不在本书的知识范围内。

这些边界不是免责声明,而是阅读时的坐标系。知道一本书不做什么,才能更好地使用它做了什么。

怎么读这本书

顺序读

从第 1 章开始,按章推进。适合第一次接触客户端架构,或者想系统补齐判断依据的读者。每一章的输出会变成下一章的输入,跳读容易漏掉前提。

第 1 章的四个约束是后面所有判断的输入;第 2、3 章定端形态与质量预算;第 4 章定分层与依赖方向;第 5、6、7 章定状态、路由与数据层;第 8 到 12 章分别处理组件、构建、渲染、错误与观测;第 13 章收口。

按问题读

如果你正在处理一个具体问题,可以直接跳到对应章节。目录里有一张“问题到章节”的对照表,这里不再重复。

按问题读的风险是漏掉前提。比如直接读第 10 章的渲染模式,可能不知道第 3 章的首屏预算与第 9 章的分包策略已经限定了可选范围。读到某个结论觉得“不成立”时,回到它依赖的前置章节看一眼。

按角色读

产品与设计可以重点读第 2、3、6 章,理解端上约束如何影响体验方案;客户端工程师建议按顺序读;技术负责人可以重点读第 1、4、13 章与附录 C,把判断变成团队可执行的规则;服务端工程师可以读第 1、7、11 章,理解端上的失败模式与同步队列。

角色读法不是把书切成几块,而是给不同读者一个入口。真正做决定时,还是要把相关章节放在一起看。

致谢与反馈

这套书的想法,最早来自服务端卷读者的提问。有人在读完之后问:服务端讲清楚了,端上怎么办。这个问题一直留在心里,后来变成了四卷的规划。谢谢这些提问,它们比赞美更有用。

实验工程建立在 Node 与 TypeScript 之上,感谢这些开源工具链的维护者。书里的很多判断,也是在反复写测试、跑测试的过程中才变得清楚。

感谢在写作过程中读过草稿、提出过反对意见的同事与朋友。有些章节因为一句“这个结论在我们产品里不成立”而重写。具体的名字不一一列出,但那些意见留在了书里。

书中的错误由作者负责。如果你发现事实错误、代码跑不通、结论与你的经验冲突,欢迎通过站点上公布的联系方式反馈。有效的勘误会在后续版本中修正,并在页面的更新时间里体现。请不要把反馈发到不存在的邮箱或链接,站点上的联系方式是唯一入口。

反馈不要求客气,只要求具体:哪一页、哪句话、为什么不对、你的依据是什么。具体的问题更容易被修正,也更容易让后来的读者受益。

开始之前

最后说一句读法上的建议:不要急着同意书里的结论。

客户端架构没有一套放之四海而皆准的答案。书里给的每个决定,都带着前提、代价与退出条件。读的时候,不妨先问:这个前提在我的产品里成立吗?如果不成立,哪个选项更合适?如果成立,我愿意为它付出多少成本?

能带着这些问题读完,比记住几个结论更有价值。

这本书也不要求一次读完。客户端架构的判断需要在项目里反复用,读一遍能建立框架,用一遍才知道哪里不顺手。遇到具体问题时回来翻对应章节,比从头再读一遍更有效。

书中的案例、代码与数字都有边界。把边界一起读进去,才不会把“在这个前提下成立”误当成“在任何情况下都成立”。这也是前言要花这么多篇幅交代口径的原因。

如果读完之后,你能在自己的项目里指出一处“我们现在的默认做法和端上约束冲突”,这篇前言的目的就达到了。剩下的章节,是帮你把那个冲突拆开,找到可以落地的改法。

卷一的目录与学习路径在《互联网产品的客户端架构·端形态与体验基础》目录 里。第 1 章从四个独有约束开始,1.2 节把「回声」客户端收敛成第一个决定,1.3 节说明实验工程的三级视图。读完这三节,再决定后面怎么读。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「books」更多文章

  1. 《互联网产品的 AI 架构·平台化与治理》附录 C 平台化评审清单
  2. 《互联网产品的 AI 架构·平台化与治理》附录 B 能力目录与配额字段速查
  3. 《互联网产品的 AI 架构·平台化与治理》附录 A 治理角色与职责速查