React Lazy 加载的组件失去它的状态(被卸载)

React Lazy loaded component loosing it's state (gets unmounted)

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

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

无论何时渲染父级,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() 无法保证缓存(反应可能会不时释放内存),您可以使用 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

我在渲染动态字段时也遇到了类似的问题。我已经通过使用 @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 函数:它需要道具和 returns 一个缓存键。