React懒加载组件丢失其状态(被卸载)

8
我有以下组件,可以在需要时(路由变更时)加载我的组件。
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都会卸载和重新挂载惰性加载组件(而不是重新渲染)。这导致所有内部状态重置,这当然是不希望的。请问有人能推荐任何解决方案吗?
此处链接展示了这个问题。

2个回答

11
每当父组件被渲染时,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>
  );
};

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

由于 useMemo() 的缓存并不是保证的(React 可能会定期释放内存),因此您可以使用 Map 编写一个简单的惰性缓存:

const componentsMap = new Map();

const useCachedLazy = (component) => {
  useEffect(
    () => () => {
      componentsMap.delete(component);
    },
    [component]
  );

  if (!componentsMap.has(component)) {
    componentsMap.set(
      component,
      React.lazy(() => import(component))
    );
  }

  return componentsMap.get(component);
};

const DynamicLoader = ({ component, parentUpdate }) => {
  const LazyComponent = useCachedLazy(component);

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent parentUpdate={parentUpdate} />
    </Suspense>
  );
};

沙盒


谢谢,看起来不错!关于useMemo的阅读,Facebook似乎在暗示useMemo不能保证值的保留。您认为使用Ref怎么样?或者甚至使用全局变量来保留已加载的组件呢? - Asha
1
这是一个很好的观点。你只需要一些缓存机制。我已经使用 Map 添加了一个,但你也可以使用 lodash/memoize 或任何其他缓存机制。 - Ori Drori
1
非常感谢!这解决了我遇到的一个非常神秘的错误(在添加延迟加载后,组件被卸载并重置其状态)。 - halflings

1

我在渲染动态字段时也遇到了类似的问题。我通过使用@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并返回缓存键。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接