在移动应用中,数据缓存 是提升应用性能和用户体验的重要手段。通过缓存数据,可以减少网络请求次数,降低延迟,提高应用在离线或网络不稳定情况下的可用性。React Native 提供了多种数据缓存策略,包括内存缓存、异步存储(AsyncStorage)以及第三方缓存库。本章节将详细介绍常见的数据缓存策略、实现方式以及最佳实践。
2.1 数据缓存概述
数据缓存 的目标是将从服务器获取的数据存储在本地,以便在后续请求中快速访问,而无需每次都进行网络请求。缓存策略的选择取决于数据的类型、使用频率、更新频率以及应用的具体需求。
常见的缓存策略包括:
- 内存缓存: 将数据存储在内存中,访问速度快,但生命周期与组件生命周期相关。
- 异步存储(AsyncStorage): 将数据持久化存储在设备上,适用于需要长期保存的数据。
- 第三方缓存库: 如
react-query
,SWR
等,提供更强大的缓存管理和数据同步功能。
2.2 内存缓存
内存缓存 是将数据存储在内存中,访问速度快,适用于生命周期较短的数据。
2.2.1 使用 React 的 useState
和 useEffect
可以通过 React 的 useState
和 useEffect
Hook 实现简单的内存缓存。
示例:
javascript
// components/MemoryCacheExample.js
import React, { useState, useEffect } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
const MemoryCacheExample = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const fetchData = async () => {
setLoading(true);
// 模拟网络请求
await new Promise((resolve) => setTimeout(resolve, 1000));
const fetchedData = { id: 1, title: 'Hello, World!' };
setData(fetchedData);
setLoading(false);
};
useEffect(() => {
if (!data) {
fetchData();
}
}, []);
return (
<View style={styles.container}>
{loading ? (
<Text>Loading...</Text>
) : data ? (
<Text style={styles.text}>{data.title}</Text>
) : null}
<Button title="Refresh" onPress={fetchData} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontSize: 18,
marginBottom: 10,
},
});
export default MemoryCacheExample;
解释:
- 数据存储在
data
状态中。 - 组件挂载时,如果
data
为空,则发起网络请求。 - 数据被存储在内存中,刷新页面时不会重新发起网络请求。
2.2.2 使用 React Context API
可以通过 Context API 实现全局内存缓存。
示例:
javascript
// context/DataContext.js
import React, { createContext, useState, useEffect } from 'react';
export const DataContext = createContext();
export const DataProvider = ({ children }) => {
const [data, setData] = useState(null);
useEffect(() => {
// 模拟数据获取
const fetchData = async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
const fetchedData = { id: 1, title: 'Hello, Context!' };
setData(fetchedData);
};
fetchData();
}, []);
return <DataContext.Provider value={data}>{children}</DataContext.Provider>;
};
javascript
// components/MemoryCacheWithContext.js
import React, { useContext } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { DataContext, DataProvider } from '../context/DataContext';
const MemoryCacheWithContext = () => {
const data = useContext(DataContext);
const refreshData = () => {
// 重新获取数据
// 这里简单模拟,实际应用中需要更新 Context 的值
alert('Refresh Data');
};
return (
<View style={styles.container}>
{data ? <Text style={styles.text}>{data.title}</Text> : <Text>Loading...</Text>}
<Button title="Refresh" onPress={refreshData} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontSize: 18,
marginBottom: 10,
},
});
export default MemoryCacheWithContext;
解释:
DataContext
提供全局数据存储。- 数据被存储在 Context 中,所有订阅的组件都可以访问到。
2.3 使用 AsyncStorage 进行持久化缓存
AsyncStorage 是 React Native 提供的一个简单的异步键值对存储系统,适用于持久化存储数据。
2.3.1 安装 AsyncStorage
对于 React Native 0.59 及以上版本,AsyncStorage 已经内置,无需额外安装。
对于 React Native 0.58 及以下版本,需要手动安装:
bash
npm install @react-native-async-storage/async-storage
2.3.2 基本用法
存储数据:
javascript
import AsyncStorage from '@react-native-async-storage/async-storage';
const storeData = async (key, value) => {
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(key, jsonValue);
} catch (e) {
console.error(e);
}
};
获取数据:
javascript
const getData = async (key) => {
try {
const jsonValue = await AsyncStorage.getItem(key);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (e) {
console.error(e);
return null;
}
};
示例:
javascript
// components/AsyncStorageCacheExample.js
import React, { useState, useEffect } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
const AsyncStorageCacheExample = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const fetchData = async () => {
setLoading(true);
// 模拟网络请求
await new Promise((resolve) => setTimeout(resolve, 1000));
const fetchedData = { id: 1, title: 'Hello, AsyncStorage!' };
setData(fetchedData);
await AsyncStorage.setItem('data_key', JSON.stringify(fetchedData));
setLoading(false);
};
useEffect(() => {
const loadData = async () => {
const storedData = await AsyncStorage.getItem('data_key');
if (storedData) {
setData(JSON.parse(storedData));
} else {
fetchData();
}
};
loadData();
}, []);
return (
<View style={styles.container}>
{loading ? (
<Text>Loading...</Text>
) : data ? (
<Text style={styles.text}>{data.title}</Text>
) : null}
<Button title="Refresh" onPress={fetchData} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontSize: 18,
marginBottom: 10,
},
});
export default AsyncStorageCacheExample;
解释:
- 数据被存储在
data_key
键下。 - 组件挂载时,先从 AsyncStorage 中获取数据,如果不存在,则发起网络请求并存储数据。
2.3.3 注意事项
- 数据大小限制: AsyncStorage 适用于存储少量数据,不适合存储大量数据。
- 性能: AsyncStorage 的性能不如内存缓存,不适合频繁读写操作。
- 安全性: AsyncStorage 不提供加密功能,敏感数据需要谨慎处理。
2.4 使用第三方缓存库
第三方缓存库提供了更强大的缓存管理和数据同步功能,适用于复杂的数据缓存需求。
2.4.1 react-query
react-query
是一个用于数据获取和缓存的库,支持自动重新获取数据、缓存失效、乐观更新等功能。
安装:
bash
npm install @tanstack/react-query
示例:
javascript
// components/ReactQueryExample.js
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
const queryClient = new QueryClient();
const fetchData = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
return response.json();
};
const MyQueryComponent = () => {
const { data, error, isLoading } = useQuery(['post'], fetchData, {
staleTime: 5 * 60 * 1000, // 数据缓存时间
});
if (isLoading) return <Text>Loading...</Text>;
if (error) return <Text>Error: {error.message}</Text>;
return <Text style={styles.text}>{data.title}</Text>;
};
const ReactQueryExample = () => {
return (
<QueryClientProvider client={queryClient}>
<View style={styles.container}>
<MyQueryComponent />
</View>
</QueryClientProvider>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontSize: 18,
marginBottom: 10,
},
});
export default ReactQueryExample;
解释:
useQuery
钩子用于发起数据请求,并自动管理缓存。staleTime
定义数据缓存时间,超过时间后数据会被标记为过期,下次请求时会重新获取。
2.4.1 SWR
简介
SWR(Stale-While-Revalidate)是一个由 Vercel 开发的 React Hooks 库,用于数据获取和缓存。SWR 的核心思想是:在请求数据时,首先返回缓存中的旧数据(stale),然后在后台重新获取最新数据(revalidate),并将最新数据更新到缓存中。这种方式可以有效减少请求延迟,提高用户体验。
SWR 的主要特点:
- 快速响应: 立即返回缓存数据,同时在后台重新获取最新数据。
- 自动重新获取: 在组件重新挂载或窗口重新聚焦时,自动重新获取数据。
- 错误重试: 在请求失败时,自动重试请求。
- 乐观更新: 在数据提交时,先更新本地缓存,再提交到服务器。
- 灵活的缓存策略: 支持自定义缓存逻辑和缓存失效时间。
安装 SWR:
bash
npm install swr
2.4.2 基本用法
以下是一个使用 SWR 进行数据获取和缓存的示例。
示例:
javascript
// components/SWRExample.js
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import useSWR from 'swr';
const fetcher = async (url) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
const SWRExample = () => {
const { data, error, isValidating } = useSWR('https://jsonplaceholder.typicode.com/posts/1', fetcher);
if (error) return <Text style={styles.text}>Error: {error.message}</Text>;
if (isValidating) return <Text style={styles.text}>Loading...</Text>;
return (
<View style={styles.container}>
<Text style={styles.text}>Title: {data.title}</Text>
<Text style={styles.text}>Body: {data.body}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
text: {
fontSize: 16,
},
});
export default SWRExample;
解释:
-
useSWR
Hook:- 第一个参数是数据的唯一键(key),通常是请求的 URL。
- 第二个参数是数据获取函数(fetcher),用于发起网络请求。
- 返回一个对象,包含
data
,error
,isValidating
等属性。
-
数据获取流程:
- 组件挂载时,
useSWR
会检查缓存中是否存在数据。 - 如果有缓存数据,立即返回缓存数据,同时在后台重新获取最新数据。
- 如果没有缓存数据,则发起网络请求。
- 组件挂载时,
-
错误处理:
- 如果请求失败,
error
属性会包含错误信息。
- 如果请求失败,
-
加载状态:
isValidating
属性表示数据是否正在重新获取。
2.4.3 缓存失效与重新获取
SWR 提供了多种方式来控制缓存失效和重新获取数据。
2.4.3.1 revalidateOnFocus
默认情况下,SWR 会在窗口重新聚焦时重新获取数据。可以通过 revalidateOnFocus
选项关闭此功能。
示例:
javascript
const { data, error, isValidating } = useSWR('https://jsonplaceholder.typicode.com/posts/1', fetcher, {
revalidateOnFocus: false,
});
2.4.3.2 revalidateOnReconnect
默认情况下,SWR 会在网络重新连接时重新获取数据。可以通过 revalidateOnReconnect
选项关闭此功能。
示例:
javascript
const { data, error, isValidating } = useSWR('https://jsonplaceholder.typicode.com/posts/1', fetcher, {
revalidateOnReconnect: false,
});
2.4.3.3 refreshInterval
可以通过 refreshInterval
选项设置定时重新获取数据的间隔时间(毫秒)。
示例:
javascript
const { data, error, isValidating } = useSWR('https://jsonplaceholder.typicode.com/posts/1', fetcher, {
refreshInterval: 5000, // 每5秒重新获取一次数据
});
2.4.3.4 dedupingInterval
可以通过 dedupingInterval
选项设置请求去重的时间间隔(毫秒),避免在短时间内重复请求相同的数据。
示例:
javascript
const { data, error, isValidating } = useSWR('https://jsonplaceholder.typicode.com/posts/1', fetcher, {
dedupingInterval: 1000, // 1秒内重复请求相同的数据会被去重
});
2.4.4 错误重试
SWR 支持自动重试失败的请求,可以通过 errorRetryCount
和 errorRetryInterval
选项控制重试次数和重试间隔。
示例:
javascript
const { data, error, isValidating } = useSWR('https://jsonplaceholder.typicode.com/posts/1', fetcher, {
errorRetryCount: 3, // 最大重试次数
errorRetryInterval: 5000, // 重试间隔时间
});
2.4.5 乐观更新
SWR 支持乐观更新,即在数据提交时,先更新本地缓存,再提交到服务器。
示例:
javascript
import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import useSWR, { mutate } from 'swr';
const fetcher = async (url) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
const OptimisticUpdateExample = () => {
const { data, error, isValidating } = useSWR('https://jsonplaceholder.typicode.com/posts/1', fetcher);
const updateTitle = () => {
mutate('https://jsonplaceholder.typicode.com/posts/1', {
...data,
title: 'Updated Title',
}, false); // 先更新缓存
fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...data,
title: 'Updated Title',
}),
})
.then((response) => response.json())
.then((json) => {
mutate('https://jsonplaceholder.typicode.com/posts/1'); // 重新获取最新数据
})
.catch((error) => {
console.error(error);
mutate('https://jsonplaceholder.typicode.com/posts/1', data, false); // 回滚缓存
});
};
if (error) return <Text style={styles.text}>Error: {error.message}</Text>;
if (isValidating) return <Text style={styles.text}>Loading...</Text>;
return (
<View style={styles.container}>
<Text style={styles.text}>Title: {data.title}</Text>
<Button title="Update Title" onPress={updateTitle} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
text: {
fontSize: 16,
},
});
export default OptimisticUpdateExample;
解释:
mutate
函数用于更新缓存数据。- 在数据提交前,先调用
mutate
更新缓存。 - 然后发起网络请求,如果请求成功,则重新获取最新数据。
- 如果请求失败,则回滚缓存数据。
2.4.6 总结
SWR 是一个功能强大的数据获取和缓存库,适用于需要高效数据管理和缓存的应用。通过使用 SWR,可以简化数据获取逻辑,提高应用性能和用户体验。
作者简介
前腾讯电子签的前端负责人,现 whentimes tech CTO,专注于前端技术的大咖一枚!一路走来,从小屏到大屏,从 Web 到移动,什么前端难题都见过。热衷于用技术打磨产品,带领团队把复杂的事情做到极简,体验做到极致。喜欢探索新技术,也爱分享一些实战经验,帮助大家少走弯路!
温馨提示:可搜老码小张公号联系导师