如何优化基于LocalStorage的Apollo查询:避免空变量发起请求
优化最近浏览商品组件的查询逻辑,避免无效请求
我通过LocalStorage存储商品ID实现最近浏览商品组件,当前用useState定义状态存储ID,组件挂载时通过useEffect从LocalStorage获取数据更新状态,再将其作为Apollo Client的useQuery变量传入WP-GraphQL查询。但发现ID为空时会发起无效查询,希望找到更优的实现方式。
当前组件代码
import { useQuery } from "@apollo/client"; import { getRecentlyViewedProductArr } from "@gb-utils/product/product" import { RECENTLY_VIEWED } from "@gb-utils/queries/product"; import { useEffect, useState } from "react"; export default function RecentlyViewed() { const [recentIds, setRecentIds] = useState([]) const { loading, error, data } = useQuery(RECENTLY_VIEWED, { variables: { ids: recentIds } }) useEffect(() => { setRecentIds(getRecentlyViewedProductArr()) }, []); if (loading) { return 'Loading'; } if (error) { return error.message } return ( <div>{JSON.stringify(data)}</div> ) }
工具函数代码(helper.js)
export const addRecentlyViewedProduct = (productId) => { let recent = getRecentlyViewedProduct() if (isEmpty(recent)) { // If recently viewed is empty then we just save the product id saveRecentlyViewedProduct(productId) return getRecentlyViewedProduct() } // if not empty then we check if product id exist and remove if (recent.includes(productId)) { recent = recent.replace(productId, '') } // Since we remove the product then we Add the product id again recent = "".concat(productId, '|', recent) recent = recent.replace('||', '|'); recent = recent.startsWith('|') ? recent.substring(1) : recent recent = recent.endsWith('|') ? recent.substring(0, recent.length - 1) : recent; const limit = 5; let recentProductIds = recent.split('|') if (recentProductIds.length > limit) { recentProductIds = recentProductIds.slice(0, limit) } saveRecentlyViewedProduct(recentProductIds.join('|')) return getRecentlyViewedProduct() } export const saveRecentlyViewedProduct = (value) => { return localStorage.setItem('woo-next-recent-product', value); } export const getRecentlyViewedProduct = () => { const recentProductsStr = localStorage.getItem('woo-next-recent-product') return recentProductsStr } export const getRecentlyViewedProductArr = () => { const recentProductsStr = getRecentlyViewedProduct() if (isEmpty(recentProductsStr)) { return '' } let recentProductIds = recentProductsStr.split('|') return Array.from(recentProductIds.slice(1), Number); // get the 2nd to the last element }
优化方案
1. 初始化状态直接读取LocalStorage
组件挂载时,原代码useState初始值是空数组,会导致useQuery先发起一次带空数组的无效请求,再等待useEffect更新状态。直接在useState初始化时调用getRecentlyViewedProductArr(),让组件一开始就持有正确的ID数据,避免首次无效请求。
2. 控制查询执行时机,跳过无效请求
在useQuery配置中添加skip选项,当recentIds为空数组时跳过查询,彻底避免向服务器发送无效请求。
3. 修复工具函数的类型不一致问题
原getRecentlyViewedProductArr函数在无数据时返回空字符串,和useState的数组类型不匹配,容易引发类型错误。修改为返回空数组,保持类型统一。
4. 监听LocalStorage变化(可选)
如果其他页面或标签页会修改最近浏览记录,可以监听storage事件,同步更新组件状态,提升跨标签页的用户体验。
修改后的代码
组件代码
import { useQuery } from "@apollo/client"; import { getRecentlyViewedProductArr } from "@gb-utils/product/product" import { RECENTLY_VIEWED } from "@gb-utils/queries/product"; import { useEffect, useState } from "react"; export default function RecentlyViewed() { // 初始化直接读取LocalStorage数据,避免初始空数组 const [recentIds, setRecentIds] = useState(getRecentlyViewedProductArr()) const { loading, error, data } = useQuery(RECENTLY_VIEWED, { variables: { ids: recentIds }, // 当ID数组为空时跳过查询 skip: recentIds.length === 0 }) // 监听LocalStorage变化,同步更新状态(可选) useEffect(() => { const handleStorageUpdate = () => { setRecentIds(getRecentlyViewedProductArr()) }; window.addEventListener('storage', handleStorageUpdate); return () => window.removeEventListener('storage', handleStorageUpdate); }, []); // 无最近浏览记录时展示提示 if (recentIds.length === 0) { return <div>暂无最近浏览商品</div>; } if (loading) { return 'Loading'; } if (error) { return <div>加载失败:{error.message}</div> } return ( <div>{JSON.stringify(data)}</div> ) }
工具函数修改(getRecentlyViewedProductArr)
export const getRecentlyViewedProductArr = () => { const recentProductsStr = getRecentlyViewedProduct() // 处理空值,返回空数组保持类型统一 if (!recentProductsStr || recentProductsStr.trim() === '') { return [] } let recentProductIds = recentProductsStr.split('|') // 保留原业务逻辑:取第2个到最后一个元素并转数字 return recentProductIds.slice(1).map(Number); }
说明
- 初始化直接读取LocalStorage:减少一次不必要的查询,组件渲染时直接使用正确的初始状态。
- skip选项:彻底避免空ID数组的无效请求,减轻服务器压力。
- 类型统一:避免因返回值类型不一致导致的渲染错误或逻辑问题。
- storage事件监听:确保跨标签页操作时,组件能实时同步最近浏览记录。
内容的提问来源于stack exchange,提问作者jameshwart lopez
相关产品推荐
相关产品推荐

