文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

React中实现keepalive组件缓存效果的方法详解

2023-01-14 12:00

关注

背景

由于react官方并没有提供缓存组件相关的api(类似vue中的keepalive),在某些场景,会使得页面交互性变的很差,比如在有搜索条件的表格页面,点击某一条数据跳转到详情页面,再返回表格页面,会重新请求数据,搜索条件也将清空,用户得重新输入搜索条件,再次请求数据,大大降低办公效率,如图:

目标:封装keepalive缓存组件,实现组件的缓存,并暴露相关方法,可以手动清除缓存。

版本:React 17,react-router-dom 5

结构

代码

cache-types.js

// 缓存状态
export const CREATE = 'CREATE';        // 创建
export const CREATED = 'CREATED';      // 创建成功
export const ACTIVE = 'ACTIVE';        // 激活
export const DESTROY = 'DESTROY';      // 销毁

CacheContext.js

import React from 'react';
const CacheContext = React.createContext();
export default CacheContext;

KeepAliveProvider.js

import React, { useReducer, useCallback } from "react";
import CacheContext from "./CacheContext";
import cacheReducer from "./cacheReducer";
import * as cacheTypes from "./cache-types";
function KeepAliveProvider(props) {
  let [cacheStates, dispatch] = useReducer(cacheReducer, {});
  const mount = useCallback(
    ({ cacheId, element }) => {
      // 挂载元素方法,提供子组件调用挂载元素
      if (cacheStates[cacheId]) {
        let cacheState = cacheStates[cacheId];
        if (cacheState.status === cacheTypes.DESTROY) {
          let doms = cacheState.doms;
          doms.forEach((dom) => dom.parentNode.removeChild(dom));
          dispatch({ type: cacheTypes.CREATE, payload: { cacheId, element } }); // 创建缓存
        }
      } else {
        dispatch({ type: cacheTypes.CREATE, payload: { cacheId, element } }); // 创建缓存
      }
    },
    [cacheStates]
  );
  let handleScroll = useCallback(
    // 缓存滚动条
    (cacheId, { target }) => {
      if (cacheStates[cacheId]) {
        let scrolls = cacheStates[cacheId].scrolls;
        scrolls[target] = target.scrollTop;
      }
    },
    [cacheStates]
  );
  return (
    <CacheContext.Provider
      value={{ mount, cacheStates, dispatch, handleScroll }}
    >
      {props.children}
      {}
      {Object.values(cacheStates)
        .filter((cacheState) => cacheState.status !== cacheTypes.DESTROY)
        .map(({ cacheId, element }) => (
          <div
            id={`cache_${cacheId}`}
            key={cacheId}
            // 原生div中声明ref,当div渲染到页面,会执行ref中的回调函数,这里在id为cache_${cacheId}的div渲染完成后,会继续渲染子元素
            ref={(dom) => {
              let cacheState = cacheStates[cacheId];
              if (
                dom &&
                (!cacheState.doms || cacheState.status === cacheTypes.DESTROY)
              ) {
                let doms = Array.from(dom.childNodes);
                dispatch({
                  type: cacheTypes.CREATED,
                  payload: { cacheId, doms },
                });
              }
            }}
          >
            {element}
          </div>
        ))}
    </CacheContext.Provider>
  );
}
const useCacheContext = () => {
  const context = React.useContext(CacheContext);
  if (!context) {
    throw new Error("useCacheContext必须在Provider中使用");
  }
  return context;
};
export { KeepAliveProvider, useCacheContext };

withKeepAlive.js

import React, { useContext, useRef, useEffect } from "react";
import CacheContext from "./CacheContext";
import * as cacheTypes from "./cache-types";
function withKeepAlive(
  OldComponent,
  { cacheId = window.location.pathname, scroll = false }
) {
  return function (props) {
    const { mount, cacheStates, dispatch, handleScroll } =
      useContext(CacheContext);
    const ref = useRef(null);
    useEffect(() => {
      if (scroll) {
        // scroll = true, 监听缓存组件的滚动事件,调用handleScroll()缓存滚动条
        ref.current.addEventListener(
          "scroll",
          handleScroll.bind(null, cacheId),
          true
        );
      }
    }, [handleScroll]);
    useEffect(() => {
      let cacheState = cacheStates[cacheId];
      if (
        cacheState &&
        cacheState.doms &&
        cacheState.status !== cacheTypes.DESTROY
      ) {
        // 如果真实dom已经存在,且状态不是DESTROY,则用当前的真实dom
        let doms = cacheState.doms;
        doms.forEach((dom) => ref.current.appendChild(dom));
        if (scroll) {
          // 如果scroll = true, 则将缓存中的scrollTop拿出来赋值给当前dom
          doms.forEach((dom) => {
            if (cacheState.scrolls[dom])
              dom.scrollTop = cacheState.scrolls[dom];
          });
        }
      } else {
        // 如果还没产生真实dom,派发生成
        mount({
          cacheId,
          element: <OldComponent {...props} dispatch={dispatch} />,
        });
      }
    }, [cacheStates, dispatch, mount, props]);
    return <div id={`keepalive_${cacheId}`} ref={ref} />;
  };
}
export default withKeepAlive;

index.js

export { KeepAliveProvider } from "./KeepAliveProvider";
export {default as withKeepAlive} from './withKeepAlive';

使用

1.用<KeepAliveProvider></KeepAliveProvider>将目标缓存组件或者父级包裹;

2.将需要缓存的组件,传入withKeepAlive方法中,该方法返回一个缓存组件;

3.使用该组件;

App.js

import React from "react";
import {
  BrowserRouter,
  Link,
  Route,
  Switch,
} from "react-router-dom";
import Home from "./Home.js";
import List from "./List.js";
import Detail from "./Detail.js";
import { KeepAliveProvider, withKeepAlive } from "./keepalive-cpn";

const KeepAliveList = withKeepAlive(List, { cacheId: "list", scroll: true });

function App() {
  return (
    <KeepAliveProvider>
      <BrowserRouter>
        <ul>
          <li>
            <Link to="/">首页</Link>
          </li>
          <li>
            <Link to="/list">列表页</Link>
          </li>
          <li>
            <Link to="/detail">详情页A</Link>
          </li>
        </ul>
        <Switch>
          <Route path="/" component={Home} exact></Route>
          <Route path="/list" component={KeepAliveList}></Route>
          <Route path="/detail" component={Detail}></Route>
        </Switch>
      </BrowserRouter>
    </KeepAliveProvider>
  );
}

export default App;

效果:

假设有个需求,从首页到列表页,需要清空搜索条件,重新请求数据,即回到首页,需要清除列表页的缓存。

上面的KeepAliveProvider.js中,暴露了一个useCacheContext()的hook,该hook返回了缓存组件相关数据和方法,这里可以用于清除缓存:

Home.js

import React, { useEffect } from "react";
import { DESTROY } from "./keepalive-cpn/cache-types";
import { useCacheContext } from "./keepalive-cpn/KeepAliveProvider";

const Home = () => {
  const { cacheStates, dispatch } = useCacheContext();

  const clearCache = () => {
    if (cacheStates && dispatch) {
      for (let key in cacheStates) {
        if (key === "list") {
          dispatch({ type: DESTROY, payload: { cacheId: key } });
        }
      }
    }
  };
  useEffect(() => {
    clearCache();
    // eslint-disable-next-line
  }, []);
  return (
    <div>
      <div>首页</div>
    </div>
  );
};

export default Home;

效果

至此,react简易版的keepalive组件已经完成啦~

到此这篇关于React中实现keepalive组件缓存效果的方法详解的文章就介绍到这了,更多相关React keepalive组件缓存效果内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯