React native AsyncSctorage 封装
'# React Native AsyncStorage 封装
一、背景与问题
在React Native开发中,持久化存储是常见需求。官方提供的AsyncStorage库虽然提供了简单的键值存储能力,但其设计存在一些局限性:
- 数据类型限制:只能存储字符串
- 并发处理不足:多线程写入时可能引发冲突
- 性能瓶颈:大量数据时可能出现阻塞
- 缓存策略缺失:缺乏自动清理机制
- 安全性隐患:明文存储敏感信息
这些限制在实际项目中会引发诸多问题。例如:用户登录态的存储、配置信息管理、缓存数据保存等场景需要更完善的封装方案。
二、基本原理
AsyncStorage的底层实现机制因平台而异:
- iOS:基于NSUserDefaults
- Android:基于SharedPreferences
- 通用实现:通过SQLite数据库封装
其核心特性包括:
- 异步操作:通过Promise封装的异步IO
- 批量处理:支持多条数据的批量存储
- 键值对存储:以字符串形式存储数据
- 内存缓存:内部维护内存缓存机制
需要注意的是,AsyncStorage的写入操作是同步的,这会导致在大量数据写入时可能阻塞主线程。其设计初衷是为轻量级数据存储提供快速访问。
三、环境准备
在开始封装前,确保以下依赖:
npm install @react-native-community/async-storage在Android项目中需要额外配置:
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>四、核心实现
1. 基础封装
// src/storage/BaseStorage.ts
import AsyncStorage from '@react-native-community/async-storage';
interface StorageOptions {
prefix?: string;
ttl?: number; // 过期时间(秒)
}
class BaseStorage {
private prefix: string;
private ttl: number;
constructor(options: StorageOptions = {}) {
this.prefix = options.prefix || 'app:';
this.ttl = options.ttl || 0; // 0 表示永不过期
}
private getFullKey(key: string): string {
return `${this.prefix}${key}`;
}
async setItem(key: string, value: any): Promise<void> {
const fullKey = this.getFullKey(key);
const data = JSON.stringify(value);
// 添加过期时间戳
const expires = this.ttl > 0 ? Date.now() + this.ttl * 1000 : 0;
await AsyncStorage.setItem(fullKey, JSON.stringify({ data, expires }));
}
async getItem(key: string): Promise<any | null> {
const fullKey = this.getFullKey(key);
const item = await AsyncStorage.getItem(fullKey);
if (!item) return null;
const { data, expires } = JSON.parse(item);
// 检查过期时间
if (this.ttl > 0 && expires < Date.now()) {
await this.removeItem(key);
return null;
}
return JSON.parse(data);
}
async removeItem(key: string): Promise<void> {
const fullKey = this.getFullKey(key);
await AsyncStorage.removeItem(fullKey);
}
async clear(): Promise<void> {
await AsyncStorage.clear();
}
async getAll(): Promise<Record<string, any>> {
const keys = await AsyncStorage.getAllKeys();
const result: Record<string, any> = {};
for (const key of keys) {
const value = await this.getItem(key);
if (value) {
result[key] = value;
}
}
return result;
}
}关键代码解释:
prefix字段用于区分不同模块的存储数据ttl参数控制数据的过期时间- 在
setItem中添加过期时间戳 getItem方法中进行过期检查getAll方法实现批量获取数据
2. 带过期时间的封装
// src/storage/ExpiringStorage.ts
import { BaseStorage } from './BaseStorage';
export class ExpiringStorage extends BaseStorage {
constructor(options: { prefix?: string; ttl?: number } = {}) {
super(options);
}
async setItem(key: string, value: any, ttl?: number): Promise<void> {
const finalTtl = ttl || this.ttl;
return super.setItem(key, value);
}
async getItem(key: string): Promise<any | null> {
const result = await super.getItem(key);
if (result) {
console.log(`Retrieved ${key}: ${JSON.stringify(result)}`);
}
return result;
}
}3. 使用Promise封装
// src/storage/PromiseStorage.ts
import AsyncStorage from '@react-native-community/async-storage';
interface StorageOptions {
prefix?: string;
ttl?: number;
}
class PromiseStorage {
private prefix: string;
private ttl: number;
constructor(options: StorageOptions = {}) {
this.prefix = options.prefix || 'app:';
this.ttl = options.ttl || 0;
}
private getFullKey(key: string): string {
return `${this.prefix}${key}`;
}
async setItem(key: string, value: any): Promise<void> {
const fullKey = this.getFullKey(key);
const data = JSON.stringify(value);
const expires = this.ttl > 0 ? Date.now() + this.ttl * 1000 : 0;
return AsyncStorage.setItem(fullKey, JSON.stringify({ data, expires }));
}
async getItem(key: string): Promise<any | null> {
const fullKey = this.getFullKey(key);
const item = await AsyncStorage.getItem(fullKey);
if (!item) return null;
const { data, expires } = JSON.parse(item);
if (this.ttl > 0 && expires < Date.now()) {
await this.removeItem(key);
return null;
}
return JSON.parse(data);
}
// ...其他方法同BaseStorage
}五、完整案例
登录状态管理案例
// src/storage/authStorage.ts
import { ExpiringStorage } from './storage/ExpiringStorage';
const authStorage = new ExpiringStorage({
prefix: 'auth:',
ttl: 7 * 24 * 60 * 60 // 7天
});
// 存储登录信息
async function saveAuthInfo(userInfo: any) {
try {
await authStorage.setItem('user', userInfo);
console.log('登录信息存储成功');
} catch (error) {
console.error('存储登录信息失败:', error);
}
}
// 获取登录信息
async function getAuthInfo(): Promise<any | null> {
try {
const info = await authStorage.getItem('user');
if (!info) {
console.log('未找到登录信息');
}
return info;
} catch (error) {
console.error('获取登录信息失败:', error);
return null;
}
}
// 检查登录状态
async function checkAuthStatus(): Promise<boolean> {
const info = await getAuthInfo();
return Boolean(info);
}完整案例说明:
- 使用ExpiringStorage封装登录信息存储
- 设置7天过期时间
- 提供保存、获取和检查登录状态的方法
- 包含异常处理逻辑
六、源码解析
1. 数据存储机制
// 在setItem中进行的数据处理
const data = JSON.stringify(value);
const expires = this.ttl > 0 ? Date.now() + this.ttl * 1000 : 0;
await AsyncStorage.setItem(fullKey, JSON.stringify({ data, expires }));- 将值序列化为JSON字符串
- 添加过期时间戳
- 存储为复合结构:{ data: string, expires: number }
2. 数据读取机制
// 在getItem中进行的数据处理
const { data, expires } = JSON.parse(item);
if (this.ttl > 0 && expires < Date.now()) {
await this.removeItem(key);
return null;
}- 解析存储的复合结构
- 检查过期时间
- 自动清理过期数据
七、进阶使用
1. 增加缓存策略
// src/storage/CacheStorage.ts
import { BaseStorage } from './BaseStorage';
export class CacheStorage extends BaseStorage {
private maxCacheSize: number = 100;
private cache: Record<string, any> = {};
constructor(options: StorageOptions = {}) {
super(options);
}
async setItem(key: string, value: any): Promise<void> {
const fullKey = this.getFullKey(key);
const data = JSON.stringify(value);
// 更新缓存
this.cache[key] = value;
// 检查缓存大小
if (Object.keys(this.cache).length > this.maxCacheSize) {
const oldestKey = Object.keys(this.cache).sort((a, b) => {
const aTime = this.getLastAccessTime(a);
const bTime = this.getLastAccessTime(b);
return aTime - bTime;
})[0];
if (oldestKey) {
delete this.cache[oldestKey];
}
}
return super.setItem(key, value);
}
private getLastAccessTime(key: string): number {
// 实现访问时间记录逻辑
return Date.now();
}
}2. 使用TypeScript类型校验
// src/storage/TypeStorage.ts
import AsyncStorage from '@react-native-community/async-storage';
interface StorageOptions<T> {
prefix?: string;
ttl?: number;
type?: 'string' | 'number' | 'boolean';
}
class TypeStorage<T> {
private prefix: string;
private ttl: number;
private type: 'string' | 'number' | 'boolean';
constructor(options: StorageOptions<T> = {}) {
this.prefix = options.prefix || 'app:';
this.ttl = options.ttl || 0;
this.type = options.type || 'string';
}
private getFullKey(key: string): string {
return `${this.prefix}${key}`;
}
async setItem(key: string, value: T): Promise<void> {
const fullKey = this.getFullKey(key);
let data: string;
switch (this.type) {
case 'string':
data = String(value);
break;
case 'number':
data = String(Number(value));
break;
case 'boolean':
data = String(Boolean(value));
break;
default:
data = JSON.stringify(value);
}
const expires = this.ttl > 0 ? Date.now() + this.ttl * 1000 : 0;
await AsyncStorage.setItem(fullKey, JSON.stringify({ data, expires }));
}
async getItem(key: string): Promise<T | null> {
const fullKey = this.getFullKey(key);
const item = await AsyncStorage.getItem(fullKey);
if (!item) return null;
const { data, expires } = JSON.parse(item);
if (this.ttl > 0 && expires < Date.now()) {
await this.removeItem(key);
return null;
}
let result: T;
switch (this.type) {
case 'string':
result = data as T;
break;
case 'number':
result = Number(data) as T;
break;
case 'boolean':
result = Boolean(data) as T;
break;
default:
result = JSON.parse(data) as T;
}
return result;
}
}八、性能与工程实践
1. 性能优化策略
| 优化措施 | 描述 | 实现方法 |
|---|---|---|
| 分页处理 | 大数据量时采用分页读取 | 在getAll方法中添加分页参数 |
| 内存缓存 | 提供内存缓存层 | 实现LRU缓存策略 |
| 异步批处理 | 支持批量写入操作 | 使用Promise.all处理多条数据 |
| 资源清理 | 自动清理过期数据 | 在getItem中自动清理过期项 |
2. 安全风险控制
- 数据加密:使用AES加密存储数据
- 敏感数据隔离:使用不同的prefix区分敏感数据
- 权限控制:在Android中添加存储权限控制
- 密钥管理:使用Keychain存储敏感信息
3. 异常处理机制
// 添加异常处理中间件
async function safeStorage<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn();
} catch (error) {
console.error('Storage operation failed:', error);
// 可以选择重试或返回默认值
return null;
}
}九、常见问题与踩坑
1. 常见错误及解决办法
| 错误类型 | 描述 | 解决办法 |
|---|---|---|
| 数据类型错误 | 存储对象未正确序列化 | 使用JSON.stringify |
| 键名冲突 | 不同模块使用相同prefix | 确保prefix唯一性 |
| 异步回调问题 | 直接使用未完成的Promise | 使用await或.then() |
| 内存泄漏 | 未清理过期数据 | 实现自动清理机制 |
| 平台差异 | iOS/Android存储方式不同 | 使用统一封装层 |
2. 典型错误示例
// 错误示例:未处理异步操作
const user = AsyncStorage.getItem('user');
console.log(user); // 输出undefined// 正确示例:使用async/await
async function getUser() {
const user = await AsyncStorage.getItem('user');
console.log(user);
}十、最佳实践
- 使用唯一prefix:避免不同模块的存储冲突
- 设置合理的过期时间:根据业务需求调整ttl参数
- 采用类型安全封装:使用TypeScript进行类型校验
- 实现缓存策略:使用LRU策略管理内存缓存
- 添加异常处理:封装统一的异常处理中间件
- 区分敏感数据:对敏感数据进行加密处理
- 定期清理过期数据:在getItem中自动清理过期项
- 避免存储大量数据:使用SQLite或Redux Persist处理复杂数据
十一、总结
AsyncStorage作为React Native的本地存储方案,虽然存在一定的局限性,但通过合理的封装可以解决大部分实际问题。本文深入探讨了其工作原理,提供了三种不同的封装方案,并结合实际案例展示了如何在登录状态管理中使用。
在实际开发中,应根据具体需求选择合适的封装方案:
- 轻量级存储:使用基础封装
- 需要过期控制:使用带ttl的封装
- 需要类型校验:使用TypeStorage
- 需要缓存策略:使用CacheStorage
需要注意的是,AsyncStorage不适合存储敏感信息或大量数据。对于需要更高安全性的场景,建议使用Keychain或加密存储方案。对于需要复杂查询的场景,应考虑使用SQLite或第三方库如react-native-sqlite-storage。
通过合理的封装和使用,AsyncStorage可以成为React Native项目中可靠的本地存储解决方案。
评论已关闭