localStorage可保存Web Components模板状态:在connectedCallback中读取,input事件中写入;需过滤XSS、压缩HTML、防抖写入,并避免存dataset或outerHTML。

如何用 localStorage 保存 Web Components 的模板状态
Web Components 的 Shadow DOM 天然隔离样式和逻辑,但它的状态默认不跨刷新保留。要让自定义元素(比如 <my-editor>)恢复上次编辑的 HTML 内容,不能只靠 innerHTML 赋值,得配合 localStorage 在生命周期中主动存取。
关键点在于:组件挂载后立即读取,用户修改后立即写入。不要等表单提交或页面卸载——那些时机太晚,刷新就丢了。
- 在
connectedCallback中调用localStorage.getItem('my-editor-content'),并用this.shadowRoot.innerHTML = ...恢复内容 - 监听内部
contenteditable元素的input事件(不是change),触发时立刻执行localStorage.setItem('my-editor-content', this.shadowRoot.querySelector('[contenteditable]').innerHTML) - 如果模板里含
<slot>或动态插入的子组件,确保只序列化你真正想持久化的部分,避免把整个 shadowRoot.outerHTML 存进去(会混入框架生成的 id、事件绑定残留)
为什么不能直接存 dataset 或 data-* 属性
dataset 是只读代理,改它不会写回 DOM;data-* 属性本身是静态的,刷新即丢。它们适合标记初始配置(如 data-mode="preview"),但绝不能当状态容器用。
常见误操作:this.dataset.lastHtml = htmlString —— 这行代码完全没效果,DevTools 看不到属性变化,后续 getAttribute('data-last-html') 也拿不到值。
立即学习“前端免费学习笔记(深入)”;
- 真要写入 HTML 属性,必须用
this.setAttribute('data-last-html', htmlString) - 但即便这样,也不解决持久化问题:刷新后属性还在,但值仍是初始 HTML,不是用户最后输入的内容
- 所以
data-*只能做“元数据锚点”,比如告诉组件从哪个localStorage键读取真实状态:const key = this.getAttribute('data-storage-key') || 'default-key'
存 HTML 字符串前必须过滤 XSS 吗
必须。如果你允许用户输入任意 HTML(比如富文本编辑器),直接 localStorage.setItem('raw-html', userHtml),下次恢复时 innerHTML = ... 就可能执行恶意脚本——localStorage 不做任何净化,它只是字符串搬运工。
- 生产环境务必在存入前调用
DOMPurify.sanitize(userHtml),再存;恢复时直接赋值即可 - 不要图省事用
innerText替代innerHTML来规避风险——这等于放弃所有格式(加粗、链接、列表),违背使用 HTML 存储的初衷 - 如果项目没引入 DOMPurify,至少剔除
<script>、onerror=、javascript:等关键词(正则不可靠,仅作兜底)
localStorage 容量不够存大段 HTML 怎么办
5 MB 是硬限制,但实际可用远小于此:UTF-8 编码下中文字符占 3 字节,带大量内联样式和 class 的 HTML 很快就超。别等到报 QuotaExceededError 才处理。
- 先压缩:用
html-minifier或简易正则(如.replace(/\s+/g, ' '))去除空白,可省 20–40% 体积 - 再裁剪:对编辑器类组件,只存可视区域内的 HTML,或按段落分片存到多个键(
'editor-chunk-0','editor-chunk-1') - 最后降级:超过阈值时自动 fallback 到
sessionStorage(标签页级)或提示用户导出为文件,而不是静默失败
最易被忽略的是:localStorage 的写入是同步阻塞的,大 HTML 字符串(>100KB)可能导致主线程卡顿。高频输入场景建议加防抖(如 300ms 延迟写入),而非每次 input 都触发 setItem。



















