加载中...

本篇要回答的问题:在正式连服务端之前,先让画图程序支持浏览器端持久化(localStorage)——为了离线可编辑、联网自动同步,DOM 树在内存与在 localStorage 中该怎样分层存储、怎样设计对象 ID 和数据变更粒度?

前两讲(实战一、二)我们让画图程序具备了创建/选择/改样式/删除/移动等基本可用功能,但仍是纯单机内存版。本讲是连接服务端的前半步:先在浏览器端做持久化,以获得更好的离线体验。技术选型上不用 PWA,而用兼容性更好的 localStorage

关键判断:技术选型首先要考虑兼容性。虽然 PWA 很关注离线体验,但本讲基于更传统、兼容性更好的 localStorage 来实现离线。

对象 ID

为支持持久化,给根 QPaintDoc 引入两个 ID,每个 Shape 也引入 ID:

QPaintDoc 引入 localID 与 displayID

ID 含义 是否会变
displayID 用户可见 ID(如 URL #t10001);前缀 t 表示从未与服务器同步过的临时文档 会变:首次同步到服务端后改用服务端返回的 ID
localID 本地 ID(如 10001);QPaintDoc.init 后固定,同一浏览器下唯一 不变

关键洞见:为什么要两个 ID?因为 localStorage 分层存储——QPaintDoc 的 shapes 数组只存 shapeID,每个 shape 单独存 shapeID => shapeJsonData(实际 key 是 localID + ":" + shape.id,保证全局唯一)。若只有一个会变的 ID,那么 ID 一变,这篇文档在 localStorage 里所有图形对象的 key 都得跟着改。引入不变的 localID 就把存储 key 钉死了。

所以首次访问 localhost:8888/ 自动跳 #t10001,第二次跳 #t10002——同一浏览器下不会有两个相同的 localID。

数据变更

数据变更分两级,对应两级存储更新:

级别 触发场景 变更后动作
shapeChanged addShape / setProp(改样式)/ move(移动) 更新 shapeID => shapeJsonData
documentChanged addShape(图形数 +1)/ deleteShape(图形数 -1)/ 未来改 Z-Order(shapes 数组内容变) 更新 localID => documentJsonData

注意 addShape 同时触发两级(既改了 shape,又改了文档图形数量)。

关键洞见:数据变更不只来自本地交互。多人协同编辑场景下变更也来自其他浏览器端,链路是:
Client B 操作 → B 的 DOM 变更 → 服务端数据变更 → A 收到变更 → A 的 DOM 变更 → A 的 View 更新

关键判断:26、27 讲里我们图省事,是 Controller 改完数据后自己qview.invalidateRect 通知重绘——这并不符合标准 MVC。标准 MVC 中界面更新应由 Model 层的 DataChanged 事件触发,而非 Controller 触发。本讲引入两级数据变更事件,正是向标准 MVC 回归。

存储的容量限制与安全

localStorage 容量有限(多数浏览器 5~10M),且同一浏览器下多个 QPaintDoc 共享,占用会越来越大:

问题 对策
容量超限 localStorage_setItem 统一接管 setItem,捕获 QuotaExceededError淘汰最早创建的一篇文档;只要及时联网同步,数据就不丢
隐私安全 localStorage 数据可被查看;多租户登录登出时不同用户文档同存一处,登出后他人仍可见 → 最简单方案是用户登出时清空所有 localStorage 文档

总结

本讲为画图程序加上离线持久化:核心是 DOM 树在内存与在 localStorage 中的差异——为避免每次整篇文档重存,存储分 shape / document 两级,数据变更事件相应分 shapeChanged / documentChanged 两级;用 localID(不变)与 displayID(可变)解决"ID 变化导致所有存储 key 连带改"的问题;并处理了容量淘汰与登出隐私。

先把"是什么"回答清楚

概念 一句话说明
displayID 用户可见、会变的文档 ID;t 前缀表临时未同步
localID 本地不变的文档 ID,钉死 localStorage 的存储 key
分层存储 shapes 数组只存 shapeID,shape 单独存,key 为 localID:shape.id
shapeChanged / documentChanged 两级数据变更,分别更新 shape 与 document 存储
容量淘汰 QuotaExceededError 时淘汰最早创建的文档

一句话速记

离线持久化的关键是分层存储 + 两级变更事件:shapes 数组只存 ID、shape 单独存,用不变的 localID 钉死存储 key(displayID 可变),变更分 shapeChanged/documentChanged;并向"由 DataChanged 触发重绘"的标准 MVC 回归。

几条值得记住的判断

  • 技术选型先看兼容性:离线用 localStorage 而非 PWA。
  • 不变的 localID 隔离"可变的 displayID",避免 ID 变化引发连锁的存储 key 修改。
  • 引入两级 DataChanged 事件,是从"Controller 主动重绘"回归标准 MVC,也为多人协同打底。

思考题

文中用"不变的 localID + 可变的 displayID"把存储 key 与展示 ID 解耦。回到你的系统:你有没有把一个"会变的对外 ID"直接当成了内部主键/存储 key?一旦它需要变更(如对外编号规则调整),会引发多大范围的连锁修改?

公告栏
这是我的个人知识库。
记录技术,也记录生活 —— 读过的、试过的、想明白的,都堆在这儿。
最新文章
网站资讯
文章数目 :
5
已运行时间 :
本站总字数 :
15.7k
本站访客数 :
本站总访问量 :
最后更新时间 :
全局知识图谱
当前页面 已访问 文章 标签
ESC 关闭 · 滚轮缩放 · 拖拽移动 · Ctrl+G 开关