我想做过vue的同学都知道keepalive是什么吧,就是把那些加载过的组件存到缓存里面,即使页面切换了,组件已经销毁,但是在缓存里面的组件依旧在,等下次用户用到这个组件的时候,直接会读取缓存里面处理好的dom就好了,不用再进行编译解析。
那么在react里也会有这样的场景,比如我在手机上要填写一个信息,但是我不知道该写什么,就跳出去查找了一下,回来发现表单上填写的东西全部没有了,你会不会头顶飘过一万个草泥马?
还有翻看大型购物网站的时候,你进入一个商品的详情,看了一会,又退出来,发现居然依旧停留在刚才进入的地方,难道它没有销毁?不对呀,它应该是销毁了又重新加载的页面呀?它是如何实现的?
其实在生活中,我们有很多场景都会用到缓存组件,它最大的优点就是提升用户的体验感。
那就动动小手自己实现一下看看:
1.初始化项目
npx create-vite
创建一个vite项目,之后就是创造一个简单的页面出来了,一般情况下,我们都是缓存路由组件的。在app。jsx里面写一个简单的路由和页面。
2.测试组件
写三个组件,很简单如下:
具体代码如下:
import { useState } from 'react';
import { Link, useLocation, RouterProvider, createBrowserRouter, useOutlet } from 'react-router-dom';
const Layout = () => {
const { pathname } = useLocation();
const element = useOutlet();
console.log(pathname, 999);
//用outLet指定组件的children,类似useOutlet
return <div>
<div>现在的路由是:{pathname}</div>
<div>我是首页</div>
{element}
</div>;
};
const DemoA = () => {
const [num, setNum] = useState(0);
const handleClick = () => {
setNum(() => num + 1);
};
return <div style={{ width: "500px", height: "200px", marginLeft: "100px" }}>
<p>我是demoA页面,你不要看错了</p>
<p>我现在是:{num}</p>
<p>
<button onClick={handleClick}>点你</button><br />
<Link to="/demob">去DemoB页面</Link><br />
<Link to="/democ">去DemoC页面</Link><br />
</p>
</div>;
};
const DemoB = () => {
const [num, setNum] = useState(0);
const handleClick = () => {
setNum(() => num + 2);
};
return <div style={{ width: "500px", height: "200px", marginLeft: "100px" }}>
<p>我是demoB页面, 我的数据需要被记住</p>
<p>我现在是:{num}</p>
<p>
<button onClick={handleClick}>点你</button>
<Link to="/">我要去首页</Link>
</p>
</div>;
};
const DemoC = () => {
return <div style={{ width: "500px", height: "200px", marginLeft: "100px" }}>
<p>我是demoC页面,我需要干点啥呢</p>
<p>我是C页面,我想干点啥呢?</p>
<Link to='/'>去首面</Link>
</div>;
};
const routes = [{
path: '/',
element: <Layout></Layout>,
children: [{
path: '/',
element: <DemoA></DemoA>
}, {
path: '/demob',
element: <DemoB></DemoB>
}, {
path: '/democ',
element: <DemoC></DemoC>
}]
}];
export const router = createBrowserRouter(routes);
function App() {
return (
<RouterProvider router={router} />
);
}
export default App;
首先我创建routes数组,创建router对象
再次我用RouterProvider,他是个context对象,负责把路由派发下去
这样react-router会根据具体的路由,从配置文件里面拿到具体的组件,最主要的组件就是这个Layout,他是个根组件,一般会将网页的 Nav、公告,用户等放在里面,供整个网站共用。
在Layout上使用 useOutlet 拿到了路由对应的组件,然后在首页 Layout 上显示出来,然后,
这个组件很简单不再赘述,我们看看KeepAlive组件
3.KeepAlive组件
KeepAlive组件组件的核心思想就是:我要在App上创建一个context,然后用useLocation拿到当前路由,然后用useOutlet拿到路由对应的组件。然后把他们一对一的存到这个context里面。但是你想一下总不能把所有的组件都存起来吧,所以存哪些组件是不是需要一个参数去确定,所以这个组件的第一个入参就是要缓存的路由地址,它应该是个数组,一个应用不可能只缓存一个页面的。所以基础代码如下:
使用:
数据已经派发下去了,怎么渲染呢?就要看Layout了,他才是专门负责渲染组件的根组件,在这里它会用 useOutlet 获取到子组件,然后去渲染。
那我们是不是也应该在这里去处理当前的组件?继续在KeepAlive.jsx里面写一个hooks出来解决这个事情。
其实它就做了两件事:用uselocation和useOutlet拿到当前的路由和组件,看当前路由是不是在keepPaths里面,如果在,我们就缓存,如果不在,就不管了。2.遍历keepElements数组,渲染所有在缓存里面的组件。其实在缓存里面的组件它一直都在页面上,并没有被销毁,它只是被隐藏了而已!
使用:
测试:
多次切换路由,页面数据依旧是上一次的。
/*eslint-disable */
import React, { createContext, useContext } from 'react';
import { useOutlet, useLocation, matchPath } from 'react-router-dom';
const keepElements = {};
//创建一个全局变量来存储组件,在content里面存放路径,组件,及其删除缓存的方法
export const KeepAliveContext = createContext({
keepPaths: [],
keepElements,
dropByPath(path) {
keepElements[path] = null;
}
});
console.log(KeepAliveContext, 99999);
//keepPaths和当前路径做比较,keepPaths可以是字符串也可以是正则表达式,所以通过if else判断即可,
const isKeepPath = (keepPaths, path) => {
let isKeep = false;
for (let i = 0; i < keepPaths.length; i++) {
let item = keepPaths[i];
if (item === path) {
isKeep = true;
}
if (item instanceof RegExp && item.test(path)) {
isKeep = true;
}
if (typeof item === 'string' && item.toLowerCase() === path) {
isKeep = true;
}
}
return isKeep;
};
export function useKeepOutlet() {
const location = useLocation();
const element = useOutlet();
const { keepElements, keepPaths } = useContext(KeepAliveContext);
const isKeep = isKeepPath(keepPaths, location.pathname);
if (isKeep) {
keepElements[location.pathname] = element;
}
return <>
{!isKeep && element}
{
Object.entries(keepElements).map(([pathname, element]) => (
<div
key={pathname}
style={{ height: '100%', width: '100%', position: 'relative', overflow: 'hidden auto' }}
className="keep-alive-page"
hidden={!matchPath(location.pathname, pathname)}
>
{element}
</div>
))
}
</>;
}
const KeepAliveLayout = (props) => {
const { keepPaths, ...other } = props;
const { keepElements, dropByPath } = useContext(KeepAliveContext);
return (
<KeepAliveContext.Provider value={{ keepPaths, keepElements, dropByPath }} {...other} />
);
};
export default KeepAliveLayout;