You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.21 12:15:29