当前位置: 首页 > news >正文

39-设置开关退出就复原-用偏好写队列和回滚状态兜住

第39篇|设置开关退出就复原:用偏好写队列和回滚状态兜住

摘要:设置页开关最容易被写成@State一改就完事。实际项目里,用户快速连点、离开页面、写入失败、重启应用都会暴露问题:UI 显示已开启,持久化里还是旧值。更稳的做法是让设置写入走串行队列,页面可以乐观展示,但失败必须回滚,并用重启验证证明真的保存成功。

我处理过一个提醒开关问题:用户打开“每日提醒”,页面显示已开启,退出再进又变回关闭。日志里没有崩溃,因为 UI 状态确实变了,只是 Preferences 写入被后一次旧值覆盖。更麻烦的是用户快速点三次,最后界面和持久化各拿一个值。这个问题不能靠“加个 await”随便修,要把写入路径收成一个队列。

这篇文章解决四个实际问题:

  1. 区分页面乐观状态和已落盘状态。
  2. 用 SettingWriteQueue 串行处理同一个 key 的写入。
  3. 写入失败时回滚 UI,并给用户可理解提示。
  4. 用退出重进和杀进程重启验证持久化真实生效。


先确认复原发生在哪个时刻

设置复原可能发生在返回页面时,也可能是杀进程重启后。两个现象对应的根因不同,不能只看开关 UI。

位置现象风险处理方向
返回页面复原页面重新读取旧缓存写入还没完成等待写入确认或展示保存中
重启后复原持久化失败只改了内存状态统一写入 Preferences
快速连点错乱并发写同一个 key后完成的旧值覆盖新值串行队列
写入失败无提示catch 后吞错用户以为保存成功失败回滚

这一步的价值是先把问题放回运行链路。只要能确认问题停在哪个位置,后面就不用靠猜测改页面。

页面只能发意图,不能直接散写偏好

设置项看起来简单,但长期维护时会有默认值、迁移、校验和失败回滚。页面直接写 Preferences 会让这些逻辑散得到处都是。

模块应该负责不应该负责
SettingsPage展示值、保存中、错误文案Preferences API
SettingsService设置项校验和保存流程具体 UI 布局
SettingWriteQueue同 key 串行写入业务默认值
PreferenceStore读写持久化连点策略
Migration旧 key 到新 key页面事件

边界定清后,代码就不会在页面、服务和回调之间来回复制同一段判断。后续新增场景也能先判断应该落在哪一层。

设置状态要有 pending 标记

exportinterfaceSettingItemState{key:stringvalue:booleanpersistedValue:booleanpending:booleanerrorText:string}exportfunctioncreateSettingState(key:string,value:boolean):SettingItemState{return{key,value,persistedValue:value,pending:false,errorText:''}}

value是页面当前展示,persistedValue是最后一次确认落盘的值。失败时页面能回滚到真实持久化状态,而不是随便猜一个默认值。

这类模型最好放在modelscommon中,页面、Service 和仓储都使用同一份类型,避免各层用字符串互相猜。

写队列保证同一个 key 顺序执行

exportclassSettingWriteQueue{privatejobs:Map<string,Promise<void>>=newMap()enqueue(key:string,task:()=>Promise<void>):Promise<void>{constprevious=this.jobs.get(key)??Promise.resolve()constnext=previous.then(task).finally(()=>{if(this.jobs.get(key)===next){this.jobs.delete(key)}})this.jobs.set(key,next)returnnext}}

队列按 key 串行,不会让两个提醒开关互相阻塞,但能保证同一个 key 的后一次操作不会被更早的写入覆盖。

Service 的目标不是把所有逻辑都塞进去,而是把跨页面、跨生命周期或需要持久化的判断集中起来。页面只表达用户动作。

页面先乐观更新,再等确认

@Componentstruct ReminderSettingRow{@Stateitem:SettingItemState=createSettingState('daily_reminder',false)privateservice:SettingsService=createSettingsService()asynctoggleReminder(nextValue:boolean):Promise<void>{constbefore=this.item.persistedValuethis.item={...this.item,value:nextValue,pending:true,errorText:''}constsaved=awaitthis.service.saveBoolean(this.item.key,nextValue)if(!saved){this.item={...this.item,value:before,pending:false,errorText:'保存失败,请重试'}return}this.item={...this.item,persistedValue:nextValue,pending:false,errorText:''}}}

乐观更新能让交互及时,但pending和回滚不能省。否则用户看到的是“已开启”,持久化里却可能还是关闭。

页面层要控制展示和交互节奏,但不应该拥有底层事实。这样页面重建、横竖屏变化或返回前台时,都能重新从 Service 拿到可信结果。

SettingsService 统一读写默认值

exportclassSettingsService{constructor(privatereadonlystore:PreferenceStore,privatereadonlyqueue:SettingWriteQueue){}asyncloadBoolean(key:string,defaultValue:boolean):Promise<boolean>{constvalue=awaitthis.store.getBoolean(key)returnvalue??defaultValue}asyncsaveBoolean(key:string,value:boolean):Promise<boolean>{returnthis.queue.enqueue(key,async()=>{awaitthis.store.putBoolean(key,value)awaitthis.store.flush()}).then(()=>true).catch(()=>false)}}

读默认值和写入确认都在 Service 层。页面不用知道是否需要 flush,也不会因为多个页面同时写同一个 key 出现竞争。

实际项目里,很多问题不是第一次进入页面暴露,而是在冷启动、返回前台、页面重建或回调晚到时暴露。把这段生命周期补上,修复才算闭环。

失败路径要回滚而不是假装成功

情况页面表现工程处理
写入失败回滚到 persistedValue展示保存失败
用户快速连点同 key 排队最后一次结果一致
读取旧 key迁移后删除旧 key避免重启读回旧值
页面离开时 pending保留保存中提示或禁用返回不要直接丢状态

兜底路径不是可有可无。用户遇到异常时,页面至少要给出当前状态、可执行动作和可排查线索。

排查时找散写偏好

rg-n"putBoolean|getBoolean|Preferences|flush|daily_reminder"entry features common rg-n"@State.*reminder|persistedValue|pending|SettingWriteQueue"entry features common rg-n"onClick|onChange|Toggle|Switch"entry features common

第一组命令找旧路径,第二组命令找新边界,第三组命令看生命周期或回调位置。命中后不要只看字段名,要判断它是否仍在直接改页面状态。

验证清单

验证项预期结果
打开开关后返回再进仍保持新值
杀进程重启仍读取到新值
快速连点三次最终 UI 和持久化一致
模拟写入失败UI 回滚并显示错误
旧版本迁移旧 key 不再覆盖新 key

这些验证最好在真机或模拟器里按顺序走一遍。只看源码很难发现冷启动、重启和回调晚到这类问题。

常见问题和处理方式

现象常见原因处理方式
退出就复原只改 @State必须走 SettingsService
偶现反向并发写入乱序同 key 串行队列
失败无感catch 后吞错回滚并提示
重启读旧值默认值和旧 key 散落集中迁移和读取

如果现象没有出现在表里,仍然建议按“输入来源 -> 中间状态 -> 持久化或页面展示 -> 失败兜底”的顺序排查。

小结:设置项要把显示和落盘分清

开关状态不是点一下就结束。页面可以先展示用户选择,但服务层必须串行写入、确认落盘、失败回滚,最后用重启验证。这样设置页才不会出现看似保存成功、实际重进复原的问题。
| 默认值和旧 key 散落 | 集中迁移和读取 |

如果现象没有出现在表里,仍然建议按“输入来源 -> 中间状态 -> 持久化或页面展示 -> 失败兜底”的顺序排查。

小结:设置项要把显示和落盘分清

开关状态不是点一下就结束。页面可以先展示用户选择,但服务层必须串行写入、确认落盘、失败回滚,最后用重启验证。这样设置页才不会出现看似保存成功、实际重进复原的问题。

http://www.jsqmd.com/news/1215422/

相关文章:

  • 服装店大货下单前,怎么判断供应商能不能稳定补货?
  • 更新 mellanox ib/RoCEv2网卡的组件 mlxup
  • 2026批量采购平台怎么选:用总到手成本、交期和供货深度比较
  • 远程开发效率提升300%?Cursor云端IDE配置全链路拆解,含VS Code无法替代的3项核心能力
  • 2026乌兰察布宣传片制作公司TOP5排名权威评测报告 | 真实报价对比+产品宣传片与品牌宣传片评测,附行业选型指南 - 影视产业研究
  • 我是文科背景希望转型懂技术懂商业懂管理企业高管-有哪些国内硕士项目-2027清单与交大MTT
  • 【海纳数聚智能办公四件套】海聚智文|AI写作:以智能笔墨,纾解公文伏案之重
  • FSR传感器信号调理与ADC采样方案的技术实现详解
  • 抖店一件代发必看!合规一键下单实操指南,避开店铺处罚 - 电商分享
  • 论无知:分布式
  • 【ChatGPT口语练习黄金法则】:20年语言技术专家亲授——97.3%用户3天突破开口恐惧的5个隐藏指令
  • 河南品牌AI曝光优化
  • github发布pages
  • 3分钟快速上手DBCHM:终极数据库文档生成器完全指南
  • 2026三明宣传片制作公司TOP5权威排名深度实测:独家十维评分+TVC广告与产品宣传片报价对比,附行业选型避坑指南 - 影视产业研究
  • 我的编程学习之旅:目标、计划与展望
  • 写一篇 1000 字以内的文章,要求能过审核,并且带上这款芯片的关键词,增加搜索率
  • 申博文献综述撰写高阶方法论
  • 2026乌兰察布活动跟拍公司TOP5排名权威评测报告 | 真实报价对比+视频直播与活动摄影评测数据,附企业选型避坑权威指南 - 影视产业研究
  • 免费AI语音转文字终极指南:faster-whisper-GUI让你的音频秒变文字
  • 2026三明活动跟拍公司TOP5权威排名深度实测:独家十维评分+照片直播与视频直播报价对比,附选型避坑权威指南 - 影视产业研究
  • ModelSim软件使用问题
  • 40-多端同步消息顺序乱-用本地事件序号和幂等消费收口
  • 课题申报的暴论
  • C语言基础回顾(2026/7/19)
  • C++:5.编译加载过程
  • 2026巴彦淖尔宣传片制作公司五大服务商权威排名深度实测 — 独家评分体系+三维动画与信息流广告制作报价对比,附企业选型避坑指南 - 影视产业研究
  • 韩山师范学院23级“3+1”微专业暑期实训开班仪式圆满举行
  • 【桁架】基于matlab三维桁架优化以实现最大刚度【含Matlab源码 15893期】含报告
  • 芯片异常复位问题(看门狗异常复位)