在移动应用开发中,数据持久化(Data Persistence)是绕不开的话题。无论是为了让用户免于重复登录,还是保存用户的偏好设置(如深色模式),我们都需要将数据存储在用户的设备上。
对于使用 Expo 开发的 React Native 项目,官方推荐了两种截然不同的存储方案:AsyncStorage 和 expo-secure-store。
本文将深入解析这两者的区别、适用场景,并教你如何在项目中优雅地封装它们。
方案一:AsyncStorage(通用存储)
AsyncStorage 是 React Native 社区维护的一个异步、非加密的键值对(Key-Value)存储系统。你可以把它想象成浏览器端的 localStorage。
1. 适用场景
由于它是未加密 的,任何拥有设备文件系统访问权限的人(或恶意软件)都能读取这些数据。因此,它只适合存储:
- ✅ 用户偏好设置(如:是否开启推送、语言设置、主题色)
- ✅ 应用缓存数据(如:首页的临时数据列表)
- ✅ Redux/Zustand 状态的持久化
- ❌ 绝对不要存:用户密码、身份认证 Token、信用卡信息
2. 安装
Bash
npx expo install @react-native-async-storage/async-storage
3. 使用示例
AsyncStorage 只能存储字符串。如果你要存储对象,需要使用 JSON.stringify 和 JSON.parse 进行转换。
TypeScript
import AsyncStorage from '@react-native-async-storage/async-storage';
// 1. 存储数据 (Key 必须是字符串,Value 也必须是字符串)
const saveSettings = async () => {
try {
await AsyncStorage.setItem('app_theme', 'dark');
// 存储对象需序列化
await AsyncStorage.setItem('user_profile', JSON.stringify({ name: 'John', age: 30 }));
} catch (e) {
console.error('保存失败', e);
}
};
// 2. 读取数据
const loadSettings = async () => {
try {
const theme = await AsyncStorage.getItem('app_theme');
const jsonValue = await AsyncStorage.getItem('user_profile');
const userProfile = jsonValue != null ? JSON.parse(jsonValue) : null;
console.log(theme, userProfile);
} catch (e) {
console.error('读取失败', e);
}
};
// 3. 删除/清空
const clearData = async () => {
await AsyncStorage.removeItem('app_theme'); // 删除单个
await AsyncStorage.clear(); // 清空所有(慎用)
}
方案二:expo-secure-store(安全存储)
SecureStore 是 Expo 提供的一个库,它利用了操作系统底层的安全机制来加密存储数据。
- iOS : 使用 Keychain Services(钥匙串)。
- Android : 使用 SharedPreferences(配合 Keystore 加密)。
1. 适用场景
这是存储敏感数据的唯一正确选择:
- ✅ 身份认证 Token (Auth Token / Refresh Token)
- ✅ 用户密码 (Password)
- ✅ API 密钥 (API Keys)
2. 安装
Bash
npx expo install expo-secure-store
3. 使用示例
API 与 AsyncStorage 非常相似,但它保证了数据是加密落盘的。
TypeScript
import * as SecureStore from 'expo-secure-store';
// 1. 存储敏感数据
const saveToken = async (token: string) => {
// 注意:Key 和 Value 都必须是字符串
await SecureStore.setItemAsync('user_token', token);
};
// 2. 读取数据
const getToken = async () => {
const token = await SecureStore.getItemAsync('user_token');
if (token) {
console.log('获取到 Token:', token);
} else {
console.log('未找到 Token');
}
};
// 3. 删除数据 (如用户登出时)
const removeToken = async () => {
await SecureStore.deleteItemAsync('user_token');
};
深度对比:该选哪一个?
| 特性 | AsyncStorage | SecureStore |
|---|---|---|
| 安全性 | 🚨 低 (明文存储,类似 TXT 文件) | 🔒 高 (OS 级硬件加密) |
| 存储容量 | 较大 (几 MB 到几十 MB 没问题) | 极小 (建议只存 < 2KB 的短字符串) |
| 读写速度 | 快 | 稍慢 (涉及解密过程) |
| 数据类型 | 仅字符串 (需手动 JSON 序列化) | 仅字符串 |
| 核心用途 | 缓存、设置、非敏感状态 | Token、密码、证书 |
最佳实践:封装统一的存储工具
在真实的项目开发中,我们不应该在 UI 组件里直接调用 AsyncStorage 或 SecureStore,而是应该封装一个工具模块。这样既能统一管理 Key,又能屏蔽底层实现的差异。
以下是一个推荐的 utils/storage.ts 封装示例:
TypeScript
// utils/storage.ts
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as SecureStore from 'expo-secure-store';
// 定义所有的 Key,防止手误写错
const KEYS = {
TOKEN: 'auth_token',
USER_INFO: 'user_info',
THEME: 'app_theme',
};
/**
* 敏感数据存储工具 (使用 SecureStore)
*/
export const AuthStorage = {
async setToken(token: string) {
await SecureStore.setItemAsync(KEYS.TOKEN, token);
},
async getToken() {
return await SecureStore.getItemAsync(KEYS.TOKEN);
},
async clearToken() {
await SecureStore.deleteItemAsync(KEYS.TOKEN);
},
};
/**
* 普通数据存储工具 (使用 AsyncStorage)
*/
export const AppStorage = {
// 封装对象存储,自动处理 JSON 转换
async setUser(user: any) {
try {
const jsonValue = JSON.stringify(user);
await AsyncStorage.setItem(KEYS.USER_INFO, jsonValue);
} catch (e) {
console.error('Saving user failed', e);
}
},
async getUser() {
try {
const jsonValue = await AsyncStorage.getItem(KEYS.USER_INFO);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (e) {
console.error('Loading user failed', e);
return null;
}
},
async clearAll() {
await AsyncStorage.clear();
}
};
如何在业务中使用?
TypeScript
import { AuthStorage, AppStorage } from './utils/storage';
// 登录成功后
const handleLoginSuccess = async (apiResponse) => {
const { token, user } = apiResponse;
// 1. Token 进保险箱
await AuthStorage.setToken(token);
// 2. 用户信息进普通仓库
await AppStorage.setUser(user);
console.log('登录数据已持久化');
};
// 退出登录时
const handleLogout = async () => {
await AuthStorage.clearToken();
await AppStorage.setUser(null);
};
总结
在 React Native (Expo) 开发中,请务必遵守 "数据分级" 原则:
- Token 和密码 :必须通过
expo-secure-store放入系统的"保险箱"。 - 设置和缓存 :可以通过
@react-native-async-storage/async-storage放入普通的"储物柜"。
通过合理的封装,你可以让代码更安全、更整洁,也更易于维护。