要想实现页面缓存,在React应用中通常有以下两种解决方案。
第一种方案:状态存在内存中。比如使用Redux、Mobx或自定义内存变量,在页面离开前,将页面内用户产生的数据存储在内存中,将页面“快照”下来,并销毁页面的DOM节点。这样虽然页面的DOM节点被销毁,但是在重新挂载页面的时候,可从内存中将各节点通过“快照”进行恢复。这种方案的优点主要是路由切换DOM节点销毁,DOM节点数量可控,而其缺点也同样明显,将引入额外开发量,无论是在业务上还是在框架上,都将引入额外的开发成本。

第二种方案:不销毁DOM节点,对其进行缓存。这种方案的优点主要是可以降低开发成本。虽然页面的DOM节点会保留,但是由于减少了页面离开时的“快照”过程,业务开发的流程侵入性较小,并不需要在页面销毁时将页面状态保存到内存中。通过缓存DOM节点,一来无须框架层面的额外开发,二来也不会侵入业务代码,仅需要对路由的行为做一些改变。但这类方案也同样存在缺点,由于一般通过CSS方式缓存DOM隐藏页面,因此页面的DOM节点数量会增多。但若控制得当,DOM的节点数量也在可控范围内。

  1. import * as React from "react";
  2. import { Route, RouteChildrenProps, RouteProps } from "react-router";
  3. import { omit } from "lodash";
  4. import MemoChildrenWithRouteMatch, {
  5. MemoChildrenWithRouteMatchExact
  6. } from "./Cache";
  7. import Remount from "./Remount";
  8. interface Props {
  9. forceHide?: boolean;
  10. shouldReMount?: boolean;
  11. shouldDestroyDomWhenNotMatch?: boolean;
  12. shouldMatchExact?: boolean;
  13. }
  14. export default function CacheRoute(props: RouteProps & Props) {
  15. const routeHadRenderRef = React.useRef(false);
  16. return (
  17. <Route
  18. {...omit(props, "component", "render", "children")}
  19. children={(routeProps: RouteChildrenProps) => {
  20. const Component = props.component;
  21. const routeMatch = routeProps.match;
  22. let match = !!routeMatch;
  23. if (props.shouldMatchExact) {
  24. match = routeMatch && routeMatch.isExact;
  25. }
  26. if (props.shouldDestroyDomWhenNotMatch) {
  27. if (!match) routeHadRenderRef.current = false;
  28. // 按正常逻辑
  29. if (props.render) {
  30. return match && props.render(routeProps);
  31. }
  32. return (
  33. match && Component && React.createElement(Component, routeProps)
  34. );
  35. } else {
  36. const matchStyle = {
  37. // 隐藏
  38. display: match && !props.forceHide ? "block" : "none"
  39. };
  40. if (match && !routeHadRenderRef.current) {
  41. routeHadRenderRef.current = true;
  42. }
  43. let shouldRender = true;
  44. if (!match && !routeHadRenderRef.current) {
  45. shouldRender = false;
  46. }
  47. const MemoCache = props.shouldMatchExact
  48. ? MemoChildrenWithRouteMatchExact
  49. : MemoChildrenWithRouteMatch;
  50. // css隐藏保留dom
  51. let component;
  52. if (props.render) {
  53. component = props.render(routeProps);
  54. } else {
  55. component = <Component {...routeProps} />;
  56. }
  57. return (
  58. shouldRender && (
  59. <div style={matchStyle}>
  60. {/*提供remount能力*/}
  61. <Remount shouldRemountComponent={props.shouldReMount}>
  62. {/*提供渲染优化*/}
  63. <MemoCache {...routeProps}>{component}</MemoCache>
  64. </Remount>
  65. </div>
  66. )
  67. );
  68. }
  69. }}
  70. />
  71. );
  72. }

源码