react-native-mmkv 是微信团队 MMKV 库的 RN 移植版,基于 JSI(JavaScript Interface)实现同步读写,性能比基于 Bridge 的 AsyncStorage 快约 30 倍。它是 RN 新架构在生态库中最典型的 JSI 应用之一——理解 MMKV 的工作方式,就理解了 JSI 如何从根本上改变了 JS 与 Native 的通信模型。
概述:RN 持久化的问题
RN 应用需要持久化用户偏好、业务数据、缓存等小体量高频读写数据。AsyncStorage 是最早的方案,但它有三个固有限制:
- 强制异步。底层走 Bridge,每次读写要跨 JS 和 Native 两趟 JSON 序列化/反序列化。
- 全量序列化。改一个字段也要重新序列化整个 value。
- 性能天花板低。1000 次连续读取约 15000ms(MMKV 约 50ms)。
这些问题是 Bridge 架构的固有缺陷——不换通信模型就修不好。MMKV + JSI 从底层替换了通信方式。
RN 持久化迭代史
- AsyncStorage(2015—):RN 最初内置,后拆到
@react-native-async-storage/async-storage。模仿 WeblocalStorage但强制异步。底层 SQLite。 - SQLite(2016—):
react-native-sqlite-storage,完整 SQL 能力。适合结构化查询,但 KV 场景过重。 - MMKV(2021—,RN 移植):微信 2018 年开源的 C++ KV 库,Marc Rousavy 基于 JSI 移植到 RN。mmap + protobuf,同步读写。
- WatermelonDB:面向大规模离线应用(几万条记录 + 复杂查询),底层 SQLite + Observable 层。
- Realm/Atlas Device SDK:嵌入式对象数据库 + 云同步,bundle size 约 5MB,远重于 MMKV 的约 0.5MB。
工作方式
底层存储:mmap + protobuf
mmap(内存映射文件)
传统文件 I/O 需要两次内存拷贝(磁盘 → 内核缓冲区 → 用户缓冲区)和两次上下文切换。mmap 把文件直接映射到进程的虚拟地址空间——读写文件变成读写内存地址,操作系统在背后异步刷脏页回磁盘。
MMKV 名字里的 “MM” 就是 Memory Mapped。
protobuf 编码
MMKV 不用 JSON,用类似 Protocol Buffers 的紧凑二进制编码。关键优势不在于压缩率,而在于追加写入:新数据直接追加到文件末尾,不需要重新编码整个文件。JSON 改一个字段就要重新生成整个字符串。
写入流程
MMKV.set('key', value)
→ protobuf 编码
→ 追加到 mmap 映射的内存区域
→ 同步返回
→ 操作系统后台刷脏页到磁盘
没有系统调用(mmap 之后读写是普通内存操作),没有 JSON 序列化,没有异步回调。
通信层:JSI 替代 Bridge
MMKV 本身是 C++ 库。在旧架构下,JS 调用 C++ 必须经过 Bridge:
AsyncStorage 流程:
JS → JSON.stringify → Bridge 队列(异步) → Native JSON.parse
→ SQLite → JSON.stringify → Bridge 队列(异步) → JS JSON.parse → resolve(Promise)
MMKV + JSI 流程:
JS → JSI 直调 C++ → 读 mmap 内存 → 同步返回
JSI(JavaScript Interface)让 JS 通过 Host Object 直接持有 C++ 对象引用,调用时同步执行 C++ 代码,无 JSON 序列化、无队列等待。不是 Bridge 的优化版,而是完全不同的通信模型。
性能差距来源(约 30 倍)
- Bridge 往返延迟:两次异步消息传递 + 队列等待(最大瓶颈)
- JSON 双向序列化:每次通信 stringify + parse
- 底层存储差异:SQLite 查询 vs mmap 内存读取
- Promise 调度:async/await 微任务开销
Zustand persist 集成
react-native-mmkv 的 API(getString/set/delete)和 Zustand persist middleware 要求的 StateStorage 接口(getItem/setItem/removeItem)几乎一对一映射,只需要一个薄 adapter:
export const mmkvStorage: StateStorage = {
getItem(name) { return storage.getString(name) ?? null },
setItem(name, value) { storage.set(name, value) },
removeItem(name) { storage.delete(name) },
}
三个方法都是同步的(MMKV + JSI 的核心优势),但 Zustand persist 的接口允许返回 Promise,所以同步返回也完全兼容。
缺点
- 不支持复杂查询。想按条件筛选数据,只能整个读出来在 JS 里 filter。数据量大时有瓶颈。
- 追加写入导致文件膨胀。更新 key 不覆盖旧值而是追加。无效数据超阈值时触发同步全量重写(
trim),大文件时有可感知卡顿。 - 不适合大体积数据。mmap 把整个文件映射到虚拟内存,超过几 MB 应该用 SQLite。
方案对比
| 维度 | AsyncStorage | MMKV | SQLite | WatermelonDB |
|---|---|---|---|---|
| 通信机制 | Bridge(异步) | JSI(同步) | 视库而定 | 视版本而定 |
| 数据模型 | string → string | key → string/number/bool | SQL 表 | Observable 对象 |
| 适用场景 | 旧项目兼容 | 偏好/状态持久化 | 结构化查询 | 大量记录 + 实时 UI |
| Bundle size | ~50 KB | ~500 KB | ~1.5 MB | ~2 MB |
| 加密支持 | 无 | 内置 AES-128 | 需 SQLCipher | 依赖底层 |
面试视角
为什么 MMKV 比 AsyncStorage 快?
三层:通信层(JSI 同步调用 vs Bridge 异步队列 + JSON 序列化)、存储层(mmap 内存读写 vs SQLite 查询)、接口层(同步返回 vs Promise 调度)。约 30 倍差距。
JSI 和 Bridge 的根本区别?
Bridge 是两个独立世界用 JSON 消息异步通信;JSI 是同一个进程里 JS 通过 Host Object 直接调用 C++ 函数——同步、无序列化、直接内存访问。
MMKV 数据量大了怎么办?
MMKV 适合 KV 场景(几百 KB 到低 MB)。数据增长到几万条记录时,应该引入 SQLite 做结构化查询,或在 MMKV 里按维度做 key 分片。
版本说明
本页基于 MindGym M1 阶段(Expo SDK 54, react-native-mmkv 3.x, 2026-04-13)。
参见
- 2026-04-13-React Native 新架构概览
- 2026-04-13-Metro Hermes 与 JSI
- mmap
- Protocol Buffers
- Zustand
参考
- react-native-mmkv — Benchmarks 和 “How it works” 部分
- 微信 MMKV — wiki 的 Design 页面详解 mmap/protobuf/CRC
- RN 新架构文档 — JSI — Host Object 完整生命周期
- Marc Rousavy JSI 教程 — 从零实现 JSI 模块