React Router

本章对应视频讲解如下

项目使用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开发中比较常用的是 BrowserRouterHashRouter


官方推荐使用的路由方式是 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();

}