最稳方案是 fetch() + insertAdjacentHTML('beforeend', newHtml),配状态锁、错误兜底、IntersectionObserver 懒加载及本地状态分离,避免 DOM 重置与跳动。

直接用 fetch() + insertAdjacentHTML() 是最稳的起点,但必须配状态锁和错误兜底,否则滚动错乱、重复请求、空白页会立刻出现。
为什么不用 innerHTML = 直接替换
它会无差别销毁整个子树:已绑定的点击事件消失、input 光标重置、滚动位置归零。尤其在列表底部加载新内容时,用户正在看第 5 条,一刷新就跳回顶部。更隐蔽的问题是:如果旧 DOM 里有通过 addEventListener 绑定的监听器(比如“展开详情”按钮),全都没了,得手动重绑——实际项目里几乎没人做这事。
推荐改用:insertAdjacentHTML('beforeend', newHtml),只追加不破坏;或 replaceChildren(...nodes)(现代浏览器),可传真实节点,天然防 XSS 且保留部分父级状态。
fetch() 请求失败后页面变空白怎么办
常见错误是没检查 response.ok 就直接 .text(),结果 404/500 返回的 HTML 错误页被当内容插入,显示一堆服务端报错文本,甚至带 script 标签执行失败。
- 必须加判断:
if (!response.ok) throw new Error(`HTTP ${response.status}`) - 后端返回的应是纯 HTML 片段(不含
、),不是完整页面 - 前端要设 loading 状态,失败时恢复旧内容或显示 fallback 提示,而不是留空
- 建议加 retry 机制(最多 2 次),用指数退避:
setTimeout(() => fetch(...), 1000 * Math.pow(2, attempt))
懒加载到底该监听哪个元素
别监听列表容器本身——它高度随内容增长,容易误触发多次。正确做法是:在列表末尾放一个固定高度的占位节点,比如 ,再用 IntersectionObserver 监听它。
生成可在 deepbi 沙箱 iframe 内运行或作为独立文件运行的交互式 HTML 页面,使用 `window.deepbi` JS SDK 实现数据查询、空间管理、文件浏览及用户操作。当用户提出以下需求时启用该能力: - 创建 KPI 仪表盘、数据表格、文件浏览器、成员列表、管理页面; - 明确要求“转成 dhtml”、“转译成 HTML”、“把 XX 页面转成……”、“控制台页面”、“管理后台”、“DeepBI 页面迁移”; - 提及位于 `apps/console/src/featur
关键细节:
- 创建 observer 时设
{ root: null, threshold: 0 },确保刚进入视口就触发 - 回调里立即
observer.unobserve(triggerEl),避免重复加载 - 加载成功后,把新内容插入前,先用
document.getElementById('load-trigger').remove()清掉旧占位符,再 append 新的,否则 DOM 节点越积越多 - 加个
loading = false标志位,防止快速滚动时触发并发请求
实时更新时 DOM 状态丢失怎么救
比如聊天窗口自动滚动到底部,但每次刷新都得手动调 element.scrollTop = element.scrollHeight;或者某条消息右侧有个“已读”标记,刷新后又变回“未读”。这类状态不能存在 HTML 字符串里,得抽出来单独维护。
可行办法:
- 用
Map存每条数据的本地状态:const messageStatus = new Map(); messageStatus.set('msg-123', 'read') - 渲染新内容后,遍历新增节点,查 Map 补状态,再 patch DOM
- 对需要保持焦点的输入框,用
element.focus()+element.setSelectionRange()恢复光标位置 - 慎用
key属性模拟 Vue/React 行为——原生 JS 没虚拟 DOM,硬套反而增加复杂度
真正难的不是“怎么刷”,而是“刷完怎么让页面看起来没刷过”。状态分离、增量操作、防御性 DOM 处理,这三块漏掉任何一块,用户都会明显感觉到卡顿或跳动。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










