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

如何缓存React懒加载组件并避免不必要的卸载与重新挂载?

问题修复:懒加载组件状态变化时重复挂载而非重渲染

核心问题原因

每次App组件重渲染(比如showHiddenTriggerKeyPress状态变化)时,routes.map内部会重复调用loadable函数,每次调用都会生成全新的MemoizedLazyComponent实例。React路由检测到component属性引用变化后,会卸载旧组件并重新挂载新组件,而非仅触发重渲染。

另外你的useKeyPress钩子存在逻辑缺陷:用let prevKey保存上一次按键会在组件重渲染时被重置,且未判断shiftKey,可能导致快捷键触发异常。

修复方案

1. 全局缓存loadable生成的组件实例

修改loadable函数,新增全局缓存存储已生成的MemoizedLazyComponent,确保相同参数下返回同一个组件实例:

const componentsMap = new Map();
// 新增全局缓存,存储已生成的Memoized组件
const memoizedComponentsMap = new Map();

const useCachedLazy = (importFunc, namedExport) => {
  if (!componentsMap.has(importFunc)) {
    const LazyComponent = lazy(() =>
      importFunc().then(module => ({
        default: namedExport ? module[namedExport] : module.default,
      }))
    );
    componentsMap.set(importFunc, LazyComponent);
  }
  return componentsMap.get(importFunc);
};

const loadable = (importFunc, { namedExport = null } = {}) => {
  // 用importFunc和namedExport组合生成唯一缓存键
  const cacheKey = `${importFunc}-${namedExport}`;
  if (memoizedComponentsMap.has(cacheKey)) {
    return memoizedComponentsMap.get(cacheKey);
  }

  const MemoizedLazyComponent = React.memo(props => {
    const LazyComponent = useCachedLazy(importFunc, namedExport);
    return <LazyComponent {...props} />;
  });

  memoizedComponentsMap.set(cacheKey, MemoizedLazyComponent);
  return MemoizedLazyComponent;
};

export default loadable;

2. 在App组件中缓存路由懒加载组件

避免在routes.map中每次渲染都调用loadable,用useMemo缓存生成的懒加载组件列表:

// App.js内部
const lazyRouteComponents = useMemo(() => {
  return routes.map(route => ({
    ...route,
    lazyComponent: loadable(
      () => Promise.resolve({ default: route.component }),
      { namedExport: route.namedExport }
    )
  }));
}, [routes]); // 如果routes是静态常量,此依赖可确保仅初始化一次

// 渲染路由时使用缓存后的lazyComponent
<Suspense fallback={<div><Spin /></div>}>
  <Switch>
    {lazyRouteComponents.map(route => {
      return route.auth ? (
        <PrivateRoute
          {...route}
          key={route.key}
          collapsed={collapsed}
          toggleCollapsed={toggleCollapsed}
          showHiddenTriggerKeyPress={showHiddenTriggerKeyPress}
          component={route.lazyComponent} 
          locale={locale}
        />
      ) : (
        <Route {...route} key={route.key} component={route.lazyComponent} />
      );
    })}
  </Switch>
</Suspense>

3. 修复useKeyPress钩子逻辑

用useRef持久化上一次按键状态,并增加shiftKey判断,确保仅在Shift+X时触发状态变化:

function useKeyPress(targetKey) {
  const [keyPressed, setKeyPressed] = useState(false);
  const prevKeyRef = useRef(''); // 用useRef持久化状态

  function downHandler({ key, shiftKey }) {
    // 仅在Shift+X且未重复触发时更新状态
    if (key === targetKey && shiftKey && prevKeyRef.current !== targetKey) {
      setKeyPressed(prev => !prev);
      prevKeyRef.current = targetKey;
    }
    // 按键抬起时重置prevKey
    const upHandler = () => {
      prevKeyRef.current = '';
    };
    window.addEventListener('keyup', upHandler);
    return () => window.removeEventListener('keyup', upHandler);
  }

  useEffect(() => {
    window.addEventListener('keydown', downHandler);
    return () => {
      window.removeEventListener('keydown', downHandler);
    };
  }, [targetKey]); // 依赖仅为targetKey,避免重复绑定事件

  return keyPressed;
}

// 使用时
const showHiddenTriggerKeyPress = useKeyPress('X');

验证效果

修改完成后,按下Shift+X触发showHiddenTriggerKeyPress状态变化时,路由组件会仅执行重渲染,而非卸载后重新挂载,同时快捷键逻辑也会更稳定。

内容的提问来源于stack exchange,提问作者PoorProgrammer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 18:34:57