第6章:本地存储
📖 本章概述
本地存储是移动应用的重要功能,用于保存用户数据、应用设置、缓存等。本章将介绍 React Native 中的各种存储方案,包括 AsyncStorage、安全存储、SQLite 数据库等。
💾 AsyncStorage - 异步存储
AsyncStorage 是 React Native 中最常用的本地存储方案,提供了简单的键值对存储。
安装和基本使用
bash
npm install @react-native-async-storage/async-storage
typescript
// src/services/storage.ts
import AsyncStorage from '@react-native-async-storage/async-storage';
export class StorageService {
// 存储字符串
static async setString(key: string, value: string): Promise<void> {
try {
await AsyncStorage.setItem(key, value);
} catch (error) {
console.error('存储字符串失败:', error);
throw error;
}
}
// 获取字符串
static async getString(key: string): Promise<string | null> {
try {
return await AsyncStorage.getItem(key);
} catch (error) {
console.error('获取字符串失败:', error);
return null;
}
}
// 存储对象
static async setObject(key: string, value: any): Promise<void> {
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(key, jsonValue);
} catch (error) {
console.error('存储对象失败:', error);
throw error;
}
}
// 获取对象
static async getObject<T>(key: string): Promise<T | null> {
try {
const jsonValue = await AsyncStorage.getItem(key);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (error) {
console.error('获取对象失败:', error);
return null;
}
}
// 删除数据
static async remove(key: string): Promise<void> {
try {
await AsyncStorage.removeItem(key);
} catch (error) {
console.error('删除数据失败:', error);
throw error;
}
}
// 清空所有数据
static async clear(): Promise<void> {
try {
await AsyncStorage.clear();
} catch (error) {
console.error('清空数据失败:', error);
throw error;
}
}
// 获取所有键
static async getAllKeys(): Promise<string[]> {
try {
return await AsyncStorage.getAllKeys();
} catch (error) {
console.error('获取所有键失败:', error);
return [];
}
}
// 批量操作
static async multiSet(keyValuePairs: [string, string][]): Promise<void> {
try {
await AsyncStorage.multiSet(keyValuePairs);
} catch (error) {
console.error('批量存储失败:', error);
throw error;
}
}
static async multiGet(keys: string[]): Promise<[string, string | null][]> {
try {
return await AsyncStorage.multiGet(keys);
} catch (error) {
console.error('批量获取失败:', error);
return [];
}
}
}
实际使用示例
typescript
// src/screens/SettingsScreen.tsx
import React, {useState, useEffect} from 'react';
import {
View,
Text,
Switch,
Button,
TextInput,
StyleSheet,
Alert,
} from 'react-native';
import {StorageService} from '../services/storage';
interface UserSettings {
username: string;
notifications: boolean;
theme: 'light' | 'dark';
language: 'zh' | 'en';
}
const defaultSettings: UserSettings = {
username: '',
notifications: true,
theme: 'light',
language: 'zh',
};
const SettingsScreen: React.FC = () => {
const [settings, setSettings] = useState<UserSettings>(defaultSettings);
const [loading, setLoading] = useState(true);
// 加载设置
useEffect(() => {
loadSettings();
}, []);
const loadSettings = async () => {
try {
const savedSettings = await StorageService.getObject<UserSettings>('userSettings');
if (savedSettings) {
setSettings(savedSettings);
}
} catch (error) {
Alert.alert('错误', '加载设置失败');
} finally {
setLoading(false);
}
};
// 保存设置
const saveSettings = async () => {
try {
await StorageService.setObject('userSettings', settings);
Alert.alert('成功', '设置已保存');
} catch (error) {
Alert.alert('错误', '保存设置失败');
}
};
// 重置设置
const resetSettings = async () => {
Alert.alert(
'确认重置',
'确定要重置所有设置吗?',
[
{text: '取消', style: 'cancel'},
{
text: '确定',
onPress: async () => {
try {
await StorageService.remove('userSettings');
setSettings(defaultSettings);
Alert.alert('成功', '设置已重置');
} catch (error) {
Alert.alert('错误', '重置设置失败');
}
},
},
]
);
};
const updateSetting = <K extends keyof UserSettings>(
key: K,
value: UserSettings[K]
) => {
setSettings(prev => ({...prev, [key]: value}));
};
if (loading) {
return (
<View style={styles.centerContainer}>
<Text>加载中...</Text>
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.title}>应用设置</Text>
{/* 用户名设置 */}
<View style={styles.settingItem}>
<Text style={styles.settingLabel}>用户名</Text>
<TextInput
style={styles.textInput}
value={settings.username}
onChangeText={(value) => updateSetting('username', value)}
placeholder="请输入用户名"
/>
</View>
{/* 通知设置 */}
<View style={styles.settingItem}>
<Text style={styles.settingLabel}>推送通知</Text>
<Switch
value={settings.notifications}
onValueChange={(value) => updateSetting('notifications', value)}
/>
</View>
{/* 主题设置 */}
<View style={styles.settingItem}>
<Text style={styles.settingLabel}>主题</Text>
<View style={styles.buttonGroup}>
<Button
title="浅色"
onPress={() => updateSetting('theme', 'light')}
color={settings.theme === 'light' ? '#007AFF' : '#999'}
/>
<Button
title="深色"
onPress={() => updateSetting('theme', 'dark')}
color={settings.theme === 'dark' ? '#007AFF' : '#999'}
/>
</View>
</View>
{/* 语言设置 */}
<View style={styles.settingItem}>
<Text style={styles.settingLabel}>语言</Text>
<View style={styles.buttonGroup}>
<Button
title="中文"
onPress={() => updateSetting('language', 'zh')}
color={settings.language === 'zh' ? '#007AFF' : '#999'}
/>
<Button
title="English"
onPress={() => updateSetting('language', 'en')}
color={settings.language === 'en' ? '#007AFF' : '#999'}
/>
</View>
</View>
{/* 操作按钮 */}
<View style={styles.actionButtons}>
<Button title="保存设置" onPress={saveSettings} />
<Button title="重置设置" onPress={resetSettings} color="#ff4757" />
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#fff',
},
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 30,
},
settingItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 15,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
settingLabel: {
fontSize: 16,
fontWeight: '500',
flex: 1,
},
textInput: {
flex: 1,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 8,
marginLeft: 10,
},
buttonGroup: {
flexDirection: 'row',
gap: 10,
},
actionButtons: {
marginTop: 30,
gap: 15,
},
});
export default SettingsScreen;
🔐 安全存储
对于敏感数据(如密码、token 等),需要使用更安全的存储方案。
React Native Keychain
bash
npm install react-native-keychain
typescript
// src/services/secureStorage.ts
import * as Keychain from 'react-native-keychain';
export class SecureStorageService {
// 存储用户凭据
static async setCredentials(username: string, password: string): Promise<boolean> {
try {
await Keychain.setCredentials(username, password);
return true;
} catch (error) {
console.error('存储凭据失败:', error);
return false;
}
}
// 获取用户凭据
static async getCredentials(): Promise<{username: string; password: string} | null> {
try {
const credentials = await Keychain.getCredentials();
if (credentials) {
return {
username: credentials.username,
password: credentials.password,
};
}
return null;
} catch (error) {
console.error('获取凭据失败:', error);
return null;
}
}
// 删除凭据
static async removeCredentials(): Promise<boolean> {
try {
await Keychain.resetCredentials();
return true;
} catch (error) {
console.error('删除凭据失败:', error);
return false;
}
}
// 存储通用安全数据
static async setSecureData(key: string, value: string): Promise<boolean> {
try {
await Keychain.setCredentials(key, value, {
service: key,
});
return true;
} catch (error) {
console.error('存储安全数据失败:', error);
return false;
}
}
// 获取通用安全数据
static async getSecureData(key: string): Promise<string | null> {
try {
const result = await Keychain.getCredentials({
service: key,
});
return result ? result.password : null;
} catch (error) {
console.error('获取安全数据失败:', error);
return null;
}
}
// 删除通用安全数据
static async removeSecureData(key: string): Promise<boolean> {
try {
await Keychain.resetCredentials({
service: key,
});
return true;
} catch (error) {
console.error('删除安全数据失败:', error);
return false;
}
}
}
🗄️ SQLite 数据库
对于复杂的数据存储需求,SQLite 是一个很好的选择。
安装 React Native SQLite Storage
bash
npm install react-native-sqlite-storage
typescript
// src/services/database.ts
import SQLite from 'react-native-sqlite-storage';
// 启用调试
SQLite.DEBUG(true);
SQLite.enablePromise(true);
export interface User {
id?: number;
name: string;
email: string;
age: number;
createdAt?: string;
}
export class DatabaseService {
private static db: SQLite.SQLiteDatabase;
// 初始化数据库
static async init(): Promise<void> {
try {
this.db = await SQLite.openDatabase({
name: 'MyApp.db',
location: 'default',
});
await this.createTables();
console.log('数据库初始化成功');
} catch (error) {
console.error('数据库初始化失败:', error);
throw error;
}
}
// 创建表
private static async createTables(): Promise<void> {
const createUserTable = `
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
age INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`;
await this.db.executeSql(createUserTable);
}
// 插入用户
static async insertUser(user: Omit<User, 'id' | 'createdAt'>): Promise<number> {
try {
const result = await this.db.executeSql(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
[user.name, user.email, user.age]
);
return result[0].insertId;
} catch (error) {
console.error('插入用户失败:', error);
throw error;
}
}
// 获取所有用户
static async getAllUsers(): Promise<User[]> {
try {
const result = await this.db.executeSql('SELECT * FROM users ORDER BY created_at DESC');
const users: User[] = [];
for (let i = 0; i < result[0].rows.length; i++) {
const row = result[0].rows.item(i);
users.push({
id: row.id,
name: row.name,
email: row.email,
age: row.age,
createdAt: row.created_at,
});
}
return users;
} catch (error) {
console.error('获取用户列表失败:', error);
throw error;
}
}
// 根据ID获取用户
static async getUserById(id: number): Promise<User | null> {
try {
const result = await this.db.executeSql('SELECT * FROM users WHERE id = ?', [id]);
if (result[0].rows.length > 0) {
const row = result[0].rows.item(0);
return {
id: row.id,
name: row.name,
email: row.email,
age: row.age,
createdAt: row.created_at,
};
}
return null;
} catch (error) {
console.error('获取用户失败:', error);
throw error;
}
}
// 更新用户
static async updateUser(id: number, user: Partial<Omit<User, 'id' | 'createdAt'>>): Promise<boolean> {
try {
const fields = Object.keys(user).map(key => `${key} = ?`).join(', ');
const values = Object.values(user);
const result = await this.db.executeSql(
`UPDATE users SET ${fields} WHERE id = ?`,
[...values, id]
);
return result[0].rowsAffected > 0;
} catch (error) {
console.error('更新用户失败:', error);
throw error;
}
}
// 删除用户
static async deleteUser(id: number): Promise<boolean> {
try {
const result = await this.db.executeSql('DELETE FROM users WHERE id = ?', [id]);
return result[0].rowsAffected > 0;
} catch (error) {
console.error('删除用户失败:', error);
throw error;
}
}
// 搜索用户
static async searchUsers(keyword: string): Promise<User[]> {
try {
const result = await this.db.executeSql(
'SELECT * FROM users WHERE name LIKE ? OR email LIKE ? ORDER BY created_at DESC',
[`%${keyword}%`, `%${keyword}%`]
);
const users: User[] = [];
for (let i = 0; i < result[0].rows.length; i++) {
const row = result[0].rows.item(i);
users.push({
id: row.id,
name: row.name,
email: row.email,
age: row.age,
createdAt: row.created_at,
});
}
return users;
} catch (error) {
console.error('搜索用户失败:', error);
throw error;
}
}
// 关闭数据库
static async close(): Promise<void> {
if (this.db) {
await this.db.close();
}
}
}
使用 SQLite 数据库
typescript
// src/screens/UserManagementScreen.tsx
import React, {useState, useEffect} from 'react';
import {
View,
Text,
FlatList,
TextInput,
Button,
Alert,
StyleSheet,
TouchableOpacity,
} from 'react-native';
import {DatabaseService, User} from '../services/database';
const UserManagementScreen: React.FC = () => {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [searchKeyword, setSearchKeyword] = useState('');
const [showAddForm, setShowAddForm] = useState(false);
const [newUser, setNewUser] = useState({
name: '',
email: '',
age: '',
});
useEffect(() => {
initDatabase();
}, []);
const initDatabase = async () => {
try {
await DatabaseService.init();
await loadUsers();
} catch (error) {
Alert.alert('错误', '数据库初始化失败');
} finally {
setLoading(false);
}
};
const loadUsers = async () => {
try {
const userList = await DatabaseService.getAllUsers();
setUsers(userList);
} catch (error) {
Alert.alert('错误', '加载用户列表失败');
}
};
const addUser = async () => {
if (!newUser.name || !newUser.email || !newUser.age) {
Alert.alert('错误', '请填写完整信息');
return;
}
try {
await DatabaseService.insertUser({
name: newUser.name,
email: newUser.email,
age: parseInt(newUser.age),
});
setNewUser({name: '', email: '', age: ''});
setShowAddForm(false);
await loadUsers();
Alert.alert('成功', '用户添加成功');
} catch (error) {
Alert.alert('错误', '添加用户失败');
}
};
const deleteUser = async (id: number) => {
Alert.alert(
'确认删除',
'确定要删除这个用户吗?',
[
{text: '取消', style: 'cancel'},
{
text: '删除',
style: 'destructive',
onPress: async () => {
try {
await DatabaseService.deleteUser(id);
await loadUsers();
Alert.alert('成功', '用户删除成功');
} catch (error) {
Alert.alert('错误', '删除用户失败');
}
},
},
]
);
};
const searchUsers = async () => {
if (!searchKeyword.trim()) {
await loadUsers();
return;
}
try {
const searchResults = await DatabaseService.searchUsers(searchKeyword);
setUsers(searchResults);
} catch (error) {
Alert.alert('错误', '搜索失败');
}
};
const renderUser = ({item}: {item: User}) => (
<View style={styles.userItem}>
<View style={styles.userInfo}>
<Text style={styles.userName}>{item.name}</Text>
<Text style={styles.userEmail}>{item.email}</Text>
<Text style={styles.userAge}>年龄: {item.age}</Text>
</View>
<TouchableOpacity
style={styles.deleteButton}
onPress={() => deleteUser(item.id!)}
>
<Text style={styles.deleteButtonText}>删除</Text>
</TouchableOpacity>
</View>
);
if (loading) {
return (
<View style={styles.centerContainer}>
<Text>初始化数据库...</Text>
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.title}>用户管理</Text>
{/* 搜索框 */}
<View style={styles.searchContainer}>
<TextInput
style={styles.searchInput}
value={searchKeyword}
onChangeText={setSearchKeyword}
placeholder="搜索用户..."
/>
<Button title="搜索" onPress={searchUsers} />
</View>
{/* 添加用户按钮 */}
<Button
title={showAddForm ? '取消添加' : '添加用户'}
onPress={() => setShowAddForm(!showAddForm)}
/>
{/* 添加用户表单 */}
{showAddForm && (
<View style={styles.addForm}>
<TextInput
style={styles.input}
value={newUser.name}
onChangeText={(text) => setNewUser({...newUser, name: text})}
placeholder="姓名"
/>
<TextInput
style={styles.input}
value={newUser.email}
onChangeText={(text) => setNewUser({...newUser, email: text})}
placeholder="邮箱"
keyboardType="email-address"
/>
<TextInput
style={styles.input}
value={newUser.age}
onChangeText={(text) => setNewUser({...newUser, age: text})}
placeholder="年龄"
keyboardType="numeric"
/>
<Button title="添加" onPress={addUser} />
</View>
)}
{/* 用户列表 */}
<FlatList
data={users}
renderItem={renderUser}
keyExtractor={(item) => item.id!.toString()}
style={styles.userList}
ListEmptyComponent={
<Text style={styles.emptyText}>暂无用户数据</Text>
}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#fff',
},
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 20,
},
searchContainer: {
flexDirection: 'row',
marginBottom: 15,
alignItems: 'center',
},
searchInput: {
flex: 1,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 8,
marginRight: 10,
},
addForm: {
backgroundColor: '#f8f9fa',
padding: 15,
borderRadius: 8,
marginVertical: 15,
},
input: {
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 8,
marginBottom: 10,
},
userList: {
flex: 1,
marginTop: 15,
},
userItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#f8f9fa',
padding: 15,
borderRadius: 8,
marginBottom: 10,
},
userInfo: {
flex: 1,
},
userName: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 4,
},
userEmail: {
fontSize: 14,
color: '#666',
marginBottom: 2,
},
userAge: {
fontSize: 14,
color: '#666',
},
deleteButton: {
backgroundColor: '#ff4757',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 6,
},
deleteButtonText: {
color: '#fff',
fontSize: 12,
fontWeight: 'bold',
},
emptyText: {
textAlign: 'center',
color: '#666',
fontSize: 16,
marginTop: 50,
},
});
export default UserManagementScreen;
🎉 本章小结
在这一章中,我们学习了:
- ✅ AsyncStorage 的基本使用和封装
- ✅ 安全存储敏感数据的方法
- ✅ SQLite 数据库的集成和使用
- ✅ 不同存储方案的适用场景
- ✅ 数据持久化的最佳实践
📝 作业
- 创建一个笔记应用,支持本地存储和搜索
- 实现用户登录状态的持久化存储
- 开发一个离线优先的待办事项应用
- 添加数据备份和恢复功能
准备好学习原生功能了吗?让我们继续第7章:原生功能集成!