前端架构实战问题记录
记录手写框架内核、低代码出码、高并发活动页设计中踩过的坑,每个问题都是实际遇到过并解决掉的。
问题一:递归渲染为什么不可中断
现象:想给大列表实现"渲染中途响应输入",发现递归渲染 vNode 树根本停不下来。
原因:递归的调用栈在浏览器里一旦开始无法暂停——1000 个节点必须一口气算完。
解决:Fiber 的思路是把树拍平成链表(child ↓ / sibling → / return ↑),遍历用显式循环 + 指针替代递归,每个节点处理完都是一个可暂停点:
function workLoop(deadline: IdleDeadline) {
while (nextUnitOfWork && deadline.timeRemaining() > 1) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork); // 逐节点,可中断
}
if (!nextUnitOfWork && wipRoot) commitRoot(); // commit 不可中断,DOM 原子完成
requestIdleCallback(workLoop);
}
双缓存树(current/WIP):render 可中断地构建 WIP,commit 一次性切换,用户永远看到完整 UI。高优输入插队时低优 WIP 作废重来——这就是 transition 中断的底层。
问题二:列表重排的 DOM 移动次数太多
现象:拖拽排序后 diff 触发大量 insertBefore,掉帧。
原因:朴素做法按新顺序逐个移动,每次移动都可能让后面节点的"正确位置"失效。
解决:最长递增子序列(LIS)——在新序列里找出本来就相对有序的节点子集(一个不动),只移动其余节点,移动次数 = n - LIS 长度,最优。key 纪律:不能用 index 当 key(删中间一行后输入框状态串位)。乱序频繁的列表直接用绝对定位 + transform(FLIP)绕开 DOM 移动。
问题三:低代码拖出来的页面,开发接手就想重写
现象:平台上线半年,出码生成的代码没人维护,改过的页面和 schema 对不上。
原因:四个根因——schema 无版本(历史页面停在旧协议)、无语义命名(comp_123 遍地)、样式全内联(没法接主题)、出码后代码与 schema 双向漂移。
解决:架构上分两条流水线——交互密度低的营销页运行时渲染(改完即生效),核心业务页出码进 git(可测试可定制)。出码四原则:
- schema 带
schemaVersion+ 迁移器 - AST 生成代码,不是字符串拼接
- 组件/字段语义命名进 schema
- 单向原则:出码后代码是唯一真相,schema 停止参与
AI 生成页面(LLM + DSL)必须加三层校验:schema 校验器 + 沙箱预览 + 组件白名单,否则幻觉节点直接进生产。
问题四:秒杀按钮被脚本打爆
现象:前端按钮置灰 + 令牌桶都做了,脚本用户每秒还是 1000 次请求。
原因:前端限流"防君子不防小人"——它过滤的是正常用户连点,真正的防线必须按"请求发出后"设计。
解决:三层防线各管各的:
- 入口风控(请求前):设备指纹 + 行为特征识别机器
- 频率控制:前端令牌桶只做粗节流,服务端限流才是扛量的层(按 10 倍流量压测容量)
- 体验降级(扛不住时):验证码把机器成本转嫁回人、排队页推送进度、备用静态兜底
细节:倒计时用服务端时间校准(本地时间可改)、下单结果用推送不用轮询、展示的"剩余库存"是广播快照(防黄牛探测,提交后才强一致校验)。
问题五:主 CDN 被打挂,页面还打得开
现象:一次故障主 CDN 全域超时,用户白屏投诉。
原因:单 CDN 无冗余,且页面没有离线兜底路径。
解决:
- 多 CDN 主备 + 探活脚本自动切换(超时计数防抖,避免半挂状态反复横跳);注意主备同一个源站时价值有限,静态资源要构建期打散多源
- 应用壳用 Workbox
StaleWhileRevalidate(灾时命中旧缓存),静态资源 immutable 长缓存,HTML 不过长缓存 - 核心接口超时走本地兜底数据(静态内容内置包里),非核心接口静默失败
- 错误率阈值自动触发降级开关(远程配置),不等值班人醒来
- 容灾必须定期演练(预发摘除主 CDN),没演练过的容灾等于没有
