用 React Native 与 Expo 设计离线笔记
设计React Native与Expo读书笔记的数据表和同步队列,处理持久保存、重复请求、版本冲突与平台权限。
实现本地笔记与待同步操作,并验证断网重启、重复重试、冲突处理及凭证和正文分开存储。
读书笔记可以先在设备保存,再同步到服务端。网络断开时仍能编辑,恢复连接后又不会把同一次编辑写两遍,需要本地存储、操作身份和服务器确认共同配合。React 的页面状态只负责其中一层。
本课用合成笔记 N-1 和操作 OP-1 设计流程。第一版支持列表、编辑与显式同步,不请求相机或通讯录权限,也不承诺后台始终运行。以后添加附件时再申请所需权限。
React Native 与 Expo 的边界
React Native 通过 React 组件描述界面,并使用平台组件与能力。View、Text 等不是网页 DOM 元素,浏览器里的样式和文件接口不能原样套用。Expo 提供工程、模块和开发工具,但目标系统的权限、原生配置和产物仍需分别验证。
Expo Go 与 development build 支持的原生模块范围不同。添加数据库、文件或其他模块前,先查所选 SDK 的文档并确认测试环境支持。不能因为模块在 JavaScript 中可以 import,就假定真机安装包包含了对应原生实现。
创建工程时使用当前官方推荐命令,并保留 SDK 条件。截至本次来源核对,创建页处于 SDK 58 过渡说明,未指定模板和显式指定模板可能产生不同版本。读者应按当时页面选择兼容设备的模板,记录生成版本,不把课程中的数字当长期固定要求。
本地数据表先决定恢复能力
笔记记录包含 ID、正文、本地修改序号、服务器基准版本和更新时间。同步操作另存 ID、笔记 ID、提交内容、基准版本与状态。操作内容应是发送时的快照,不能只引用不断变化的页面变量,否则重试可能发送用户后来改过的内容。
本地保存与写入队列最好处于同一事务边界。保存成功才显示本地已保存,队列存在才显示等待同步。列表由持久仓库加载,useState 或组件生命周期不会替应用完成跨进程恢复。
选择存储时按数据特点分开。官方存储页将 SQLite 用于持久数据库,将 Async Storage 描述为未加密的异步键值存储;后者适合非敏感的小型偏好,不能当秘密保险箱。小型凭证可评估 SecureStore,正文数据库与凭证存储分开管理。
把工程按数据职责接起来
在已选定版本的 Expo 工程中建立笔记模型、仓库、列表页和编辑页。使用 TextInput 接收正文,显示保存状态与同步状态。先通过内存仓库实现创建和修改,再按官方模块安装说明加入持久存储,保留所需原生配置。
创建数据库表并设置 schema 版本。启动时打开数据库、完成迁移,再查询笔记;读取失败显示错误,不自动清空数据库。编辑过程中按明确节奏保存,处理快速切换笔记时的待保存内容;离开前的提示应依据是否仍有未落盘编辑。
随后建立受控服务端端点。它校验账户、笔记归属、字段大小和基准版本,返回操作 ID 与确认版本。客户端提交待办,成功后只清理对应操作,不能把整张队列标为成功。先做主动同步,再评估系统允许的后台机制。
重试需要稳定操作 ID
超时可能发生在服务器已保存之后。客户端重试同一操作时沿用原 ID,服务器识别重复请求并返回已有结果。仅在客户端按钮上禁止连点,不能处理响应丢失或进程重启。
下面 Python 示例用集合记录处理过的操作。它验证同一 OP-1 不会再次更新笔记,没有数据库事务、网络、认证或真实 Expo 运行环境。
server = {}
seen = set()
def sync(operation):
if operation["id"] in seen:
return False
server[operation["note"]] = operation["text"]
seen.add(operation["id"])
return True
operation = {"id": "OP-1", "note": "N-1", "text": "draft"}
assert sync(operation)
assert not sync(operation)
assert server["N-1"] == "draft"
print(server)
输出 {'N-1': 'draft'}。第一次返回真,第二次返回假,服务端字典保留一次写入。真实服务必须将幂等记录与业务更新放在合适事务中;这里的内存集合会随进程结束消失,不能用于生产去重。
冲突不能用重试抹掉
客户端提交的基准版本落后时,服务器应报告冲突,附上当前版本或提供读取方式。界面保留本地草稿,显示远端内容并让用户明确选择。重试只解决暂时失败,不能让旧基准忽然变成有效版本。
用户合并后创建新操作,以最新确认版本为基准。旧操作进入已处理或已替代状态,不能继续在队列里无限重试。若前一个操作确认时用户已经继续编辑,只更新其确认信息,不把新编辑误标为已同步。
不要仅以手机更新时间决定覆盖顺序。设备时钟可能不同,正文也可能同时包含需要保留的信息。删除作为有身份的同步操作处理,否则离线设备重新上传旧记录可能让已删除笔记再次出现。
权限、错误和重启恢复
只有功能确实需要时才请求系统权限,并提供拒绝后的替代路径。普通正文输入无需读取通讯录。附件选择应由用户主动发起,描述上传内容与用途,不把授权当作一次性获得所有设备数据。
启动后枚举待同步操作,将上次中断的发送恢复为可重试。认证失败要求恢复会话,不在日志打印令牌;本地空间不足时保留未保存提示,不能宣称成功。卸载、系统存储处理和备份行为需按目标平台核对,本地保存不等于永远不会丢失。
编辑器还要处理中文输入法与快速切换笔记。不要在每个输入事件后重置光标;保存任务记录对应笔记ID和内容快照,切换后晚到的确认只能修改原笔记状态。恢复时展示最后落盘内容,并区分仍在编辑器内的未保存输入。
为同步队列设定容量和失败策略。大量操作不应一次并行发送;按照笔记版本依赖顺序处理,暂时失败可以延后,永久字段错误等待用户修正。可合并的连续编辑要保证已发送操作的身份不变,并保留删除等具有独立语义的动作。先用两条依赖操作验证,再增加批量优化。
练习:验证两个设备版本的分歧
先在一个测试设备创建笔记、断网编辑、终止应用并重开,正文和队列应恢复。模拟服务器已处理但响应丢失,重复发送相同操作后仍只有一次有效更新。
再模拟另一客户端修改了服务器版本。当前设备应进入冲突,保留草稿并能生成合并后的新操作。分别在 Android 和 iOS 检查键盘、退出恢复与模块配置,标记未验证项目。验收对照页面、数据库和服务器确认,不把 Python 去重示例当成完整同步应用。
资料来源与延伸阅读
资料核验:2026-11-10。本文为原创工程讲解,教学示例不代表线上实测。