
在react应用开发中,从外部api获取数据是常见需求。然而,不当的数据获取策略可能导致重复的api请求,从而降低应用性能并增加服务器负载。例如,在一个随机电影生成器应用中,每次点击按钮都重新调用api获取电影列表,然后从中选择一部,这显然是低效的。
最初的实现可能如下所示,每次生成新电影时都会触发API调用:
import React, { useState, useEffect } from 'react';
interface Movie {
title: string;
}
// 模拟API请求
async function getMoviesRequest(): Promise<Movie[]> {
console.log('Calling API to get movies...');
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ title: "The Shawshank Redemption" },
{ title: "The Godfather" },
{ title: "The Dark Knight" },
{ title: "Pulp Fiction" },
{ title: "Forrest Gump" },
{ title: "Inception" },
{ title: "The Matrix" },
{ title: "Interstellar" },
{ title: "Parasite" },
{ title: "Whiplash" }
]);
}, 1000); // 模拟网络延迟
});
}
function getRandomInt(max: number): number {
return Math.floor(Math.random() * max);
}
export default function Page() {
const [movie, setMovie] = useState<Movie>({
title: ""
});
useEffect(() => {
setNewMovie();
}, []);
function setNewMovie() {
getMoviesRequest().then((newMovies) => {
const randomIndex = getRandomInt(9);
const movie = newMovies[randomIndex];
setMovie(movie);
});
}
return (
<div>
<h3>
{movie.title}
</h3>
<button onClick={setNewMovie}>
Generate New Movie
</button>
</div>
);
}上述代码的问题在于,每次点击“Generate New Movie”按钮时,都会重新调用getMoviesRequest(),导致不必要的网络请求。为了优化这一点,我们希望只在组件首次加载时调用一次API,并将获取到的电影列表缓存起来,供后续操作使用。
为了缓存数据,一个常见的想法是使用useRef,因为它可以在组件的整个生命周期内持久化一个可变值,并且其值的改变不会触发组件重新渲染。然而,直接将Promise的解析值赋给useRef并在同一渲染周期内尝试访问,可能会遇到异步操作带来的挑战。
考虑以下尝试缓存电影列表的代码:
import React, { useState, useEffect, useRef } from 'react';
// ... (Movie interface, getMoviesRequest, getRandomInt 保持不变)
export default function Page() {
const movies = useRef<Movie[]>([]); // 尝试用useRef缓存电影列表
const [movie, setMovie] = useState<Movie>({
title: ""
});
useEffect(() => {
setNewMovies(); // 调用API获取电影并存入ref
setNewMovie(); // 尝试从ref中获取电影并设置
}, []);
function setNewMovies() {
getMoviesRequest().then((newMovies) => {
movies.current = newMovies; // 在Promise解析后更新ref
});
}
function setNewMovie() {
// 此时 movies.current 可能仍然是空数组,因为 setNewMovies 是异步的
const randomIndex = getRandomInt(9);
const movie = movies.current[randomIndex]; // 访问时可能数据未更新
setMovie(movie);
}
return (
<div>
<h3>
{movie.title}
</h3>
<button onClick={setNewMovie}>
Generate New Movie
</button>
</div>
);
}这段代码的问题在于setNewMovies()是一个异步函数(因为它内部调用了getMoviesRequest().then(...)),而setNewMovie()紧随其后同步执行。当setNewMovie()被调用时,setNewMovies()中的Promise可能尚未解析,导致movies.current仍然是其初始值(空数组),从而无法正确获取电影。
要解决上述异步时序问题,我们需要确保在尝试使用缓存数据之前,数据已经被成功获取并存储。这可以通过将数据获取和缓存逻辑封装在一个async函数中,并利用await关键字等待异步操作完成来实现。
核心思路是创建一个getMoviesFromCacheOrApi函数,它首先检查useRef中是否已有缓存数据。如果存在,则直接返回缓存数据;如果不存在,则发起API请求,将结果存入useRef,然后返回。
以下是优化后的实现:
import React, { useState, useEffect, useRef } from 'react';
interface Movie {
title: string;
}
// 模拟API请求
async function getMoviesRequest(): Promise<Movie[]> {
console.log('Calling API to get movies...');
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ title: "The Shawshank Redemption" },
{ title: "The Godfather" },
{ title: "The Dark Knight" },
{ title: "Pulp Fiction" },
{ title: "Forrest Gump" },
{ title: "Inception" },
{ title: "The Matrix" },
{ title: "Interstellar" },
{ title: "Parasite" },
{ title: "Whiplash" }
]);
}, 1000); // 模拟网络延迟
});
}
function getRandomInt(max: number): number {
return Math.floor(Math.random() * max);
}
export default function Page() {
// 使用 useRef 缓存电影列表,初始值为 null 表示尚未加载
const cachedMoviesFromApi = useRef<Movie[] | null>(null);
const [movie, setMovie] = useState<Movie>({
title: ""
});
useEffect(() => {
// 组件首次加载时,调用 setNewMovie 来获取并设置第一部电影
// setNewMovie 是 async 函数,所以这里也需要 await 或者 .then()
setNewMovie().then();
}, []);
/**
* 异步函数:从缓存或API获取电影列表
* @returns {Promise<Movie[]>} 电影列表
*/
async function getMoviesFromCacheOrApi(): Promise<Movie[]> {
// 检查缓存中是否已有数据
if (cachedMoviesFromApi.current) {
console.log('Returning movies from cache.');
return cachedMoviesFromApi.current;
}
// 如果缓存中没有,则调用API获取数据
console.log('Fetching movies from API...');
const moviesFromApi = await getMoviesRequest();
// 将获取到的数据存入 useRef 进行缓存
cachedMoviesFromApi.current = moviesFromApi;
return moviesFromApi;
}
/**
* 异步函数:设置一部新的随机电影
*/
async function setNewMovie() {
// 等待电影列表获取完成(可能来自缓存,也可能来自API)
const movies = await getMoviesFromCacheOrApi();
// 确保电影列表不为空,避免因API返回空数组导致错误
if (!movies || movies.length === 0) {
console.warn("No movies available to select.");
setMovie({ title: "No movies found" });
return;
}
const randomIndex = getRandomInt(movies.length); // 根据实际电影数量生成索引
const selectedMovie = movies[randomIndex];
setMovie(selectedMovie);
}
return (
<div>
<h3>
{movie.title}
</h3>
<button onClick={setNewMovie}>
Generate New Movie
</button>
</div>
);
}cachedMoviesFromApi = useRef<Movie[] | null>(null):
getMoviesFromCacheOrApi() 函数:
setNewMovie() 函数:
useEffect 钩子:
useRef与useState的选择:
async/await的重要性:
初始值设置:
错误处理:
缓存策略:
通过巧妙地结合useRef和async/await,我们成功地实现了一个高效的API数据缓存机制。这不仅减少了不必要的网络请求,提升了应用性能,还优化了用户体验。理解useRef的特性以及async/await在处理异步流中的作用,是构建高性能React应用的关键。
以上就是React中利用useRef和async/await优化API数据缓存与管理的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号