如何避免React子组件Scene切换分组时重复获取场景详情数据
如何避免React子组件Scene切换分组时重复获取场景详情数据
我明白你遇到的问题了——切换分组时,原来的Scene组件被卸载,新的又重新挂载,导致每个Scene里的useRef缓存直接丢失,不得不重复请求接口。其实核心问题就是缓存的作用域不对,组件级的缓存扛不住组件销毁重建,咱们把缓存往上提就能解决,下面给你具体的方案:
问题根源拆解
当你切换分组时,SceneList会根据新的groupNum过滤出全新的scenes数组,React会把旧分组对应的Scene组件全部卸载,再渲染新分组的Scene组件。每个Scene的useRef是和组件实例绑定的,组件没了,缓存自然就没了,所以每次切换都会重新发请求。
解决方案:把缓存提升到父组件(SceneList)
咱们把场景详情的缓存放到SceneList里,这样缓存不会随着子组件的销毁而消失,所有Scene组件共享这份缓存。具体步骤如下:
1. 在SceneList中添加全局缓存状态
在SceneList组件里新增一个状态,用来存储已经获取过的场景详情,key用分组ID-场景ID来确保唯一性:
const [sceneDetailsCache, setSceneDetailsCache] = useState<Record<string, string[]>>({});
2. 修改Scene组件的Props,接收缓存和更新方法
让Scene组件从父组件拿到缓存,不需要自己维护useRef了:
interface SceneProps { scene: HueScene; sceneDetailsCache: Record<string, string[]>; setSceneDetailsCache: React.Dispatch<React.SetStateAction<Record<string, string[]>>>; }
3. 改造Scene组件的请求逻辑
先检查父组件的缓存里有没有当前场景的详情,有就直接用,没有再发请求,请求成功后把结果存到父组件的缓存里:
const Scene = memo(function Scene({ scene, sceneDetailsCache, setSceneDetailsCache }: SceneProps) { const { num, name, group } = scene; const cacheKey = `${group}-${num}`; // 缓存存在就直接跳过加载状态 const [isLoading, setIsLoading] = useState(!sceneDetailsCache[cacheKey]); const [error, setError] = useState<string | null>(null); const ENDPOINT_POST = `${process.env.NEXT_PUBLIC_HUE_API_ADDRESS}/api/${process.env.NEXT_PUBLIC_HUE_API_USERNAME}/groups/${group}/action`; const ENDPOINT_GET = `${process.env.NEXT_PUBLIC_HUE_API_ADDRESS}/api/${process.env.NEXT_PUBLIC_HUE_API_USERNAME}/scenes/${num}`; const fetchSceneData = async () => { try { const response = await fetch(ENDPOINT_GET); const data = await response.json(); if (data[0]?.error) { setError(data[0]?.error?.description || "Error from Hue V1 API"); } else { const colors = normalizeSceneData(data); // 更新父组件的缓存 setSceneDetailsCache(prev => ({ ...prev, [cacheKey]: colors })); setError(null); } } catch (err: any) { setError(err.message || "Error loading scene data from Hue Bridge."); } finally { setIsLoading(false); } }; const normalizeSceneData = (data: Record<string, any>) => { const colors: string[] = []; for (const [key, value] of Object.entries(data?.lightstates)) { colors[parseInt(key)] = calculateSceneLampColor(value); } return colors; }; useEffect(() => { // 缓存不存在才发起请求 if (!sceneDetailsCache[cacheKey]) { fetchSceneData(); } }, [cacheKey, sceneDetailsCache]); // 省略recallScene函数(和原来一致) return ( <> {error && <p className="scene-error">{error}</p>} {!isLoading && !error && ( <button className="scene" onClick={recallScene}> <div className="scene-name">{name}</div> <div className="scene-colors"> {sceneDetailsCache[cacheKey]?.map((sceneColor, light) => ( <div key={`${group}-${light}-${num}-${sceneColor}`} className="scene-color" style={{ background: sceneColor }} ></div> ))} </div> </button> )} </> ); }, (prevProps, nextProps) => { // 优化memo的比较逻辑,只看关键属性 const prevCacheKey = `${prevProps.scene.group}-${prevProps.scene.num}`; const nextCacheKey = `${nextProps.scene.group}-${nextProps.scene.num}`; return prevCacheKey === nextCacheKey && JSON.stringify(prevProps.sceneDetailsCache[prevCacheKey]) === JSON.stringify(nextProps.sceneDetailsCache[nextCacheKey]); });
4. 在SceneList中渲染Scene时传递缓存
修改SceneList里的渲染逻辑,把缓存和更新方法传给每个Scene:
{scenes && ( <div> <h2>Scenes</h2> {scenes.map((scene) => ( <Scene key={`${scene.group}-${scene.num}`} scene={scene} sceneDetailsCache={sceneDetailsCache} setSceneDetailsCache={setSceneDetailsCache} /> ))} </div> )}
额外优化建议
- 如果你的应用已经在用Redux、Zustand这类全局状态管理工具,可以把缓存放到全局状态里,这样其他组件也能复用这些数据。
- 可以给Scene组件的key加上group,确保即使同一个场景(理论上你的场景是和分组绑定的)跨分组存在,也能正确区分。
- 可以把加载状态统一放到SceneList里,避免每个Scene都单独显示loading,提升视觉体验。
备注:内容来源于stack exchange,提问作者Fer
相关产品推荐
相关产品推荐

