前言
react-router-dom 是 react 开发中必备的路由库,使用也简单方便入手,它将我们的的页面和路由关联起来,因此也避免了很多隐性变量的使用,某种程度上降低了我们不同页面之间的依赖,并且可以直接通过路由定位到指定页面功能,可以说非常实用
下面就简单介绍下平时比较常用的功能吧 -- 简易案例demo
react-router-dom
下面会从其安装、使用、传参等功能介绍
安装
js
yarn add react-router-dom
BrowserRouter
平时使用 router 时,外层第一步就是要将我们的根组件 app
父节点设置为 BrowserRouter
,这样我们的项目才能够正常使用路由功能
我们可能除了 BrowserRouter,还会见到 HashRouter,这个主要在客户端使用的散列值方案,用的比较少,一般都是 BrowserRouter,就不多介绍了
js
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
Routes、Route、Navigate
Routes、Route、Navigate
分别是 路由组、路由、导航
我们的所有路由都由 Routes
包裹,可以认为放到了一个集合中统一管理
Route
就是我们的单个路由设置信息,其主要包含了我们的路由路径 path
,节点指向的组件 element
,大小写区分等,并且 Route
具备子 Route
功能,即包含子路由(二级路由,三级路由等),可以让我们的项目更有层次感
Navigate
导航功能,可以配合我们的 Route
实现重定向
功能,可以将某个页面路由导航到另外一个路由 path
path
路由,/
开头的为绝对路由,无/
则为相对路由,即一个根据根节点,一个根据上级节点,*
为匹配所有路由,优先级比较低,一般用来编写非法路由
提示页面
如下所示,加入了一些基本路由和子路由
js
return (
<Routes>
<Route path='/home' element={<HomeView />} >
<Route path='index1' element={<HomeIndex1View />} />
<Route path='index2' element={<HomeIndex2View />} />
<Route path='index3' element={<HomeIndex3View />} />
<Route path='index4' element={<HomeIndex4View />} />
</Route>
<Route path='/login' element={<LoginView />} />
{/* 重定向 */}
<Route path="/" element={<Navigate to="/login" />} />
{/* 匹配所有路由,优先级比较低 */}
<Route path='*' element={<NotFoundView />} />
</Routes>
);
Outlet
Outlet
用于展示对应路由显示的组件,可以嵌入到指定组件位置,其更像是上面 Routes 一系列的替代品,如果不写他,就要在对应地点写上上面一堆重复的 Routes 一系列组件,否则是无法正常显示的,正常就一个公用的导航页面倒是有这么写的,不过不推荐,推荐使用上面那种加上 outlet
使用时,其会根据路由以及所处的层级,自动展示指定路由对应的组件
js
// /home
<div>
...
<div>
<Outlet /> //这里及时上面 homeIndex1View 页面显示的位置
</div>
</div>
Link、NavLink、useNavigate
他们三个都是用来跳转路由的,只不过 Link、NavLink
是显示跳转组件,useNavigate
是手动跳转
Link、NavLink
两个类似,如下所示,通过 to
设置跳转路径,state
传递参数
js
<Link to='/home/index3' state='我是link传递的信息' >link跳转</Link>
<NavLink to='/home/index4' state='我是navlink传递的信息' >navlink跳转</NavLink>
useNavigate
为手动跳转,也可以传递参数,看名字就知道是 hook
方式的写法
js
const view = () => {
const navigate = useNavigate()
...
navigate(path, {
state: '我传递的信息'
})
}
接收参数(useLocation、useSearchParams)
前面我们通过 state 传递参数,这里可以通过 useLocation
来接收参数,一般也都是使用这个手段来传递参数
js
const location = useLocation()
//取出里面的 state 即可
location.state
query + useSearchParams 传参
当然也有狠人,直接在路由中拼出了 query
类型的参数,我们也可以通过 useSearchParams
来获取
js
//例如传递参数是通过 query 的方式
navigate('/home?id=123')
使用 useSearchParams
来获取 query 类型数据的内容
js
const params = useSearchParams()
...
params[0].get('id')
嵌套路由
前面讲了如何使用 Routes
,如何使用 Outlet
来完成我们的路由功能,然后只介绍了一级路由,如果有二级路由(路由里面还有路由,Outlet
还有 Outlet
该怎么办呢,其实逻辑跟第一个一样,就是多嵌了一个 Outlet
罢了)
如下面页面所示,做了一个有一级,一级里面一个页面还嵌套了一个二级路由的页面(页面丑了点,毕竟是案例哈),实际上逻辑并不复杂,也是提前使用 Routes
配置好路由关系,使用 Outlet
来放位置
ps
:配置这种路由时,最好将我们外层的宽高都设置成 100%
,否则内部可能后续很难控制,或者适配的更好(用media 写一堆的当我没说)
js
body {
...
width: 100%;
height: 100%;
}
html {
width: 100%;
height: 100%;
}
/* id选择器 */
#root{
height: 100%;
width: 100%;
}
配置 Routes
js
<Routes>
<Route path='/home' element={<HomeView />} >
<Route path='index1' element={<HomeIndex1View />} />
<Route path='index2' element={<HomeIndex2View />} />
<Route path='index3' element={<HomeIndex3View />} />
<Route path='index4' element={<HomeIndex4View />} />
//可以看到,我们在 settting 中嵌入了两个子路由,setting 也在 home 路由中
<Route path='setting' element={<SettingView />}>
<Route path='user' element={<SettingUserView />} />
<Route path='love' element={<SettingLoveView />} />
</Route>
</Route>
<Route path='/login' element={<LoginView />} />
{/* 匹配所有路由,优先级比较低 */}
<Route path='*' element={<NotFoundView />} />
</Routes>
一级路由 Home
上面导航
,下面子路由 Outlet
js
// titles path除了跳转用,也会用来对比是否是当前页面
// 对于存在二级路由的,则可以额外添加参数判断,避免不正常显示
const titles = [{
name: '首页一',
path: '/home/index1'
}, {
name: '首页二',
path: '/home/index2'
}, {
name: '首页三',
path: '/home/index3'
}, {
name: '设置页面',
path: '/home/setting/user',
matchpath: '/home/setting' //用于处理外层包含关系的内容
}]
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column' }}>
//上面的内容
<div style={{ width: '100%', height: 100, display: 'flex', justifyContent: 'space-evenly', alignItems: 'center', backgroundColor: '#999' }}>
<Link to='/home/index3' state='我是link传递的信息' >link跳转</Link>
<NavLink to='/home/index4' state='我是navlink传递的信息' >navlink跳转</NavLink>
{
titles.map(function (item, index) {
return (
<div
key={index}
style={{
cursor: 'pointer',
backgroundColor: pathname.includes(item.matchpath ? item.matchpath : item.path) ? 'skyblue' : '#fff'
}}
onClick={() => {
navigate(item.path, {
state: '我传递的信息是' + item.path
})
setPathname(item.path)
}} >{item.name}</div>
)
})
}
<div style={{ cursor: 'pointer', color: 'red' }} onClick={() => {
navigate('/login')
}}>退出登录</div>
</div>
//底部路由组件显示内容
<div style={{ display: 'flex', width: '100%', height: '100%' }}>
<Outlet />
</div>
</div>
二级路由 Setting
上面一级路由,下面左侧二级路由,下面右侧,二级路由子页面
js
<div style={{ flex: 1, display: 'flex' }}>
//底部左侧导航
<div style={{ width: 100, height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-evenly', alignItems: 'center', backgroundColor: '#ddd' }}>
{
titles.map(function (item, index) {
return (
<div
key={index}
style={{
cursor: 'pointer',
backgroundColor: pathname === item.path ? 'skyblue' : '#fff'
}}
onClick={() => {
navigate(item.path, {
state: '我传递的信息是' + item.path
})
setPathname(item.path)
}} >{item.name}</div>
)
})
}
</div>
//底部右侧子路由组件显示内容
<div style={{ display: 'flex', flex: 1, margin: 40 }}>
<Outlet />
</div>
</div>
最后
到这里就差不多了,相信三级路由也知道怎么搞了,然后再加入一个 NotFound
组件,再加入一个重定向,避免错误路由,或者局部路由出现
我们将 /
重定向到 login
相信就知道啥意思了,默认跳转到登陆页面即可,home、setting
的子页面也类似
NotFound
除了主路由,为何每个子路由中也放置了呢,因为要替换子路由没有的页面,如果使用最外层,会覆盖掉整个页面,写到里面,会在子路由显示的模块中显示我们的 NotFound
模块,这样也能知道哪个路由出错了
js
<Routes>
<Route path='/home' element={<HomeView />} >
<Route path='index1' element={<HomeIndex1View />} />
<Route path='index2' element={<HomeIndex2View />} />
<Route path='index3' element={<HomeIndex3View />} />
<Route path='index4' element={<HomeIndex4View />} />
{/* 重定向 */}
<Route path="" element={<Navigate to="index1" />} />
<Route path='setting' element={<SettingView />}>
<Route path='user' element={<SettingUserView />} />
<Route path='love' element={<SettingLoveView />} />
{/* 重定向 */}
<Route path="" element={<Navigate to="user" />} />
<Route path='*' element={<NotFoundView />} />
</Route>
<Route path='*' element={<NotFoundView />} />
</Route>
<Route path='/login' element={<LoginView />} />
{/* 重定向 */}
<Route path="/" element={<Navigate to="/login" />} />
{/* 匹配所有路由,优先级比较低 */}
<Route path='*' element={<NotFoundView />} />
</Routes>
历史记录 history
这个也是用的比较多的,一般用来恢复某个指定页面的显示效果,我们需要该功能来完善我们的页面使得回退
、重新打开指定页面
也能够有一个正常的显示效果
导入history
js
yarn add history
获取历史对象
我们需要使用 createBrowserHistory
获取历史记录对象,通过其使用历史记录功能
js
const history = createBrowserHistory()
创建监听、销毁监听
我们通过使用 history
的 listen
来监听路由的变化,通过该路由变化,我们可以用来更新我们的页面状态
使用完毕记得主动销毁,否则会出现意想不到的效果,监听对象会返回取消该监听的函数
,直接调用即可取消监听
如果有多级路由,那么多个地方使用 一般全局使用一个 history
对外导出即可,其他页面使用完毕后销毁即可
js
通过保存 pathname 进行对比,来判断进入了那个路由页面
const [pathname, setPathname] = useState<string>(location.pathname)
useEffect(() => {
//返回时会监听到
const unlisten = history.listen((res) => {
setPathname(res.location.pathname)
})
return () => {
//销毁时记得取消监听
unlisten()
}
}, [])
ps
:只有一个单页面不存在销毁不销毁的,不取消监听似乎也没啥问题哈
useRoutes
和 Routes
使用类似, 只不过是使用 hook
的方式,使用一个数组包含所有的对象,可以减少我们的代码
js
const routes = useRoutes([
{
path: '/home',
element: <HomeView />,
children: [
{path: 'index1', element: <HomeView />},
{path: 'index2', element: <HomeIndex2View />},
{path: 'index3', element: <HomeIndex3View />},
{path: 'index4', element: <HomeIndex4View />},
{path: '', element: <Navigate to="index1" />},
]},
{path: '/login', element: <LoginView />},
{path: '/', element: <Navigate to="/login" />},
])
实际使用起来两个都差不太多
动态路由
简而言之就是动态的路由,上面的 Routes、useRoutes
,我们看到了,我们的路由默认弄出了所有的路由
对于一些严谨的项目来说,这就出现小漏洞
了,就是我们用户如果没有进入某个页面的权限,那么如果强行输入对应路由,也是可以进入的,除非内部控制进去不让出来,否则还是可以进去的,要不每个页面都判断权限跳出,这也是一笔不小的工作量
那么动态路由怎么做呢,很简单,根据用户所拥有的权限,来动态调整更换整个 Routes
其一般有两个操作方案,一个是后端给好权限类型,前端根据不同权限筛选出应当存在的页面路由(实际上相对于第二步多了个翻译过程),第二个是后台直接给出该用户能访问的整个路由结构,然后前端根据返回的指定路由集合来配置路由
以上面项目页面为样本,使用第一种方案,设置一个需求:游客只有基础功能(home), 普通用户多了登录(home + login),没有设置,admin账户有设置(home + login + /home/setting),对于两者后台返回权限 1、3(1 + 2)、7(1 + 2 + 4),设置权限是2,也有可能返回数组,就以前面位操作为例
js
//home
const routes1 = () => {
return {
path: '/home',
element: <HomeView />,
children: [
{path: 'index1', element: <HomeIndex1View />},
{path: 'index2', element: <HomeIndex2View />},
{path: 'index3', element: <HomeIndex3View />},
{path: 'index4', element: <HomeIndex4View />},
{path: '', element: <Navigate to="index1" />},
]}
}
//login
const routes2 = () => {
return {
path: '/login',
element: <LoginView />
}
}
// home/setting
const routes3 = () => {
return {
path: 'setting',
element: <SettingView />,
children: [
{path: 'user', element: <SettingUserView />},
{path: 'love', element: <SettingLoveView />},
{path: '', element: <Navigate to="index1" />},
]},
}
const setupPermission = () => {
//后端返回权限 permission
const permission = ...
const routeNames = []
if (permission & 1) {
//获取home
const routes = routes1()
routeNames.push(routes)
//满足设置我们需要再子路由加入setting
if (permission & 4) {
routes.children.push(routes3())
}
}
//加入登录
if (permission & 2) {
routeNames.push(routes2())
}
setRouteNames(retes[2])
}
const getRoutes = (children: any[]) => {
return children.map((e, index) => {
return (
<Route key={index} path={e.path} element={e.element} >
{
e.children && getRoutes(e.children)
}
</Route>
)
})
}
<Routes>
{
getRoutes(routeNames)
}
</Routes>
ps
:除了上面这些,我们除了更新根部路由之外,最好加上 notfound,或者没有权限之类的提示,另外我们一些路由的页面也是会跟随权限变动的,可以直接根据更新后的用户权限判断好,也可以将处理好的方便外部使用的数据结构,通过 context 传递给子节点们使用
就这样,就完成了一个简易版动态路由了,如果是后台使用第二种方案怎么样,那就更好了,设置权限那一步不用我们做了,他直接整理好了,我们直接用后台给我们返回处理好的对象,调用 getRoutes
传递对应的方法即可,也可以整理权限相关,按照同样逻辑处理
最后
上面的基本上已经足够我们日常使用了,日常学习记录,也是我们进步的源泉,加油!