Expo (React Native) 本地存储全攻略:普通数据与敏感数据该存哪?

在移动应用开发中,数据持久化(Data Persistence)是绕不开的话题。无论是为了让用户免于重复登录,还是保存用户的偏好设置(如深色模式),我们都需要将数据存储在用户的设备上。

对于使用 Expo 开发的 React Native 项目,官方推荐了两种截然不同的存储方案:AsyncStorageexpo-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.stringifyJSON.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 组件里直接调用 AsyncStorageSecureStore,而是应该封装一个工具模块。这样既能统一管理 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) 开发中,请务必遵守 "数据分级" 原则:

  1. Token 和密码 :必须通过 expo-secure-store 放入系统的"保险箱"。
  2. 设置和缓存 :可以通过 @react-native-async-storage/async-storage 放入普通的"储物柜"。

通过合理的封装,你可以让代码更安全、更整洁,也更易于维护。

相关推荐
zlpzlpzyd2 小时前
vue.js 3中全局组件和局部组件的区别
前端·javascript·vue.js
浩星2 小时前
css实现类似element官网的磨砂屏幕效果
前端·javascript·css
一只小风华~2 小时前
Vue.js 核心知识点全面解析
前端·javascript·vue.js
2022.11.7始学前端2 小时前
n8n第七节 只提醒重要的待办
前端·javascript·ui·n8n
SakuraOnTheWay3 小时前
React Grab实践 | 记一次与Cursor的有趣对话
前端·cursor
阿星AI工作室3 小时前
gemini3手势互动圣诞树保姆级教程来了!附提示词
前端·人工智能
徐小夕3 小时前
知识库创业复盘:从闭源到开源,这3个教训价值百万
前端·javascript·github
xhxxx3 小时前
函数执行完就销毁?那闭包里的变量凭什么活下来!—— 深入 JS 内存模型
前端·javascript·ecmascript 6
StarkCoder3 小时前
求求你试试 DiffableDataSource!别再手算 indexPath 了(否则迟早崩)
前端