CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
本地存储
这一课你将能做到
用 AsyncStorage 存取用户偏好和一般数据,封装成带类型的 storage 工具
说出 AsyncStorage / MMKV / SQLite / SecureStore 的区别,给场景就能选对
用 SecureStore 安全保存 token,并实现「启动时恢复登录态」的标准模式
为什么要学
App 杀掉重开,登录态还在;夜间模式设置不会丢;离线时能看到缓存的列表——这些都靠本地存储。存错地方后果很严重:token 明文放 AsyncStorage,拿 adb 或越狱工具一翻就到手;存大段 JSON 到 SecureStore,超出容量上限直接崩。
人话:手机本地存储就像家里的储物方案——抽屉(AsyncStorage)放日常杂物,保险柜(SecureStore)放存折和证件,档案柜(SQLite)放需要检索的大量资料,速度型寄存器(MMKV)放频繁改写的数据。东西放错地方,要么难找,要么危险。
先建立图像
import AsyncStorage from '@react-native-async-storage/async-storage';
// 存:只能存字符串,返回 Promise
await AsyncStorage.setItem('theme', 'dark');
// 取:取不到返回 null
const theme = await AsyncStorage.getItem('theme'); // 'dark'
// 删
await AsyncStorage.removeItem('theme');
AsyncStorage 的 API 和 Web 的 localStorage 长得几乎一样,但有两条天差地别:第一,全部异步 (返回 Promise,不写 await 拿到的是 Promise 不是值);第二,只能存字符串 (对象数组必须 JSON.stringify)。
跟着做
步骤 1:安装 AsyncStorage
npx expo install @react-native-async-storage/async-storage
步骤 2:封装带类型的存储工具
裸用 AsyncStorage 容易出错:存对象忘 stringify、读取忘 parse、key 命名冲突。封装一层:
// utils/storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';
export const storage = {
async set(key, value) {
await AsyncStorage.setItem(key, JSON.stringify(value));
},
async get(key) {
const raw = await AsyncStorage.getItem(key);
try {
return raw ? JSON.parse(raw) : null;
} catch {
return raw; // 万一存的不是 JSON,返回原字符串兜底
}
},
async remove(key) {
await AsyncStorage.removeItem(key);
},
};
// 使用:对象数组直接存,不用手写 stringify
await storage.set('user', { name: 'Tom', age: 20 });
const user = await storage.get('user');
AsyncStorage 只能存字符串,这是它和 Web localStorage 最大的坑。忘掉序列化,你读到的永远是 "[object Object]"。
步骤 3:App 启动时恢复登录态(checking 模式)
经典模式:启动时先读存储,读到用户就进主页,没读到去登录。不这么做,已登录用户会看到一闪而过的登录页——体验极差。
import { useState, useEffect } from 'react';
import { ActivityIndicator } from 'react-native';
import { storage } from './utils/storage';
function App() {
const [user, setUser] = useState(null);
const [checking, setChecking] = useState(true);
useEffect(() => {
(async () => {
const saved = await storage.get('user');
if (saved) setUser(saved);
setChecking(false);
})();
}, []);
if (checking) return <ActivityIndicator style={{ flex: 1 }} />;
return user ? <MainTabs /> : <LoginScreen onLogin={setUser} />;
}
关键细节:checking 状态必须先设为 true,等异步读取完成再决定渲染哪个页面。这个「白屏转圈 → 路由决定」的模式是行业标准,不要一上来就渲染登录页再异步跳转。
步骤 4:token 用 SecureStore npx expo install expo-secure-store
import * as SecureStore from 'expo-secure-store';
// 登录成功后写入
await SecureStore.setItemAsync('token', 'eyJhbGciOiJIUzI1NiIs...');
// 请求拦截器里取出(见第 7 课)
const token = await SecureStore.getItemAsync('token');
// 退出登录时清理
await SecureStore.deleteItemAsync('token');
SecureStore 底层用 iOS Keychain / Android Keystore (系统级加密),数据不是明文放在沙盒文件里,而是交给系统的安全模块。即使手机被 root/越狱,数据也不会随便裸奔——这是和 AsyncStorage 的本质区别。
注意限制:SecureStore 单条数据约 2KB,它就是给 token、密钥设计的。别往里塞用户对象或配置列表,那会超限报错。
步骤 5:认识 MMKV(高速 KV 存储) npx expo install react-native-mmkv
import { MMKV } from 'react-native-mmkv';
const mmkv = new MMKV();
mmkv.set('theme', 'dark'); // 同步 API,不用 await
const theme = mmkv.getString('theme'); // 直接返回值,没有 Promise
mmkv.set('count', 42); // 存数字
const count = mmkv.getNumber('count');
MMKV 的优势:API 同步(不用 await),读写比 AsyncStorage 快几十倍。劣势:需要原生模块,Expo Go 里跑不了(要自定义开发构建)。所以高频读写(埋点队列、本地草稿)上 MMKV,普通配置用 AsyncStorage。
步骤 6:认识 expo-sqlite(结构化存储) npx expo install expo-sqlite
import * as SQLite from 'expo-sqlite';
const db = await SQLite.openDatabaseAsync('app.db');
await db.execAsync(`
CREATE TABLE IF NOT EXISTS msgs (
id INTEGER PRIMARY KEY,
text TEXT,
created_at INTEGER
)
`);
await db.runAsync('INSERT INTO msgs (text, created_at) VALUES (?, ?)', '你好', Date.now());
const rows = await db.getAllAsync('SELECT * FROM msgs ORDER BY created_at DESC');
SQLite 适合结构化查询场景:聊天记录需要按关键字检索、离线表单有多字段过滤、本地要做 JOIN 关联。纯 KV 存这些会一团糟。
步骤 7:退出登录清理策略 退登不能只清 state,必须同时清掉所有持久化数据,否则下一个用户能打开 App 看到上一个的数据:
async function logout() {
setUser(null);
await SecureStore.deleteItemAsync('token');
await storage.remove('user');
await storage.remove('cart');
await storage.remove('drafts');
// 全局状态库也要清空,如 Zustand 的 persist 层
}
讲清楚要点
AsyncStorage 全异步 :这是它和 Web localStorage 最大的坑,忘记 await 拿到的是 Promise 不是值。设置后立刻读取也可能读到旧值——await 的 Promise 没 resolve 之前,读取拿不到刚写入的内容。
只存字符串 :对象数组必须 JSON.stringify,读取时 parse。封装一层一劳永逸。
选型口诀 :一般偏好配置 → AsyncStorage;高频读写/大数据量 KV → MMKV;敏感凭证(token/密钥)→ SecureStore;结构化查询(聊天记录/离线数据)→ SQLite。
启动时恢复状态是标准模式 :读存储期间显示启动屏/转圈,读完再决定渲染哪个页面。跳过 checking 会导致已登录用户闪一下登录页。
退出登录要全清 :清 token、清用户缓存、重置全局状态,三者缺一,下一个用户可能看到上一个的数据。
你可能会懵的地方
AsyncStorage 容量多大?
Android 默认 6MB 上限(可调),iOS 基本无硬限制。存图片、大文件请用 expo-file-system,KV 存储只放小数据。试图把图片 base64 塞进去,性能和容量都会爆。
MMKV 那么好,为什么还用 AsyncStorage?
MMKV 需要原生模块,Expo Go 里跑不了(得用自定义 dev build),安装复杂度高一档。普通 KV 场景 AsyncStorage 性能完全够用;性能敏感(埋点、本地草稿频繁写入)再上 MMKV。
SecureStore 能存多大?
单条 2KB 警告线,它就是给 token、密钥设计的。别往里塞用户对象或 JSON 配置。
存进去的数据卸载 App 会丢吗?
会。iOS 的 Keychain 数据在卸载后可能保留,这是 iOS 安全体系的设计特性,不是 bug。开发测试时反复安装可能读取到旧 token——这正是好事(用户重装 App 不用重新登录),但开发阶段要注意清理。
AsyncStorage 的 key 冲突怎么办?
按命名空间写前缀:user:profile、cache:list:home、setting:theme,别上来就写个 data。
错法对照 错法 问题 正解 忘写 await 拿到 Promise,判断永远为真(truthy) 所有存储调用都 await 直接存对象 读出 "[object Object]" 封装层 JSON.stringify / parse token 存 AsyncStorage 明文裸奔,可被导出 SecureStore 加密存储 把大段 JSON/图片塞 KV 卡顿、超容量 大数据用 SQLite 或文件系统 退出登录只清 state 重开 App 又自动登录了 同时清 SecureStore 和缓存 启动时不等读取完成就渲染 已登录用户闪一下登录页 checking 状态控制启动屏 SecureStore 超限存大数据 报错或数据截断 只存 token 和小段字符串
卡点急救
存了但读不出来 :确认 key 完全一致(大小写敏感);确认读取前 set 的 Promise 已 resolve(await 到位)。
SecureStore 在 Expo Go 报错 :部分旧 SDK 版本有兼容问题,升级到最新 expo-secure-store 并 npx expo start -c 清缓存。
数据莫名其妙没了 :检查是不是别处写了同 key 覆盖;或测试时卸载了 App。
MMKV 初始化报错 :检查是否在 Expo Go 中运行——MMKV 需要自定义开发构建,EAS Build 出的包才包含原生模块。
面试常问
AsyncStorage 的原理和局限?
iOS 基于 NSUserDefaults/文件、Android 基于 SQLite/RocksDB 的键值存储,异步 API 走原生桥。局限:容量小、只能存字符串、性能一般,不适合高频读写。底层数据在手机越狱/root 后可被导出。
MMKV 为什么快?
基于 mmap 内存映射文件,读写直接在内存页上操作,由操作系统异步落盘;且 API 同步,不用跨桥等回调。微信内部大量使用,经过高并发验证。
敏感信息怎么存?
token、密钥放 SecureStore(iOS Keychain / Android Keystore,硬件级加密);绝不硬编码在代码里;传输走 https;必要时做 token 短期化 + refreshToken 刷新机制。
自测三问
AsyncStorage 和 localStorage 的两个最大区别是什么?
token 应该放哪里?为什么不能放 AsyncStorage?
需要存 10 万条聊天记录并能按关键字搜索,选什么方案?为什么不用 AsyncStorage?
小结一句话 本地存储选型的口诀:杂物 AsyncStorage、凭证 SecureStore、高频 MMKV、结构化 SQLite——放对地方,既安全又高效。
延伸