一、安装
在当前项目中用npm i react-router-dom来安装react-router-dom这个包
二、使用
2.1配置路由
2.1.1 createBrowserRouter和createHashRouter API
配置路由的目的就是配置你什么地址跳转到什么页面,目前官方提供的最新的配置路由的api中主要的两个有createBrowserRouter和createHashRouter。这两个api底层用了不用的方法来实现相同的效果,也就是切换地址不会向后端请求数据,而是前端切换页面,以此来达到最快的页面响应。
createBrowserRouter底层是用到了h5的新特性history,这个方法可以实现修改地址栏地址而不会向后端发起请求,并且history这个对象本身就提供了很多控制页面跳转,前进后退等方法。而createHashRouter则是利用了锚点跳转不发起请求的特点,也就是你在网络地址后面加上#,#后面的内容无论怎么改变都不会引起浏览器发起网络请求,然后通过监听onhashchange事件来监听这个锚点的变化,以此来匹配配置的路由。
2.1.2如何配置路由
两个api的使用方法相同,我这里就介绍createBrowserRouter的使用方法,这个api里面传入一个对象数组参数,如下面代码所示。这是按层级进行页面的配置的,里面主要的就是path、loader、action、element、errorElement、children这几个属性
path: 这个属性是用来配置当前的路由的地址,也就是当你访问http://localhost:3000/contacts就会匹配到这个路由。(我这里默认项目是开在本地3000端口上,下面也默认开在这个地址上,不在赘述)
loader: 这个属性是一个方法,当你进入当前路由后,渲染也面前会触发的一个方法,这个之后会详细讲解
action: 当你发起form提交事件的时候,会触发这个方法,需要配合react-router内部提供的Form组件使用,后面会详细讲解。
element: 这个就是你需要渲染出来的页面了。
errorElement: 当你页面出现错误时候,一般是不希望显示一些浏览器默认的报错信息页面,显得产品很多错误,可以通过这个来显示你想要展示给用户的页面和信息,将错误控制在可控范围内。
children: 这里面的内容也是一个对象数组,对象里也是这些配置,作为嵌套路由,像下面这个例子,当你这样配置后,当你访问http://localhost/contacts/detail时候,react-router就会匹配到最外层的那一层路由,然后显示Contact组件,然后会匹配到里面的那一层地址为detail的路由,然后在Contact组件里的Outlet组件里显示Detail组件。这里引出一个Outlet组件,这个是react-router提供的组件,用来显示嵌套路由的。
index: 还有一个index属性,通过配置其为true,则默认匹配这个路由
const router=createBrowserRouter([
{
path:'/contacts',
loader:contactLoader,
action:contactAction,
element:<Contact/>,
errorElement:<ErrorPage/>,
children:[
{
path:'detail',
element:<Detail/>
}
]
},
])
2.2 应用路由
RouterProvider是react-router提供的组件,利用这个组件来将react-router应用到也项目上,这组件上有一个属性 router,将之前用createBrowerRouter创建好的router赋值上去,就正常应用上react-router了。
ReactDOM.createRoot(document.querySelector('#root')).render(
<React.StrictMode>
<RouterProvider router={router}/>
</React.StrictMode>
)
2.3嵌套路由
之前也有提到过,通过children属性可以配置需要嵌套在当前路由下的嵌套子路由,另外需要做的是在当前路由所显示的element里要放置一个Outlet组件来进行占位,这个组件所在的位置就是嵌套子路由将会渲染的位置。
<div id="detail" className={navigation.state==="loading"?"loading":''}>
<Outlet />
</div>
2.4 loader详细解释
loader方法在渲染页面前执行,这个方法有一个对象参数,里面常用的有两个属性params和request,下面用对象解构的方法把这两个属性解构出来了。然后他的返回值,在实际渲染的路由组件上,可以通过useLoaderData()的方法获取到。
params: 当你的路由path写的是类似/contact/:name/:age,当碰到类似/contact/lin/12这样的路径的时候,就会匹配这个路由,这里的name对应lin,age对应12,而params里就会有这些数据,以键值对的形式,也就是你可以通过params.name来得到lin。
request: 当你是使用?键=值这样的形式传递页面之间的参数的时候,要取到这些参数信息,就要用到request,这个对象里面的url里存放着我们需要的数据,那你就需要将这个url通过URL构造函数构造一个URL对象,在这个对象里的searchParams上可以通过get方法获取到那些数据。
export async function loader({request,params}) {
console.log("loader")
const url=new URL(request.url);
const q=url.searchParams.get('q');
const contacts = await getContacts(q);
return { contacts,q }
}
2.4 action详细解释
action是当你触发Form组件的提交事件时候触发,注意这里是Form组件,不是浏览器的原生form。当你触发Form的提交事件后,就会触发对应路由下配置的action事件,action事件的执行不会阻塞页面的渲染,也就是当你触发提交事件后,如果你的action是异步的,组件不会等待你的action执行完在渲染,他会先渲染,然后等你action执行完毕后在进行一次渲染。
这里有出现了一个新的方法,formData(),这个就是获取到请求体数据,然后通过Object.fromEntries将其转化为我们平常使用的键值对对象数据结构。
然后它的返回值可以通过useActionData()来获取。
export async function action({request,params}){
const formData=await request.formData();
const updates=Object.fromEntries(formData);
await updateContact(params.contactId,updates)
return redirect(`/contacts/${params.contactId}`)
}
2.5路由跳转
路由跳转有两种主要的途径,一种是通过组件,一种是通过脚本。
2.5.1 组件跳转
组件跳转主要是通过Link组件或者是NavLink组件,类似与a标签,点击后会更改地址栏地址,不过不会触发网络请求,而是触发前端路由跳转。这两个组件里有一个to属性,就是用来配置跳转的路径,需要注意的是这个路径书写,当你的路径是 ‘/’ 这个开头的话,那就会以你写的路径作为根路径进行跳转,如果你写的不是以 / 开头的话,那就是将你写的路劲增添到现有页面路径的后面。举个例子,当你现在所在的页面路径是 http://localhost:3000/main ,如果你的跳转组件里面写的跳转路径是 contact ,则点击后将会跳转到 http://localhost:3000/main/contact ,而如果你所配置的跳转路径是 /contact ,则会跳转到不同 http://localhost:3000/contact。
Link和NavLink组件的基本功能和用法相同,需要注意的不同点是NavLink会匹配当前所在的路由地址,然后给对应的NavLink组件添加active类,你就可以通过这个特性来对这些路由跳转组件进行不一样的页面展示效果。
2.5.2 脚本跳转
很多情况下,需要用到脚本跳转,比如说有时候的跳转是要通过执行一部分脚本逻辑后进行页面的跳转的,这个时候单靠组件跳转就不太行了,react-router-dom提供了一个钩子useNavigate(),它返回一个路由控制方法navigate,有两种使用方法,一种是传入路径,会跳转到对应路径页面,还有一种是传入是数字,意为前进后退到历史页面。
三、进阶
以上使用部分是react-router-dom库的基本使用,了解到上面的几个钩子、组件、方法,就可以基本搭建实现了前端路由跳转的react项目,不过还不够,还需要考虑许多优化问题,
3.1路由懒加载
在引入组件的时候,改用react库提供的lazy方法配合import方法进行组件的导入,这样就可以实现组件的懒加载,不会再用户第一次访问项目的时候将这个组件请求加载下来,而是在使用的时候的时候才会实际去请求这部分资源,需要注意的是,在使用懒加载组件的时候,需要在展示组件的外面在套一层Suspense组件,这个组件有个fallback属性,这个属性配置了你不如果没有加载成功这个对应的组件的时候,其暂时应该展示的组件的组件。这里作为路由组件,是直接在Outlet外面套了一层Suspense。
// import Contact from './routes/contact'
const Contact=lazy(()=>import('./routes/contact'))
//Outlet部分
<Suspense fallback={<h1>this is suspense</h1>}>
<Outlet />
</Suspense>
3.2容错,即时反馈
像之前的懒加载与suspense一样,你需要把风险控制在底层,展示给用户可控的画面,当你懒加载的路由没有成功的时候,不能展示给用户一个404或者502等错误的页面,需要给用户一个可以接收的反馈,Suspense就是做了这一部分工作。还有很多细节也需要容错,比方说之前创建路由里面的errorElement属性,当前路由页面如果有误,就会展示这个你希望展示给用户看的报错信息,而不会是冷冰冰的404错误页面等。
react-router-dom提供了一个useNavigation的钩子,它返回的navigation里有一个state属性,这个属性有三个值,idle(空闲)、submitting(提交中)、loading(加载中),通过这个state可以用来做一些页面的即时反馈,如下所示,当状态是loading时,就给这个div加上loading类,在css中对这个loading类进行处理,展示好看的加载页面,效果类似Suspense。
<div id="detail" className={navigation.state==="loading"?"loading":''}>
<Outlet />
</div>
3.3 路由缓存
路由缓存在很多场景下是必要的。比方说当前路由展示的一个列表组件,在这个组件渲染到页面上前都要进行数据的请求,获取数据后渲染在页面上,此时如果你跳转到其他页面,则默认原先那个组件就会被卸载掉,再次回来的时候,就相当于是重新再一次创建这个组件,需要重新初始化,重新请求数据。但这些步骤其实是多余的,因为我们曾经拥有过这份数据,不过没有好好珍惜。要减少网络请求,提高数据的利用率,有一个办法是通过全局状态管理来将这些数据保存下来,然后下次需要时直接给出,还有一个办法就是将原先组件缓存下来,而不是卸载掉,即设置其display为none,然后在需要时,不是再创建,而是将该组件的display设置为block显示出来。这样原来的组件就可以得到复用,数据不会丢失。
3.3.1路由缓存实现
原先我们用的是Outlet组件来展示当前页面需要展示的子路由组件,现在我需要控制这个路由组件,所以就不能用组件的方式了,改用useoutlet钩子,这个钩子返回的是当前级别路由匹配的子路由组件。现在我们拿到匹配到的组件了,将页面路径当作key与该组件进行绑定,然后保存再数组里,然后渲染这个数组到页面上,只有key与当前页面路径相等的项display设置为block,其他的display设置为none。
下面将该功能封装了起来,封装了一个ActiveRouter函数组件,主要逻辑放在这里面
function ActiveRouter({outlet}){
const {pathname}=useLocation();
const [componentList,setComponentList]=useState(new Map());
useEffect(()=>{
setComponentList(componentList=>{
if(!componentList.has(pathname)){
componentList.set(pathname,outlet)
}
return new Map([...componentList.entries()])
})
},[outlet])
return (
<>
{[...componentList.entries()].map(([key,component])=>
(
<div key={key} style={{display:key===pathname?'block':'none'}}>
{component}
</div>
)
)}
</>
)
}
export default memo(ActiveRouter)
然后再需要使用的地方,使用useOutlet钩子获取outlet,并将其传给ActiveRouter进行处理,
const outlet=useOutlet();
// 将原先的Outlet改为以下这个组件
<ActiveRouter outlet={outlet}/>
{/* <Outlet /> */}
通过上面这个ActiveRouter组件就实现了路由的缓存,用户可以安心的跳转到其他页面,归来依旧,还给用户原来的数据,像是表单输入的数据、滚动条位置,请求的数据,在归来时依旧保留。
上面的这个ActiveRouter只是实现了一个简单的路由,之后会编写一个钩子,实现更好的路由缓存,可以选择性的缓存路由,可以设置缓存的路由的最大数量等等功能,尽情期待…