实现带有缓存优先策略的 React Hook
在这篇博客文章中,我们将逐步介绍如何实现一个 React Hook,该 Hook 采用缓存优先策略从后端获取数据,仅在必要时才进行网络请求。我们的目标是展示如何通过缓存改善用户体验(UX),减少延迟,使应用感觉更快。
问题:高延迟的 API 调用
假设你正在构建一个从 API 获取产品数据的应用。在现实场景中,这些数据可能来自具有延迟的远程服务器(例如,2-3 秒的延迟)。在等待数据时,用户可能会感到沮丧。那么,我们如何解决这个问题呢?
通过使用 IndexedDB 缓存数据,我们可以立即显示之前获取的数据,同时在后台获取新数据。这样可以给用户带来更快、更流畅的体验。
逐步解析 useGetProducts Hook
让我们逐步解析 Hook,该 Hook 处理缓存逻辑和 API 调用。useGetProducts
步骤 1:定义 Product 接口
首先,我们定义 接口来表示我们要处理的数据:Product
interface Product {
id: number;
name: string;
}
这是一个简单的对象结构,包含 和 ,代表一个产品。id
name
步骤 2:模拟具有延迟的 API 调用
接下来,我们使用 函数模拟一个具有 2 秒延迟的 API 调用,以模拟真实的后端 API:getProducts
const getProducts = async (): Promise<Product[]> => {
return await new Promise<Product[]>((resolve) =>
setTimeout(
() =>
resolve([
{ id: 1, name: 'Product A' },
{ id: 2, name: 'Product B' },
]),
2000
)
);
};
这个函数模拟了一个延迟的 API 调用,返回一个产品数组,延迟时间为 2 秒。
步骤 3:实现 useGetProducts Hook
现在,我们来构建 Hook。核心思想是首先检查是否有可用的缓存数据,并立即使用它,然后在后台获取最新数据。useGetProducts
import React from 'react';
import { getCachedData, updateCache } from './cacheUtils';
const useGetProducts = (): {
data: Product[] | undefined;
loading: boolean;
} => {
const [products, setProducts] = React.useState<Product[] | undefined>(undefined);
const [loading, setLoading] = React.useState(true);
const cacheKey = 'cache_products';
// 加载产品数据,如果有缓存则立即显示,然后从 API 获取最新数据更新缓存
const loadProducts = React.useCallback(async () => {
setLoading(true);
// 第一步:从缓存加载数据,如果有缓存则立即显示
const cachedProducts = await getCachedData<Product[]>(cacheKey);
if (cachedProducts && cachedProducts.length > 0) {
setProducts(cachedProducts); // 立即显示缓存数据
setLoading(false);
}
// 第二步:从 API 获取最新数据,即使已经使用了缓存
try {
const response = await getProducts();
setProducts(response); // 更新为最新数据
updateCache(response, cacheKey); // 更新缓存
} catch (err) {
console.error('Error fetching products:', err);
} finally {
setLoading(false);
}
}, []);
React.useEffect(() => {
loadProducts();
}, [loadProducts]);
return { data: products, loading };
};
解释:
-
状态管理:我们使用 来管理两个状态:
React.useState
-
products
:产品数据数组。 -
loading
:一个标志,表示数据是否仍在获取中。
-
-
缓存查找:在 函数中,我们首先尝试使用 函数从缓存中获取产品数据。如果有缓存数据,它将立即显示,并将 设置为 。
loadProducts
getCachedData
loading
false
-
API 调用:同时,我们使用 函数从 API 获取最新数据。一旦获取到新数据,我们更新状态并使用 函数将其缓存起来以供未来使用。
getProducts
updateCache
-
乐观 UI:这种方法有助于改善用户体验,因为它会立即显示缓存数据,而新数据在后台加载,减少了感知延迟。
步骤 4:缓存实用函数
缓存功能由两个实用函数 和 提供,它们与 IndexedDB 交互以存储和检索数据。getCachedData
updateCache
import {
deleteFromIndexedDB,
getAllKeysFromIndexedDB,
getFromIndexedDB,
saveToIndexedDB,
} from '../indexDb';
const encryptData = (data: string): string => btoa(encodeURIComponent(data));
const decryptData = (encryptedData: string): string =>
decodeURIComponent(atob(encryptedData));
export const updateCache = async <T>(data: T, cacheKey: string) => {
try {
const [, cacheConst] = cacheKey.split('_');
const allKeys = await getAllKeysFromIndexedDB();
const existingKey = allKeys.find((key) => key.endsWith(`_${cacheConst}`));
if (existingKey) {
await deleteFromIndexedDB(existingKey);
}
const serializedData = JSON.stringify(data);
const encryptedData = encryptData(serializedData);
await saveToIndexedDB(cacheKey, encryptedData);
} catch (error) {
console.error('Failed to update cache:', error);
}
};
export const getCachedData = async <T>(cacheKey: string): Promise<T | null> => {
try {
const cached = await getFromIndexedDB(cacheKey);
if (cached) {
const decryptedData = decryptData(cached);
return JSON.parse(decryptedData) as T;
}
return null;
} catch (error) {
console.error('Failed to retrieve cached data:', error);
return null;
}
};
这些函数允许我们轻松地使用 IndexedDB 在浏览器中访问和更新缓存,提供持久的数据存储。
步骤 5:最终使用
以下是如何在组件中使用 Hook 来显示产品的示例:useGetProducts
import React from 'react';
import useGetProducts from './useGetProducts';
const ProductList: React.FC = () => {
const { data: products, loading } = useGetProducts();
if (loading && !products) return <div>Loading products...</div>;
if (!products || products.length === 0) return <div>No products found.</div>;
return (
<div>
<h3>Available Products:</h3>
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
};
export default ProductList;
结论
通过这种方式在 IndexedDB 中实现缓存,我们可以显著改善依赖于高延迟外部 API 的应用的用户体验。用户会立即看到缓存数据,而新数据会在后台更新,确保用户总是获得最新的信息而不必等待。
这种方法减少了应用的感知延迟,使其感觉更快、更响应,即使在后端可能较慢的情况下也是如此。