React多页面图书应用:NYT畅销书渲染异常及请求优化问询
纽约时报畅销书列表加载优化问题
问题背景
我开发了一个基于React的多页面图书应用,计划在首页底部展示纽约时报(NYT)畅销书列表。核心逻辑为:
- 调用NYT Books API获取当前畅销书的ISBN列表
- 遍历ISBN列表,调用Google Books API获取对应图书数据并存入state
原代码问题现象
- 初始渲染时,畅销书列表元素无法显示
- 跳转其他页面再返回首页,列表元素仅短暂显示后消失
- 切换主题状态后,列表元素正常显示并保持至页面刷新
将getBooksWithIsbn改为async/await函数后解决了显示问题,但请求总耗时约5秒,希望优化该耗时。
相关代码
Context文件(原版本)
import React, { createContext, useState } from "react" const Context = createContext() function ContextProvider(props) { const [bestSeller, setBestSeller] = useState([]) const [theme, setTheme] = useState( () => JSON.parse(localStorage.getItem("theme")) || "light" ) async function getBestSellerIsbns() { try { const url = `https://api.nytimes.com/svc/books/v3/lists/current/hardcover-fiction.json?&api-key=${NYT_API_KEY}` const response = await fetch(url) const data = await response.json() const isbns = data.results.books.map(book => book.primary_isbn10) getBooksWithIsbn(isbns) } catch (error) { console.log(error) } } function getBooksWithIsbn(isbnList) { let arr = []; for (let isbn of isbnList) { fetch(`https://www.googleapis.com/books/v1/volumes?q=isbn:${isbn}&printType=books`) .then((res) => res.json()) .then((data) => { arr.push(data.items[0]) }) } setBestSeller(arr) } function changeTheme() { setTheme(prevTheme => prevTheme === "light" ? "dark" : "light") } return ( <Context.Provider value={{ bestSeller, changeTheme, getBestSellerIsbns }} > {props.children} </Context.Provider> ) }
Homepage组件
const HomePage = () => { const { bestSeller, getBestSellerIsbns} = useContext(Context) useEffect(() => { getBestSellerIsbns() }, []) const bestSellerElements = bestSeller.map(best => ( <BestsellerEl key={best.id} best={best}/> )) return ( <div className="homepage-container"> <div className="bottom-container"> <div className="container-title"> <h1>New York Times Bestsellers:</h1> </div> <div classname="bestseller-div"> {bestSellerElements} </div> </div> </div> ) }
Navbar组件
const Navbar = () => { const {changeTheme} = useContext(Context) return ( <div className="navbar"> <div className="nav-elements"> <label className="switch"> <input type="checkbox" onClick={changeTheme} /> <span className="slider"></span> </label> </div> </div> ); }
修改后的getBooksWithIsbn函数(解决显示问题但耗时久)
async function getBooksWithIsbn(isbnList) { try { let arr = [] for(let isbn of isbnList) { const url = `https://www.googleapis.com/books/v1/volumes?q=isbn:${isbn}&printType=books` const response = await fetch(url) const data = await response.json() arr.push(data.items[0]) } setBestSeller(arr) } catch (error) { console.log(error) } }
优化方案
1. 并行请求替代串行请求
修改后的async/await是串行执行每个请求,总耗时为所有请求时间之和。改用Promise.all并行发起所有请求,总耗时等于最长的单个请求时间,能大幅缩短加载时间。
优化后的代码:
async function getBooksWithIsbn(isbnList) { try { // 生成所有ISBN对应的请求Promise const bookPromises = isbnList.map(isbn => { const url = `https://www.googleapis.com/books/v1/volumes?q=isbn:${isbn}&printType=books`; return fetch(url) .then(res => res.json()) .then(data => data.items?.[0]); // 用可选链避免无匹配图书时报错 }); // 并行等待所有请求完成 const books = await Promise.all(bookPromises); // 过滤无效数据(如未找到的图书) const validBooks = books.filter(book => book); setBestSeller(validBooks); } catch (error) { console.error('加载畅销书数据失败:', error); } }
2. 缓存请求结果
避免重复请求相同ISBN的数据,可使用useRef实现内存缓存:
// 在ContextProvider中添加缓存ref const bookCache = useRef({}); async function getBooksWithIsbn(isbnList) { try { const bookPromises = isbnList.map(isbn => { // 优先使用缓存数据 if (bookCache.current[isbn]) { return Promise.resolve(bookCache.current[isbn]); } const url = `https://www.googleapis.com/books/v1/volumes?q=isbn:${isbn}&printType=books`; return fetch(url) .then(res => res.json()) .then(data => { const book = data.items?.[0]; // 将结果存入缓存 bookCache.current[isbn] = book; return book; }); }); const books = await Promise.all(bookPromises); const validBooks = books.filter(book => book); setBestSeller(validBooks); } catch (error) { console.error('加载畅销书数据失败:', error); } }
3. 提前预加载数据
将getBestSellerIsbns的调用时机提前到应用初始化时(如App组件挂载),让用户进入首页前就开始加载数据:
// 在App组件中 useEffect(() => { getBestSellerIsbns(); }, [getBestSellerIsbns]);
4. 增强错误处理
针对单个请求失败的情况,返回null并后续过滤,避免影响整个列表的展示:
const bookPromises = isbnList.map(isbn => { const url = `https://www.googleapis.com/books/v1/volumes?q=isbn:${isbn}&printType=books`; return fetch(url) .then(res => { if (!res.ok) throw new Error(`请求失败: ${res.status}`); return res.json(); }) .then(data => data.items?.[0]) .catch(error => { console.error(`加载ISBN ${isbn}失败:`, error); return null; // 返回null,后续过滤掉 }); });
内容的提问来源于stack exchange,提问作者devham23
相关产品推荐
相关产品推荐

