CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
React Native 开发入门/课时/本地存储
RawBlame
课时目录12 节

React Native 开发入门

  1. 01RN 与 Expo
  2. 02核心组件
  3. 03布局与列表
  4. 04平台差异
  5. 05导航与手势
  6. 06State 与 JSX
  7. 07网络
  8. 08本地存储
  9. 09原生模块
  10. 10动画
  11. 11性能优化
  12. 12测试与发布

本地存储

SecureStore/SQLite。

30 分钟

本地存储

这一课你将能做到

  1. 用 AsyncStorage 存取用户偏好和一般数据,封装成带类型的 storage 工具
  2. 说出 AsyncStorage / MMKV / SQLite / SecureStore 的区别,给场景就能选对
  3. 用 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 层
}

讲清楚要点

  1. AsyncStorage 全异步:这是它和 Web localStorage 最大的坑,忘记 await 拿到的是 Promise 不是值。设置后立刻读取也可能读到旧值——await 的 Promise 没 resolve 之前,读取拿不到刚写入的内容。
  2. 只存字符串:对象数组必须 JSON.stringify,读取时 parse。封装一层一劳永逸。
  3. 选型口诀:一般偏好配置 → AsyncStorage;高频读写/大数据量 KV → MMKV;敏感凭证(token/密钥)→ SecureStore;结构化查询(聊天记录/离线数据)→ SQLite。
  4. 启动时恢复状态是标准模式:读存储期间显示启动屏/转圈,读完再决定渲染哪个页面。跳过 checking 会导致已登录用户闪一下登录页。
  5. 退出登录要全清:清 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 和小段字符串

卡点急救

  1. 存了但读不出来:确认 key 完全一致(大小写敏感);确认读取前 set 的 Promise 已 resolve(await 到位)。
  2. SecureStore 在 Expo Go 报错:部分旧 SDK 版本有兼容问题,升级到最新 expo-secure-store 并 npx expo start -c 清缓存。
  3. 数据莫名其妙没了:检查是不是别处写了同 key 覆盖;或测试时卸载了 App。
  4. 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 刷新机制。

自测三问

  1. AsyncStorage 和 localStorage 的两个最大区别是什么?
  2. token 应该放哪里?为什么不能放 AsyncStorage?
  3. 需要存 10 万条聊天记录并能按关键字搜索,选什么方案?为什么不用 AsyncStorage?

小结一句话

本地存储选型的口诀:杂物 AsyncStorage、凭证 SecureStore、高频 MMKV、结构化 SQLite——放对地方,既安全又高效。

延伸

  • Expo SecureStore 文档
  • react-native-mmkv 文档
  • 下一课:原生能力——相机、相册、定位与推送。
加载本节练习…
← 上一课网络下一课 →原生模块