react react路由使用掌握

简介: react react路由使用掌握

目录


前言


react 路由的使用依靠 react-router-dom,今天进行一个系统的使用示范进行记录学习。


内容包括:基本路由结构、顶级路由的提取、实现keepAlive的方法。


注意:以下代码都运行于 react 项目中,学习前请自行创建简单 react 项目。


基本路由结构


最外层由 HashRouter 包裹,还有另一个可选组件 BrowserRouter,最浅显的区别是路由中是否携带 # 隔开路由,前者携带后者不携带。


然后 Routes 组件中间开始包裹 Route 组件编写每个路由,Route 中 path 属性为路由名,element 为路由组件。


Link 用于使用 to 属性进行路由间跳转。

import React from 'react';
import ReactDOM from 'react-dom/client';
import {HashRouter, Routes, Route, Link} from "react-router-dom";
const Home = () => {
    return (
        <>
            <p>当前的路由:/</p>
            <Link to='/users'>跳转用户页</Link>
        </>);
};
const Users = () => {
    return (
        <>
            <p>当前的路由:/users</p>
            <Link to='/'>go Home</Link>
        </>);
};
const App = () => {
    return (
        <HashRouter>
            <Routes>
                <Route path="/" element={<Home/>}/>
                <Route path="/users" element={<Users/>}/>
            </Routes>
        </HashRouter>
    );
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(React.createElement(App));

至此我们完成了最简单的路由页面:

image.png

路由公共部分提取


有时候我们需要对路由公共的部分提取,比如一个每个路由页面都存在的导航,每个页面都会现实的提示等。


比如我们简单页面显示的一行当前的路由,我们可以提取到公共的部分,我喜欢叫它顶级路由。


很简单,我们只要用一个 Route 创建一个顶级路由(这里的Top)包裹所有的路由页面。


然后这里用到了两个函数:


useLocation,因为我们的公共部分需要用到当前路由名,useLocation 可以得到对应的 pathname,因为当前路由这段话被提到了公共部分,因此各个路由组件中的这句都可以删除。

useOutlet,可以得到当前包裹的路由组件,怎么理解呢?大概就是可以理解为进入每个路由之前都会先进入 Top ,比如进入 Home 路由时,先进入 Top,然后 Top 中可以使用 useOutlet 得到真正要进入的路由组件,也就是 Home 组件,进入 User 路由时同理。

import React, {useState} from 'react';
import ReactDOM from 'react-dom/client';
import {HashRouter, Routes, Route, Link, useOutlet, useLocation} from "react-router-dom";
const Top = () => {
    const {pathname} = useLocation();
    const child = useOutlet();
    return (
        <div>
            <p>当前的路由:{pathname}</p>
            <div>
                {child}
            </div>
        </div>
    )
}
const Home = () => {
    return (
        <>
            <Link to='/users'>跳转用户页</Link>
        </>);
};
const Users = () => {
    return (
        <>
            <Link to='/'>go Home</Link>
        </>);
};
const App = () => {
    return (
        <HashRouter>
            <Routes>
                <Route path="/" element={<Top/>}>
                    <Route path="/" element={<Home/>}/>
                    <Route path="/users" element={<Users/>}/>
                </Route>
            </Routes>
        </HashRouter>
    );
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(React.createElement(App));

实现keepalive


首先我们要知道一个问题,那就是路由切换时,路由的组件会被卸载,因此状态不会保留。

我们将 Users 组件换成以下内容测试:

const Users = () => {
    const [num, setNum] = useState(0)
    return (
        <>
            <div>{num}</div>
            <div>
                <button onClick={() => {
                    setNum(num + 1)
                }}>数字加一
                </button>
            </div>
            <Link to='/'>go Home</Link>
        </>);
};

当我们切换之后,数字又变为0了。

image.png

在某些场景下我们不希望如此。


比如填写表单时,我们不希望自己不小心退出了表单页面就需要重新填写已填写过的内容。


那这时候我们就需要 keepalive,实现对路由状态的保存。


怎么实现呢,我们可以做一个假设,我们在路由切换时只是隐藏了路由组件而不是销毁它。


那我们的第一步就是要让所有路由组件显示在一起。


第一步 —— 保存所有路由组件


注意:以下内容可能需要掌握 Context 和 Ref 的用法,不了解得先去 React 官网学习


我们可以使用 Context 作为路由的全局状态,然后状态中维护一个 Ref 对象 allRouteElements ,用它来保存所有路由组件,所有路由组件中都可以访问到 allRouteElements。


使用前不要忘了引入createContext, useContext, useRef。

import React, {useState, createContext, useContext, useRef} from 'react';
const KeepAliveContext = createContext()
const App = () => {
    const allRouteElements = useRef({})
    return (
        // 使用KeepAliveContext.Provider包裹,value提供一个全局状态
        // 状态中有一个allRouteElements
        <KeepAliveContext.Provider value={{allRouteElements}}>
            <HashRouter>
                <Routes>
                    <Route path="/" element={<Top/>}>
                        <Route path="/" element={<Home/>}/>
                        <Route path="/users" element={<Users/>}/>
                    </Route>
                </Routes>
            </HashRouter>
        </KeepAliveContext.Provider>
    );
}

然后我们可以在顶级路由 Top 中进行各路由组件的保存,然后再遍历 allRouteElements.current 将所有路由组件渲染在一起。

const Top = () => {
    const {pathname} = useLocation();
    const child = useOutlet();
    // 使用useContext得到全局状态中的allRouteElements
    const {allRouteElements} = useContext(KeepAliveContext)
    // 每次访问到新路由组件时,都会加进allRouteElements
    allRouteElements.current[pathname] = child
    return (
        <div>
            <p>当前的路由:{pathname}</p>
            {Object.entries(allRouteElements.current).map(([curPathname, curChild]) => {
                return <div key={curPathname} >{curChild}</div>
            })}
        </div>
    )
}

我们的第二步就是,让路由组件根据是否在自己的路由下进行隐藏操作。

第二步 —— 控制路由组件显隐

我们通过 hidden={curPathname !== pathname} ,如果遍历到的路由名与当前真实路由不一致,则隐藏。

const Top = () => {
    const {pathname} = useLocation();
    const child = useOutlet();
    const {allRouteElements} = useContext(KeepAliveContext)
    allRouteElements.current[pathname] = child
    return (
        <div>
            <p>当前的路由:{pathname}</p>
            {Object.entries(allRouteElements.current).map(([curPathname, curChild]) => {
                // 遍历到的路由名与当前真实路由不一致,则隐藏
                return <div key={curPathname} hidden={curPathname !== pathname}>{curChild}</div>
            })}
        </div>
    )
}

至此我们实现了路由的 keepalive,我们测试一下。

image.png

第三步 —— 控制需要keepalive的路由


我们可能不是所有页面都希望实现 keepalive ,因此我们可以通过一些参数来控制,比如我们可以手动传入需要 keepalive 的路由。


我们在 Context 状态中多加一个全局属性,用数组的形式保存我们需要 keepalive 的路由。

const App = () => {
    const allRouteElements = useRef({})
    const needAlive = ['/users']
    return (
        <KeepAliveContext.Provider value={{allRouteElements, needAlive}}>
            <HashRouter>
                <Routes>
                    <Route path="/" element={<Top/>}>
                        <Route path="/" element={<Home/>}/>
                        <Route path="/users" element={<Users/>}/>
                    </Route>
                </Routes>
            </HashRouter>
        </KeepAliveContext.Provider>
    );
}

然后我们再去顶级路由做判断:

const Top = () => {
    const {pathname} = useLocation();
    const child = useOutlet();
    const {allRouteElements, needAlive} = useContext(KeepAliveContext)
    // 通过 needAlive 数组来判断当前路由是否需要 keepAlive
    let isNeed = false
    needAlive.map(cur => {
        if (cur === pathname) {
            isNeed = true
        }
    })
    if (isNeed) {
        allRouteElements.current[pathname] = child
    }
    return (
        <div>
            <p>当前的路由:{pathname}</p>
            {Object.entries(allRouteElements.current).map(([curPathname, curChild]) => {
                return <div key={curPathname} hidden={curPathname !== pathname}>{curChild}</div>
            })}
            {/* 不需要 keepAlive 的不存在于 allRouteElements.current,需要额外渲染与销毁 */}
            {!isNeed ? <div>{child}</div> : null}
        </div>
    )
}

此时我们的首页没有 keepAlive 而 用户页有,因此我们在首页也加上一个状态变更的计数器,进行对比:

const Home = () => {
    const [num, setNum] = useState(0)
    return (
        <>
            <div>{num}</div>
            <div>
                <button onClick={() => {
                    setNum(num - 1)
                }}>数字减一
                </button>
            </div>
            <Link to='/users'>跳转用户页</Link>
        </>);
};

可以看到我们首页的数字变化之后切换路由就又变为0了,而有用户页加的数字并没有变化。

image.png

第四步 —— 清除缓存


即便我们加入了 keepAlive ,有时候我们有需求需要手动清除页面的状态,也可以当作清除缓存。


怎么做呢?我们现在实现 keepAlive 的路由组件全部保存在 allRouteElements 中,渲染时只是不停在渲染 allRouteElements 这个装了路由组件的类数组而已,我们只需要添加一个可以删除 allRouteElements 对应路由组件的方法不就可以了吗。

const Users = () => {
    const [num, setNum] = useState(0)
    // 获取该函数
    const {delCache} = useContext(KeepAliveContext)
    const {pathname} = useLocation()
    return (
        <>
            <div>{num}</div>
            <div>
                <button onClick={() => {
                    setNum(num + 1)
                }}>数字加一
                </button>
            </div>
            <div>
                {/* 可以通过事件控制删除缓存 */}
                <button onClick={() => {
                    delCache(pathname)
                }}>清除缓存
                </button>
            </div>
            <Link to='/'>go Home</Link>
        </>);
};
const App = () => {
    const allRouteElements = useRef({})
    const needAlive = ['/users']
    // 添加一个该函数
    const delCache = (pathname) => {
        allRouteElements.current[pathname] = null
    }
    return (
        <KeepAliveContext.Provider value={{allRouteElements, needAlive, delCache}}>
            <HashRouter>
                <Routes>
                    <Route path="/" element={<Top/>}>
                        <Route path="/" element={<Home/>}/>
                        <Route path="/users" element={<Users/>}/>
                    </Route>
                </Routes>
            </HashRouter>
        </KeepAliveContext.Provider>
    );
}

按步骤理解:


allRouteElements中本来保存了 Users ,离开 Users 路由时只是将 Users组件隐藏了。

你现在使用 delCache 删除 allRouteElements 中的 Users 组件,离开 Users 路由时就不再是隐藏而是销毁它了。

然后再次进入 Users 时,顶级路由 Top 又重新将 Users 组件保存到 allRouteElements,但是原来的状态已经丢失,就实现了清除缓存的要求。

image.png

完整代码,出现问题可以核对

import React, {useState, createContext, useContext, useRef} from 'react';
import ReactDOM from 'react-dom/client';
import {HashRouter, Routes, Route, Link, useOutlet, useLocation} from "react-router-dom";
const KeepAliveContext = createContext()
const Top = () => {
    const {pathname} = useLocation();
    const child = useOutlet();
    const {allRouteElements, needAlive} = useContext(KeepAliveContext)
    // 通过needAlive数组来判断当前路由是否需要keepAlive
    let isNeed = false
    needAlive.map(cur => {
        if (cur === pathname) {
            isNeed = true
        }
    })
    if (isNeed) {
        allRouteElements.current[pathname] = child
    }
    return (
        <div>
            <p>当前的路由:{pathname}</p>
            {Object.entries(allRouteElements.current).map(([curPathname, curChild]) => {
                return <div key={curPathname} hidden={curPathname !== pathname}>{curChild}</div>
            })}
            {/*不需要keepAlive的不存在于allRouteElements.current,需要额外渲染与销毁*/}
            {!isNeed ? <div>{child}</div> : null}
        </div>
    )
}
const Home = () => {
    const [num, setNum] = useState(0)
    return (
        <>
            <div>{num}</div>
            <div>
                <button onClick={() => {
                    setNum(num - 1)
                }}>数字减一
                </button>
            </div>
            <Link to='/users'>跳转用户页</Link>
        </>);
};
const Users = () => {
    const [num, setNum] = useState(0)
    // 获取该函数
    const {delCache} = useContext(KeepAliveContext)
    const {pathname} = useLocation()
    return (
        <>
            <div>{num}</div>
            <div>
                <button onClick={() => {
                    setNum(num + 1)
                }}>数字加一
                </button>
            </div>
            <div>
                {/* 可以通过事件控制删除缓存 */}
                <button onClick={() => {
                    delCache(pathname)
                }}>清除缓存
                </button>
            </div>
            <Link to='/'>go Home</Link>
        </>);
};
const App = () => {
    const allRouteElements = useRef({})
    const needAlive = ['/users']
    // 添加一个该函数
    const delCache = (pathname) => {
        allRouteElements.current[pathname] = null
    }
    return (
        <KeepAliveContext.Provider value={{allRouteElements, needAlive, delCache}}>
            <HashRouter>
                <Routes>
                    <Route path="/" element={<Top/>}>
                        <Route path="/" element={<Home/>}/>
                        <Route path="/users" element={<Users/>}/>
                    </Route>
                </Routes>
            </HashRouter>
        </KeepAliveContext.Provider>
    );
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(React.createElement(App));

第五步 —— 使用依赖包

我将以上内容封装为npm包,可以直接使用,地址:@chanjs/keepalive

尾言


如果觉得文章对你有帮助的话,欢迎点赞收藏哦,有什么错误或者意见建议也可以留言,感谢~

相关文章
|
2月前
|
前端开发 JavaScript
React项目路由懒加载lazy、Suspense,使第一次打开项目页面变快
本文介绍了在React项目中实现路由懒加载的方法,使用React提供的`lazy`和`Suspense`来优化项目首次加载的速度。通过将路由组件改为懒加载的方式,可以显著减少初始包的大小,从而加快首次加载速度。文章还展示了如何使用`Suspense`组件包裹`Switch`来实现懒加载过程中的fallback效果,并提供了使用前后的加载时间对比,说明了懒加载对性能的提升作用。
180 2
React项目路由懒加载lazy、Suspense,使第一次打开项目页面变快
|
16天前
|
前端开发 API UED
React 路由守卫 Guarded Routes
【10月更文挑战第26天】本文介绍了 React 中的路由守卫(Guarded Routes),使用 `react-router-dom` 实现权限验证、登录验证和数据预加载等场景。通过创建 `AuthContext` 管理认证状态,实现 `PrivateRoute` 组件进行路由保护,并在 `App.js` 中使用。文章还讨论了常见问题和易错点,提供了处理异步操作的示例,帮助开发者提升应用的安全性和用户体验。
32 1
|
3月前
|
移动开发 资源调度 前端开发
介绍React路由模式
【8月更文挑战第10天】介绍React路由模式
57 12
|
19天前
|
前端开发 安全 网络安全
React——路由Route
React——路由Route
27 2
React——路由Route
|
1月前
|
资源调度 前端开发 测试技术
React Router 路由管理
【10月更文挑战第10天】本文介绍了 React Router,一个在 React 应用中管理路由的强大工具。内容涵盖基本概念、安装与使用方法、常见问题及解决方案,如路由嵌套、动态路由和路由守卫等,并提供代码示例。通过学习本文,开发者可以更高效地使用 React Router,提升应用的导航体验和安全性。
202 19
|
1月前
|
前端开发 网络架构
React 路由
10月更文挑战第11天
33 2
|
1月前
|
前端开发 JavaScript 网络架构
实现动态路由与状态管理的SPA——使用React Router与Redux
【10月更文挑战第1天】实现动态路由与状态管理的SPA——使用React Router与Redux
33 1
|
2月前
|
前端开发 Python
React技术栈-React路由插件之自定义组件标签
关于React技术栈中React路由插件自定义组件标签的教程。
54 4
React技术栈-React路由插件之自定义组件标签
|
2月前
|
移动开发 前端开发 应用服务中间件
React两种路由模式的实现原理
React两种路由模式的实现原理
83 3
|
2月前
|
前端开发 程序员 API
React技术栈-React路由插件之react-router的基本使用
这篇博客介绍了React路由插件react-router的基本使用,包括其概念、API、以及如何通过实战案例在React应用中实现SPA(单页Web应用)的路由管理。
68 9