React Router
本章对应视频讲解如下
边看视频讲解,边学习教程
参考:
https://reactrouter.com/docs/en/v6/getting-started/overview
https://stackoverflow.com/a/36623117/2602410
https://stackoverflow.com/a/51976069/2602410
前面的项目实战中,不同的页码跳转时,我们是自己写代码监听url hash 变化,实现url路由功能的
而实际工作中,大家基本都是用 著名的第3方库 React Router 实现 React url路由功能的
安装 React Router
执行如下命令安装 React Router 库
npm install react-router-dom
另外,如果使用 React Router,React库最好也使用 ES6模块库,而不是script标签导入的UMD库
Router 方式
React Router 中,有好几种路由方式
web开发中比较常用的是 BrowserRouter 和 HashRouter
官方推荐使用的路由方式是 BrowserRouter
就是直接通过常规 URL变化来实现路由。
比如 当 url切换为 /hyms.html/customer 、 /hyms.html/medicine 时,
React Router 可以做到不发送新的http请求到后端,而是触发前端render不同的 组件。
这底层通过现代浏览器的 history API 实现,具体参考这个
但问题是:首次请求必须从后端获取html和相关js库。
这就要求 服务端 做相应的支持 ,首次请求时,不同的url,比如 /hyms.html/customer /hyms.html/medicine
都对应到同一个html资源,具体参考这个
这样会导致开发、生产环境的 后端配置,都要做一定的修改,有些麻烦,
而且有的浏览器( 比如IE9 )对history API 不支持
所以根据我们现在的需求,使用 HashRouter 更方便一些
使用 HashRouter
前面我们自己的路由实现,是把url中 hash部分作为路由路径的
React Router中 与之对应的 是 HashRouter
使用 它,我们就不需要自己编写代码进行 hash路由处理了。
在需要使用 的模块代码前面导入 HashRouter相关名字,如下
import { Routes,Route,HashRouter } from "react-router-dom"
然后把 需要用到URL路由部分的 最上层组件 放到 HashRouter组件内部,
比如,
let root = ReactDOM.createRoot(document.querySelector('main'));
root.render(
<HashRouter>
<App />
</HashRouter>
);
然后APP组件,或者其内部子组件,都可以这样使用路由
export default function App() {
return (
<Routes>
<Route path="/" element={<Customer />} />
<Route path="customer" element={<Customer />} />
<Route path="medicine" element={<Medicine />} />
</Routes>
);
}
这样,当执行到这里时,HashRouter 会根据当前的 url hash 内容 选择对应的 React Element
但是缺省情况下,HashRouter 要求 url hash 得以 / 开头,
url 和选择组件 的对应关系如下:
hyms.html => Customer
hyms.html# => Customer
hyms.html#/ => Customer
hyms.html#/customer => Customer
hyms.html#/medicine => Medicine
我们的实战项目示例如下
let root = ReactDOM.createRoot(document.querySelector('main'));
root.render(
<HashRouter>
<Routes>
<Route path="" element={<Customer />} />
<Route path="customer" element={<Customer />} />
<Route path="medicine" element={<Medicine />} />
<Route path="order" element={<Order />} />
<Route path="myprofile" element={<MyProfile />} />
<Route path="stats" element={<Stats />} />
</Routes>
</HashRouter>
)
另外,需要修改前面的 html链接href的值,
<a href="#medicine">药品</a> 改为 <a href="#/medicine">药品</a>
<a href="#customer">客户</a> 改为 <a href="#/customer">客户</a>
等等
也可以使用 React Router 里面的 Link 组件来指定连接,如下
import { Link} from 'react-router-dom'
// 组件 render 里面的链接,可以这样写
<Link to="/medicine" >药品</Link>
<Link to="/customer" >客户</Link>
而且也不需要我们编写 hashchange 时间的处理函数,React Router 会帮我们处理好
my
关键概念
参考 https://reactrouter.com/docs/en/v6/getting-started/concepts#matching
嵌套路由
参考 https://www.robinwieruch.de/react-router-nested-routes/
组件懒加载
参考
https://www.robinwieruch.de/react-router-lazy-loading/
https://react.dev/reference/react/lazy
https://react.dev/reference/react/Suspense
自己实现 HashRouter
let root
const hash_router = {
'/home' : '/home.js',
'/deposit' : '/deposit.js',
'/svc_record' : '/svc_record.js',
'/charger' : {file: '/device.js', component:'Charger'},
'/carwasher' : {file: '/device.js', component:'CarWasher'},
'/locker' : {file: '/device.js', component:'Locker'},
}
let hashEntries = Object.entries(hash_router)
const ModuleFile2Element = {}
async function routerChanged(){
let url = window.location.hash.slice(1)
let comDesc = null // 组件描述
// 使用for ... of 遍历数组
for (let [key, value] of hashEntries) {
if (url.startsWith(key)){
comDesc = value
break
}
}
// hash_router中没有该配置
if (!comDesc){
alert('该功能未实现')
return
}
// 尝试从表中获取已经 创建 的 jsx element
let element = ModuleFile2Element[url]
// 未加载过
if (!element) {
// 只是模块文件路径, 组件肯定是缺省导出
if(typeof comDesc === 'string'){
// let { default: DClass } = await import(comDesc);
// Component = DClass
Component = (await import(comDesc)).default;
}
// 否则,是命名导出
else{
Component = (await import(comDesc.file))[comDesc.component]
}
element = React.createElement(Component)
ModuleFile2Element[url] = element
}
root.render(element);
}
window.onload = function(){
// todo 只要hash 变化就从root开始render, 效率有问题,
// 应该找到变化开始的那一层的element,调用render
// 比如 /a/b/c 变为 /a/e/f, 应该调用 a 对应的 element 的render, 而不是root的render
// 比如 /a/b/c 变为 /a/b/f, 应该调用 b 对应的 element 的render, 而不是root的render
window.addEventListener('hashchange', function() {
routerChanged()
});
root = ReactDOM.createRoot(document.querySelector('main'));
routerChanged();
}