【问题标题】:React Lazy loaded component loosing it's state (gets unmounted)React Lazy 加载的组件失去它的状态(被卸载)
【发布时间】:2019-11-15 04:17:03
【问题描述】:

我有以下组件,可以在需要时(在路线更改时)加载我的组件。

function DynamicLoader(props) {
  const LazyComponent = React.lazy(() => import(`${props.component}`));
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

我的路由(使用 React-Router)如下所示:

            <Switch>
            {routes.map((prop, key) => {
              return (
                <Route
                  exact
                  path={prop.path}
                  render={() => (
                    <DynamicLoader component={prop.component} />
                  )}
                  key={key}
                />
              );
            })}
          </Switch>

就为每个路由安装组件而言,这工作正常,但是看起来随着父组件的每次更改,React 正在卸载并重新安装延迟加载的组件(而不是重新渲染)。这会导致所有内部状态重置,这当然是不希望的。任何人都可以推荐任何解决方案吗? 这是显示此问题的codesandbox

【问题讨论】:

    标签: reactjs react-router lazy-loading react-hooks


    【解决方案1】:

    每当渲染父级时,DynamicLoader 都会重新创建 LazyComponent。 React 看到一个新组件(不是同一个对象),卸载前一个组件,并安装到新组件。

    要解决此问题,请在DynamicLoader 中使用React.useMemo() 来记忆当前的LazyComponent,并且仅在props.component 实际更改时重新创建它:

    const DynamicLoader = ({ component, parentUpdate }) => {
      const LazyComponent = useMemo(() => React.lazy(() => import(component)), [
        component
      ]);
    
      return (
        <Suspense fallback={<div>Loading...</div>}>
          <LazyComponent parentUpdate={parentUpdate} />
        </Suspense>
      );
    };
    

    Sandbox - 为了展示记忆中的LazyComponent,我将外部update 传递给HomeA 组件。

    由于不能保证useMemo() 缓存(react 可能会不时释放内存),您可以使用Map 编写一个简单的惰性缓存:

    const componentsMap = new Map();
    
    const useCachedLazy = (component) => {
      useEffect(
        () => () => {
          componentsMap.delete(component);
        },
        [component]
      );
    
      if (componentsMap.has(component)) return componentsMap.get(component);
    
      const Component = React.lazy(() => import(component));
    
      componentsMap.set(component, Component);
    
      return Component;
    };
    
    const DynamicLoader = ({ component, parentUpdate }) => {
      const LazyComponent = useCachedLazy(component);
    
      return (
        <Suspense fallback={<div>Loading...</div>}>
          <LazyComponent parentUpdate={parentUpdate} />
        </Suspense>
      );
    };
    

    Sandbox

    【讨论】:

    • 谢谢,看起来不错!阅读有关 useMemo 的信息,facebook 似乎暗示不能保证 useMemo 保持这些值。您如何看待使用 Ref 代替?甚至是一个全局变量来保存已经加载的组件?
    • 这很好。您所需要的只是某种缓存机制。我使用 Map 添加了一个,但您也可以使用 lodash/memoize 或任何其他缓存机制。
    • 非常感谢!这解决了我遇到的一个非常神秘的错误(在我添加延迟加载后,组件被卸载,并且它的状态重置)
    【解决方案2】:

    我在渲染动态字段时也遇到了类似的问题。我已经使用@loadable/component 修复了它,它接受缓存参数,如果组件已经导入,则可以避免重新渲染。

    这是我一直在使用的一个基本示例;

    import React from "react";
    import loadable from "@loadable/component";
    
    interface Props {
      className?: string;
      field: "SelectBox" | "Aligner" | "Slider" | "Segment";
      fieldProps?: {
        [key: string]: any;
      };
      label: string;
      onChange: (value: string | number) => void;
      value: string | number | null | undefined;
    }
    
    const FieldComponent = loadable((props: Props) => import(`./${props.field}`), {
      cacheKey: (props: Props) => props.field,
    });
    
    export const DynamicField = ({
      className,
      field,
      fieldProps,
      label,
      onChange = () => null,
      value,
    }: Props) => {
      return (
        <div>
          <label>{label}</label>
          <FieldComponent
            field={field}
            {...fieldProps}
            onChange={onChange}
            value={value}
          />
        </div>
      );
    };
    

    如果您使用@loadable/babel-plugin,则支持开箱即用的动态属性。否则你必须添加 cacheKey 函数:它接受 props 并返回一个缓存键。

    【讨论】:

      猜你喜欢
      • 2022-12-04
      • 2018-12-04
      • 1970-01-01
      • 2021-10-08
      • 1970-01-01
      • 2021-12-10
      • 2021-07-23
      • 2020-06-12
      • 1970-01-01
      相关资源
      最近更新 更多