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 放入普通的"储物柜"。

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

相关推荐
苦藤新鸡7 分钟前
27.合并有序链表,串葫芦
前端·javascript·链表
_OP_CHEN9 分钟前
【前端开发之HTML】(四)HTML 标签进阶:表格、表单、布局全掌握,从新手到实战高手!
前端·javascript·css·html·html5·网页开发·html标签
Alair‎20 分钟前
前端开发之环境配置
前端·react.js
Deca~25 分钟前
VueVirtualLazyTree-支持懒加载的虚拟树
前端·javascript·vue.js
爱上妖精的尾巴33 分钟前
7-11 WPS JS宏 对象的属性值为函数的写法与用法
前端·javascript·wps·js宏·jsa
zuozewei34 分钟前
零基础 | 使用LangChain框架实现ReAct Agent
前端·react.js·langchain
坠入暮云间x34 分钟前
React Native for OpenHarmony开发环境搭建指南(一)
前端·react native·开源
爱上妖精的尾巴37 分钟前
7-12 WPS JS宏 this、return用构造函数自定义类-1:对象内部函数,外部调用的写法
前端·javascript·wps·js宏·jsa
har01d42 分钟前
AI生成的 vue3 日历组件,显示农历与节日,日期可选择,年月可切换
前端·vue.js·节日
冲刺逆向1 小时前
【js逆向案例六】创宇盾(加速乐)通杀模版
java·前端·javascript