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>
);
};
我在渲染动态字段时也遇到了类似的问题。我已经通过使用 @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 一个缓存键。
我有以下组件在需要时(在路由更改时)加载我的组件。
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>
);
};
我在渲染动态字段时也遇到了类似的问题。我已经通过使用 @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 一个缓存键。