【React Native娓娓道来】第10课 数据持久化与本地存储:AsyncStorage、SecureStore 与文件系统
开篇:让数据活过重启
前九课的应用有一个共同的"缺陷":数据活不过一次重启。
待办事项,用户添加了十项,关掉再打开,全没了。登录页面,用户登录成功,关掉再打开,又要重新登录。设置页面,用户切成暗色模式,关掉再打开,又变回亮色。
这就是"持久化"要解决的问题:把数据保存到设备上,让它在 App 关闭后依然存在。
React Native 提供了多种方案,各有各的适用场景。这一课的核心可以浓缩成一句话:
// 概念表达
机密用 SecureStore,速度用 MMKV,兼容用 AsyncStorage,大文件用 FileSystem
一、四种存储方案对比
| 特性 | AsyncStorage | SecureStore | FileSystem | MMKV |
|---|---|---|---|---|
| 安全性 | 明文 | 加密(Keychain/Keystore) | 取决于内容 | 明文 |
| 容量 | 几 MB 到几十 MB | 约 2KB | 取决于设备 | 大 |
| 读写 | 异步 | 异步 | 异步 | 同步 |
| 适合数据 | 偏好、设置、缓存 | 令牌、密码、API Key | 图片、日志、大 JSON | 高频读写状态 |
| Expo Go | 支持 | 支持 | 支持 | 不支持 |
两个关键警告:
- SecureStore 有约 2KB 的大小限制。不要把大 JSON、大缓存放进去。
- AsyncStorage 不加密。令牌、密码等敏感信息绝不能放在里面。
2026 年的选型建议
- Auth tokens、API keys:用
expo-secure-store; - 主题、语言、Zustand/Jotai 持久化状态:优先考虑
react-native-mmkv; - 兼容旧代码、Expo Go 快速原型:用 AsyncStorage;
- 图片、日志、大文件:用
expo-file-system。
MMKV 速度约为 AsyncStorage 的 30 倍,而且是同步的。但它需要开发构建,不能在 Expo Go 中使用。
二、AsyncStorage:最通用的键值存储
2.1 安装
# Bash 终端命令
npx expo install @react-native-async-storage/async-storage # 安装 AsyncStorage
注意:Expo SDK 54+ 的某些版本可能存在兼容性问题。如果遇到问题,可以锁定到 2.2.0,或者迁移到 MMKV。
2.2 基本用法
所有操作都是异步的,需要 await。
// JavaScript + React Native
import AsyncStorage from '@react-native-async-storage/async-storage'; // 引入 AsyncStorage
await AsyncStorage.setItem('theme', 'dark'); // 保存主题
const theme = await AsyncStorage.getItem('theme'); // 读取主题
await AsyncStorage.removeItem('theme'); // 删除主题
await AsyncStorage.clear(); // 清空所有
2.3 保存对象
AsyncStorage 只能保存字符串。对象需要序列化。
// JavaScript + React Native
const user = { name: '小明', age: 18 }; // 用户对象
await AsyncStorage.setItem('user', JSON.stringify(user)); // 保存对象
const json = await AsyncStorage.getItem('user'); // 读取字符串
const user = json ? JSON.parse(json) : null; // 先判断再解析
如果 getItem 返回 null,JSON.parse(null) 会出错,所以要先判断。
2.4 封装成 Hook
更 React 的方式是封装成 Hook,自动在挂载时读取、变化时保存:
// JavaScript + React Native
import { useState, useEffect, useRef } from 'react'; // 引入 Hook
import AsyncStorage from '@react-native-async-storage/async-storage'; // 引入 AsyncStorage
export function useAsyncStorage(key, defaultValue) { // 定义 Hook
const [value, setValue] = useState(defaultValue); // 创建状态
const [loaded, setLoaded] = useState(false); // 加载状态
const isFirst = useRef(true); // 记录是否首次渲染
useEffect(() => { // 挂载时读取
async function load() { // 定义加载函数
try { // 开始 try
const json = await AsyncStorage.getItem(key); // 读取字符串
if (json !== null) setValue(JSON.parse(json)); // 有值就更新状态
} catch (e) { // 捕获错误
console.error('读取失败', e); // 打印错误
} finally { // 收尾
setLoaded(true); // 标记已加载
} // 结束 try
} // 结束 load
load(); // 调用加载
}, [key]); // 依赖 key
useEffect(() => { // 值变化时保存
if (!loaded) return; // 未加载完成不保存
if (isFirst.current) { // 首次跳过
isFirst.current = false; // 标记
return; // 返回
} // 结束判断
AsyncStorage.setItem(key, JSON.stringify(value)).catch(console.error); // 保存值
}, [key, value, loaded]); // 依赖
return [value, setValue, loaded]; // 返回接口
} // 结束 useAsyncStorage
使用时:
// JavaScript + React Native
const [theme, setTheme, loaded] = useAsyncStorage('theme', 'light'); // 使用 Hook
if (!loaded) return <ActivityIndicator />; // 未加载时显示指示器
loaded 状态避免"闪烁":数据还没读出来之前,不要显示默认值。
三、SecureStore:安全存储敏感数据
3.1 安装
# Bash 终端命令
npx expo install expo-secure-store # 安装 SecureStore
3.2 基本用法
方法名带 Async 后缀,只支持字符串。
// JavaScript + React Native
import * as SecureStore from 'expo-secure-store'; // 引入 SecureStore
await SecureStore.setItemAsync('authToken', token); // 保存令牌
const token = await SecureStore.getItemAsync('authToken'); // 读取令牌
await SecureStore.deleteItemAsync('authToken'); // 删除令牌
3.3 认证 Context 完整封装
// JavaScript + React Native
import { createContext, useContext, useState, useEffect } from 'react'; // 引入 Hook
import * as SecureStore from 'expo-secure-store'; // 引入 SecureStore
const AuthContext = createContext(null); // 创建 Context
export function AuthProvider({ children }) { // 定义 Provider
const [token, setToken] = useState(null); // 令牌状态
const [isLoading, setIsLoading] = useState(true); // 加载状态
useEffect(() => { // 挂载时读取令牌
async function load() { // 定义加载函数
const stored = await SecureStore.getItemAsync('authToken'); // 读取令牌
setToken(stored); // 更新状态
setIsLoading(false); // 关闭加载
} // 结束 load
load(); // 调用加载
}, []); // 空数组
const signIn = async (newToken) => { // 登录
await SecureStore.setItemAsync('authToken', newToken); // 保存令牌
setToken(newToken); // 更新状态
}; // 结束 signIn
const signOut = async () => { // 退出
await SecureStore.deleteItemAsync('authToken'); // 删除令牌
setToken(null); // 清空状态
}; // 结束 signOut
return ( // 返回界面描述
<AuthContext.Provider value={{ token, isLoading, signIn, signOut }}> // 提供接口
{children} // 渲染子组件
</AuthContext.Provider> // 结束 Provider
); // 结束 return
} // 结束 AuthProvider
export function useAuth() { // 定义 Hook
const ctx = useContext(AuthContext); // 读取 Context
if (!ctx) throw new Error('useAuth 必须在 AuthProvider 内使用'); // 检查
return ctx; // 返回 Context
} // 结束 useAuth
3.4 生物认证
SecureStore 支持 requireAuthentication,要求用户通过 Face ID 或指纹解锁后才能读取:
// JavaScript + React Native
await SecureStore.setItemAsync('secret', value, { // 保存时设置
requireAuthentication: true, // 需要生物认证
authenticationPrompt: '请验证身份以访问', // 提示文字
}); // 结束
Expo Go 中可能不支持生物认证,需要开发构建才能完整测试。
四、FileSystem:读写文件
4.1 安装
# Bash 终端命令
npx expo install expo-file-system # 安装 FileSystem
4.2 新旧 API
旧版 API 保留在 expo-file-system/legacy。本文介绍新版 API。
4.3 目录路径
// JavaScript + React Native
import { Paths } from 'expo-file-system'; // 引入 Paths
Paths.document // 文档目录,持久化,不会被系统清除
Paths.cache // 缓存目录,系统可能在空间不足时清除
4.4 写入和读取
// JavaScript + React Native
import { File, Paths } from 'expo-file-system'; // 引入 File 和 Paths
const file = new File(Paths.document, 'notes.txt'); // 创建文件引用
file.write('这是笔记内容'); // 写入内容
const content = file.text(); // 读取内容
if (file.exists) console.log('文件存在'); // 检查是否存在
file.delete(); // 删除文件
4.5 保存和读取 JSON
// JavaScript + React Native
import { File, Paths } from 'expo-file-system'; // 引入 File 和 Paths
const data = { name: '小明', age: 18 }; // 要保存的数据
const file = new File(Paths.document, 'user.json'); // 创建文件引用
file.write(JSON.stringify(data)); // 写入 JSON
const json = file.text(); // 读取文本
const user = JSON.parse(json); // 解析对象
4.6 下载文件
// JavaScript + React Native
import { File, Paths } from 'expo-file-system'; // 引入 File 和 Paths
const url = 'https://example.com/image.jpg'; // 下载地址
const destination = new File(Paths.document, 'downloaded.jpg'); // 目标文件
const downloaded = await File.downloadFileAsync(url, destination); // 下载文件
console.log('下载完成', downloaded.uri); // 打印 URI
4.7 适合的场景
- 下载的图片、音频、视频;
- 日志文件;
- 超过 2KB 的敏感数据(加密后存储);
- 缓存大量 JSON。
五、综合示例:持久化设置页
// JavaScript + React Native
import { useState, useEffect } from 'react'; // 引入 Hook
import { View, Text, Switch, Button, ActivityIndicator, StyleSheet } from 'react-native'; // 引入组件
import AsyncStorage from '@react-native-async-storage/async-storage'; // 引入 AsyncStorage
import * as SecureStore from 'expo-secure-store'; // 引入 SecureStore
export default function SettingsScreen() { // 定义设置组件
const [darkMode, setDarkMode] = useState(false); // 暗色模式状态
const [notifications, setNotifications] = useState(true); // 通知状态
const [token, setToken] = useState(null); // 令牌状态
const [loading, setLoading] = useState(true); // 加载状态
useEffect(() => { // 挂载时加载
async function load() { // 定义加载函数
try { // 开始 try
const [themeJson, notifJson, storedToken] = await Promise.all([ // 并行读取
AsyncStorage.getItem('darkMode'), // 读取暗色模式
AsyncStorage.getItem('notifications'), // 读取通知设置
SecureStore.getItemAsync('authToken'), // 读取令牌
]); // 结束并行
if (themeJson !== null) setDarkMode(JSON.parse(themeJson)); // 更新暗色模式
if (notifJson !== null) setNotifications(JSON.parse(notifJson)); // 更新通知
setToken(storedToken); // 更新令牌
} catch (e) { // 捕获错误
console.error('加载设置失败', e); // 打印错误
} finally { // 收尾
setLoading(false); // 关闭加载
} // 结束 try
} // 结束 load
load(); // 调用加载
}, []); // 空数组
const toggleDarkMode = async (value) => { // 切换暗色模式
setDarkMode(value); // 更新状态
await AsyncStorage.setItem('darkMode', JSON.stringify(value)); // 保存值
}; // 结束 toggleDarkMode
const handleSignOut = async () => { // 退出登录
await SecureStore.deleteItemAsync('authToken'); // 删除令牌
setToken(null); // 清空状态
}; // 结束 handleSignOut
if (loading) { // 加载中
return <View style={styles.center}><ActivityIndicator size="large" /></View>; // 显示指示器
} // 结束判断
return ( // 返回界面描述
<View style={[styles.container, darkMode && styles.dark]}> // 外层容器
<Text style={[styles.heading, darkMode && styles.darkText]}>设置</Text> // 标题
<View style={styles.row}> // 暗色模式行
<Text style={[styles.label, darkMode && styles.darkText]}>暗色模式</Text> // 标签
<Switch value={darkMode} onValueChange={toggleDarkMode} /> // 开关
</View> // 结束行
<View style={styles.section}> // 登录状态区
<Text style={[styles.label, darkMode && styles.darkText]}> // 状态文字
登录状态:{token ? '已登录' : '未登录'} // 显示状态
</Text> // 结束文字
{token && <Button title="退出登录" onPress={handleSignOut} />} // 退出按钮
</View> // 结束登录状态区
</View> // 结束容器
); // 结束 return
} // 结束 SettingsScreen
const styles = StyleSheet.create({ // 创建样式表
container: { flex: 1, padding: 20, paddingTop: 60, backgroundColor: '#fff' }, // 容器样式
dark: { backgroundColor: '#222' }, // 暗色模式
center: { flex: 1, justifyContent: 'center', alignItems: 'center' }, // 居中容器
heading: { fontSize: 28, fontWeight: 'bold', marginBottom: 24 }, // 标题样式
darkText: { color: '#fff' }, // 暗色文字
row: { // 行样式
flexDirection: 'row', // 水平排列
justifyContent: 'space-between', // 两端对齐
alignItems: 'center', // 垂直居中
paddingVertical: 16, // 垂直内边距
borderBottomWidth: 1, // 底部分隔线
borderBottomColor: '#eee', // 分隔线颜色
}, // 结束行样式
label: { fontSize: 16 }, // 标签样式
section: { marginTop: 32, gap: 12 }, // 区块样式
}); // 结束样式表
两种存储的配合:主题用 AsyncStorage,令牌用 SecureStore。
六、存储与"视图 = 函数(状态)"
本地存储没有打破"视图 = 函数(状态)“,它只是给状态增加了一个"持久层”:
// 概念表达
本地存储 <-> 状态 <-> 视图 // 三者循环
- 组件挂载时,从本地存储读取数据,更新状态;
- 状态变化,界面更新;
- 用户操作改变状态;
- 状态变化时,把新值写入本地存储。
本地存储是状态的"外部记忆"。它让状态在 App 关闭后依然存在。下次启动时,状态从存储中恢复,界面也恢复。
七、练习题
练习题1:选择题——存储方案选型
题目:下面哪种数据最适合用 expo-secure-store 存储?
A. 用户的暗色模式偏好
B. 登录后的认证令牌
C. 一张下载的图片
D. 一篇很长的文章缓存
参考答案:B
解读:SecureStore 适合存储令牌、密码、API Key 等敏感的小数据。
练习题2:填空题——AsyncStorage 保存对象
题目:补全代码,把对象 user 保存到 AsyncStorage。
// JavaScript + React Native
await AsyncStorage.setItem('user', ____); // 补全保存逻辑
参考答案:JSON.stringify(user)
解读:AsyncStorage 只能保存字符串。对象需要先 JSON.stringify 转换。
练习题3:改错题——读取对象时的空值处理
题目:下面代码在键不存在时会崩溃。请指出问题并修正。
// JavaScript + React Native
const json = await AsyncStorage.getItem('user'); // 读取字符串
const user = JSON.parse(json); // 解析对象
参考答案:getItem 在键不存在时返回 null。应该写成 const user = json ? JSON.parse(json) : null;。
解读:读取存储数据时,一定要处理键不存在的情况。
练习题4:代码阅读题——SecureStore 大小限制
题目:为什么不应该把一张图片的 base64 字符串存到 SecureStore 里?
参考答案:SecureStore 有约 2KB 的大小限制,过大的值可能被底层平台拒绝。
解读:大文件应该用 FileSystem,文件路径可以存在 SecureStore 里。
练习题5:动手题——保存主题偏好
题目:用 AsyncStorage 保存用户的主题偏好('light' 或 'dark'),并在组件挂载时读取。
参考答案:
// JavaScript + React Native
export function useTheme() { // 定义 Hook
const [theme, setTheme] = useState('light'); // 主题状态
const [loaded, setLoaded] = useState(false); // 加载状态
useEffect(() => { // 挂载时读取
async function load() { // 定义加载函数
const saved = await AsyncStorage.getItem('theme'); // 读取主题
if (saved) setTheme(saved); // 有值就更新
setLoaded(true); // 标记已加载
} // 结束 load
load(); // 调用加载
}, []); // 空数组
const toggleTheme = async () => { // 切换主题
const next = theme === 'light' ? 'dark' : 'light'; // 计算下一个主题
setTheme(next); // 更新状态
await AsyncStorage.setItem('theme', next); // 保存主题
}; // 结束 toggleTheme
return { theme, toggleTheme, loaded }; // 返回接口
} // 结束 useTheme
解读:这是持久化偏好设置的标准模式。loaded 避免闪烁。
练习题6:简答题——AsyncStorage 和 SecureStore 的区别
题目:简述 AsyncStorage 和 SecureStore 在安全性和适用场景上的区别。
参考答案:AsyncStorage 是明文存储,适合非敏感数据。SecureStore 使用设备的安全硬件加密,适合敏感数据,但有约 2KB 大小限制。
解读:选择存储方案时,安全性是第一考量。
练习题7:SecureStore 基础题
题目:用 SecureStore 保存、读取和删除一个令牌。
参考答案:
// JavaScript + React Native
await SecureStore.setItemAsync('authToken', token); // 保存
const token = await SecureStore.getItemAsync('authToken'); // 读取
await SecureStore.deleteItemAsync('authToken'); // 删除
解读:SecureStore 的 API 类似 AsyncStorage,但方法名有 Async 后缀。
练习题8:封装自定义 Hook 题
题目:封装一个 useAsyncStorage Hook,接收 key 和 defaultValue。
参考答案:
// JavaScript + React Native
function useAsyncStorage(key, defaultValue) { // 定义 Hook
const [value, setValue] = useState(defaultValue); // 创建状态
const [loaded, setLoaded] = useState(false); // 加载状态
useEffect(() => { // 挂载时读取
async function load() { // 定义加载函数
const json = await AsyncStorage.getItem(key); // 读取
if (json !== null) setValue(JSON.parse(json)); // 有值就更新
setLoaded(true); // 标记已加载
} // 结束 load
load(); // 调用加载
}, [key]); // 依赖 key
useEffect(() => { // 值变化时保存
if (loaded) AsyncStorage.setItem(key, JSON.stringify(value)).catch(console.error); // 保存
}, [key, value, loaded]); // 依赖
return [value, setValue, loaded]; // 返回接口
} // 结束 useAsyncStorage
解读:这个 Hook 把"读取-状态-保存"流程封装起来,使用时像 useState 一样自然。
练习题9:FileSystem 写入题
题目:用 expo-file-system 把一段文本写入文档目录下的 notes.txt,然后读出来。
参考答案:
// JavaScript + React Native
import { File, Paths } from 'expo-file-system'; // 引入组件
const file = new File(Paths.document, 'notes.txt'); // 创建文件引用
file.write('这是一条笔记'); // 写入
const content = file.text(); // 读取
console.log(content); // 打印
解读:Paths.document 是持久化目录,文件不会被系统自动清除。
练习题10:FileSystem JSON 题
题目:用 FileSystem 保存一个 settings 对象,并读取回来。
参考答案:
// JavaScript + React Native
const settings = { theme: 'dark', fontSize: 16 }; // 设置对象
const file = new File(Paths.document, 'settings.json'); // 创建文件引用
file.write(JSON.stringify(settings)); // 写入 JSON
const json = file.text(); // 读取文本
const loaded = JSON.parse(json); // 解析对象
解读:FileSystem 适合存储较大的 JSON 数据。
练习题11:认证持久化题
题目:用 SecureStore 和 Context 实现认证状态的持久化。
参考答案:
// JavaScript + React Native
export function AuthProvider({ children }) { // 定义 Provider
const [token, setToken] = useState(null); // 令牌状态
const [loading, setLoading] = useState(true); // 加载状态
useEffect(() => { // 挂载时读取
SecureStore.getItemAsync('authToken') // 读取令牌
.then((t) => setToken(t)) // 更新状态
.finally(() => setLoading(false)); // 关闭加载
}, []); // 空数组
const signIn = async (t) => { // 登录
await SecureStore.setItemAsync('authToken', t); // 保存令牌
setToken(t); // 更新状态
}; // 结束 signIn
const signOut = async () => { // 退出
await SecureStore.deleteItemAsync('authToken'); // 删除令牌
setToken(null); // 清空状态
}; // 结束 signOut
return ( // 返回界面描述
<AuthContext.Provider value={{ token, loading, signIn, signOut }}> // 提供接口
{children} // 渲染子组件
</AuthContext.Provider> // 结束 Provider
); // 结束 return
} // 结束 AuthProvider
解读:这是认证持久化的标准模式。loading 状态在令牌读取完成前阻止渲染。
练习题12:安全性判断题
题目:下面的做法有什么安全问题?
// JavaScript + React Native
await AsyncStorage.setItem('authToken', token); // 保存令牌
参考答案:AsyncStorage 是明文存储的,认证令牌可以被提取。应该使用 expo-secure-store。
解读:攻击者可以从设备上提取令牌并重放会话。
练习题13:存储选型题
题目:为下面的数据选择最合适的存储方案:
- 用户的暗色模式偏好;
- 登录后的 JWT 令牌;
- 用户下载的 10MB 视频文件;
- 一个需要频繁读写的购物车状态。
参考答案:
- AsyncStorage(或 MMKV);
- SecureStore;
- FileSystem;
- MMKV(如果使用开发构建),否则 AsyncStorage。
解读:非敏感小数据用 AsyncStorage,敏感小数据用 SecureStore,大文件用 FileSystem,高频读写状态用 MMKV。
练习题14:综合题——带持久化的待办事项
题目:改造第3课的待办事项应用,用 AsyncStorage 持久化 todos 数组。
参考答案:
// JavaScript + React Native
const STORAGE_KEY = 'todos'; // 存储键名
export default function App() { // 定义并默认导出 App
const [todos, setTodos] = useState([]); // 待办状态
const [loaded, setLoaded] = useState(false); // 加载状态
const isFirst = useRef(true); // 是否首次渲染
useEffect(() => { // 挂载时读取
async function load() { // 定义加载函数
const json = await AsyncStorage.getItem(STORAGE_KEY); // 读取
if (json) setTodos(JSON.parse(json)); // 有值就更新
setLoaded(true); // 标记已加载
} // 结束 load
load(); // 调用加载
}, []); // 空数组
useEffect(() => { // 值变化时保存
if (!loaded) return; // 未加载完成不保存
if (isFirst.current) { // 首次跳过
isFirst.current = false; // 标记
return; // 返回
} // 结束判断
AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(todos)).catch(console.error); // 保存
}, [todos, loaded]); // 依赖
// ... 其余 UI 代码
} // 结束 App
解读:两个 useEffect 分别负责读取和保存。isFirst 避免首次读取后立即触发保存。
练习题15:综合应用题——带持久化的用户资料编辑器
题目:创建一个用户资料编辑器,包含:
- 昵称(非敏感,用 AsyncStorage);
- API Key(敏感,用 SecureStore);
- 一个"保存"按钮,同时保存两项;
- App 启动时自动恢复。
参考答案:
// JavaScript + React Native
import { useState, useEffect } from 'react'; // 引入 Hook
import { View, Text, TextInput, Button, ActivityIndicator, StyleSheet } from 'react-native'; // 引入组件
import AsyncStorage from '@react-native-async-storage/async-storage'; // 引入 AsyncStorage
import * as SecureStore from 'expo-secure-store'; // 引入 SecureStore
export default function App() { // 定义并默认导出 App
const [nickname, setNickname] = useState(''); // 昵称状态
const [apiKey, setApiKey] = useState(''); // API Key 状态
const [loading, setLoading] = useState(true); // 加载状态
const [saved, setSaved] = useState(false); // 保存成功状态
useEffect(() => { // 挂载时读取
async function load() { // 定义加载函数
try { // 开始 try
const [name, key] = await Promise.all([ // 并行读取
AsyncStorage.getItem('nickname'), // 读取昵称
SecureStore.getItemAsync('apiKey'), // 读取 API Key
]); // 结束并行
if (name) setNickname(name); // 更新昵称
if (key) setApiKey(key); // 更新 API Key
} catch (e) { // 捕获错误
console.error('加载失败', e); // 打印错误
} finally { // 收尾
setLoading(false); // 关闭加载
} // 结束 try
} // 结束 load
load(); // 调用加载
}, []); // 空数组
const handleSave = async () => { // 定义保存函数
try { // 开始 try
await Promise.all([ // 并行保存
AsyncStorage.setItem('nickname', nickname), // 保存昵称
SecureStore.setItemAsync('apiKey', apiKey), // 保存 API Key
]); // 结束并行
setSaved(true); // 显示保存成功
setTimeout(() => setSaved(false), 2000); // 2 秒后隐藏
} catch (e) { // 捕获错误
console.error('保存失败', e); // 打印错误
} // 结束 try
}; // 结束 handleSave
if (loading) { // 加载中
return <View style={styles.center}><ActivityIndicator size="large" /></View>; // 显示指示器
} // 结束判断
return ( // 返回界面描述
<View style={styles.container}> // 外层容器
<Text style={styles.heading}>用户资料</Text> // 标题
<Text style={styles.label}>昵称(非敏感)</Text> // 昵称标签
<TextInput // 昵称输入框
style={styles.input} // 应用样式
value={nickname} // 值由 nickname 决定
onChangeText={setNickname} // 输入变化时更新
placeholder="请输入昵称" // 占位提示
/> // 结束输入框
<Text style={styles.label}>API Key(敏感)</Text> // API Key 标签
<TextInput // API Key 输入框
style={styles.input} // 应用样式
value={apiKey} // 值由 apiKey 决定
onChangeText={setApiKey} // 输入变化时更新
placeholder="请输入 API Key" // 占位提示
secureTextEntry // 隐藏输入
autoCapitalize="none" // 不自动大写
/> // 结束输入框
<Button title="保存" onPress={handleSave} /> // 保存按钮
{saved && <Text style={styles.saved}>已保存</Text>} // 保存成功提示
</View> // 结束容器
); // 结束 return
} // 结束 App
const styles = StyleSheet.create({ // 创建样式表
container: { flex: 1, padding: 20, paddingTop: 60 }, // 容器样式
center: { flex: 1, justifyContent: 'center', alignItems: 'center' }, // 居中容器
heading: { fontSize: 28, fontWeight: 'bold', marginBottom: 24 }, // 标题样式
label: { fontSize: 14, color: '#666', marginBottom: 6, marginTop: 16 }, // 标签样式
input: { // 输入框样式
borderWidth: 1, // 边框宽度
borderColor: '#ccc', // 边框颜色
borderRadius: 6, // 圆角
paddingHorizontal: 12, // 水平内边距
height: 44, // 高度
fontSize: 16, // 字号
}, // 结束输入框样式
saved: { color: 'green', marginTop: 12, textAlign: 'center' }, // 保存成功样式
}); // 结束样式表
解读:昵称用 AsyncStorage,API Key 用 SecureStore。启动时用 Promise.all 并行读取,保存时并行写入。敏感和非敏感数据分开存储,是持久化设计的基本原则。
八、总结
这一课我们学习了 React Native 中数据持久化的核心方案:
- AsyncStorage:异步、明文的键值存储,适合非敏感数据;
- SecureStore:加密的键值存储,适合令牌、密码等敏感数据,有约 2KB 的大小限制;
- FileSystem:文件系统访问,适合图片、日志、大 JSON 文件;
- MMKV:高性能同步存储,适合频繁读写的状态,但需要开发构建,不支持 Expo Go;
- 选型规则:机密用 SecureStore,速度用 MMKV,兼容用 AsyncStorage,大文件用 FileSystem;
- 封装成
useAsyncStorageHook,可以让持久化状态的使用和普通useState一样自然; - 认证持久化用 SecureStore + Context,启动时读取,登录时保存,退出时删除;
- 读取存储数据时,一定要处理键不存在的情况;
- 持久化是状态的"外部记忆":存储 ↔ 状态 ↔ 视图,三者循环。
记住一句话:
本地存储给状态加了一层"记忆",让"视图 = 函数(状态)"在 App 重启后依然成立。
第11课预告
下一课,我们进入 【React Native娓娓道来】第11课 错误处理与调试:Error Boundary、日志与性能分析。
更多推荐



所有评论(0)