这篇 react-router 的 API 用法,针对 react-router-dom v5 :::info React-router-dom@6 官网:https://reactrouter.com/docs/en/v6
React-router-dom@5 官网:https://v5.reactrouter.com/web/guides/quick-start :::

一、正确理解 react-router

1. 理解单页面应用

单页面应用是使用一个 html 下,一次性加载 js, css 等资源,所有页面都在一个容器页面下,页面切换实质是组件的切换。

2. react-router 初探

① react-router 、react-router-dom、history 三者关系

history可以理解为 react-router的核心,也是整个路由原理的核心,里面集成了 popState、history、pushState原生 JS 路由 API

react-router可以理解为 react-router-dom的核心,里面封装了Router、Route、Switch等核心组件,实现了从路由的改变到组件的更新的核心功能,在我们项目中只要一次性引入react-router-dom就可以了

react-router-dom,在react-router的核心基础上,添加了

  • 用于跳转的Link组件
  • history模式下的BrowserRouter
  • hash模式下的HashRouter

所谓BrowserRouter 和 HashRouter,只不过是用了 history库中createBrowserHistory 和 createHashHistory的方法

② 小 demo

  1. import { BrowserRouter as Router, Switch, Route, Redirect,Link } from 'react-router-dom'
  2. import Detail from '../src/page/detail'
  3. import List from '../src/page/list'
  4. import Index from '../src/page/home/index'
  5. const menusList = [
  6. {
  7. name: '首页',
  8. path: '/index'
  9. },
  10. {
  11. name: '列表',
  12. path: '/list'
  13. },
  14. {
  15. name: '详情',
  16. path: '/detail'
  17. },
  18. ]
  19. const index = () => {
  20. return <div >
  21. <div >
  22. <Router >
  23. <div>{
  24. /* link 路由跳转 */
  25. menusList.map(router=><Link key={router.path} to={ router.path } >
  26. <span className="routerLink" >{router.name}</span>
  27. </Link>)
  28. }</div>
  29. <Switch>
  30. <Route path={'/index'} component={Index} ></Route>
  31. <Route path={'/list'} component={List} ></Route>
  32. <Route path={'/detail'} component={Detail} ></Route>
  33. {/* 路由不匹配,重定向到/index */}
  34. <Redirect from='/*' to='/index' />
  35. </Switch>
  36. </Router>
  37. </div>
  38. </div>
  39. }

React-router - 图1

二、单页应用实现原理

单页面应用路由实现原理是,切换 url,监听 url 变化,从而渲染不同的页面组件
主要的方式有history模式和hash模式
下面介绍 原生 JS API

1. history 模式原理

改变路由

  • history.pushState(state, title, path)
    • state:一个与指定网址相关的状态对象,·popstate 事件触发时,该对象会传入回调函数。如果不需要可填 null
    • title:新页面的标题,但是所有浏览器目前都忽略这个值,可填 null
    • path:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个地址
  • history.replaceState(state, title, path)

    • 参数和 pushState一样
    • 这个方法会修改当前 history对象的记录,history.length不会改变

      监听路由

  • popState事件

    1. window.addEventListener('popstate',function(e){
    2. /* 监听改变 */
    3. })

    同一个文档的 history对象出现变化时,就会触发 popState事件。
    history.pushState可以使浏览器地址改变,但无需刷新页面。
    注意:**history.pushState()****history.replaceState()**不会触发**popState**事件
    popState事件只会在浏览器某些行为下触发,比如点击后退、前进 或者 调用 history.back()、history.forward()、history.go()方法

    2. hash 模式原理

    改变路由

  • window.location.hash

    • 通过这个属性,获取 或者 设置hash

      监听路由

      onhashchange事件
      1. window.addEventListener('hashchange', function(e) {
      2. /* 监听改变 */
      3. })

      三、原生 JS 实现前端路由

      目录结构 |-index.html |-server.js

  1. const http = require('http')
  2. const path = require('path')
  3. const fs = require('fs')
  4. const server = http.createServer((req, res) => {
  5. const source = fs.readFileSync(path.resolve(__dirname, './index.html'))
  6. res.end(source)
  7. })
  8. server.listen(3000, () => {
  9. console.log('server is listening on port 3000');
  10. })

还有许多地方可以优化的

  1. 优化 history 重写函数
    1. 多级嵌套路由渲染时,删除局部标签,而不是div#root的所有子元素
    2. router[*]不存在时,给予一个临时函数
    3. router 封装成类,提供方法注册路由路径 和 渲染组件,而不是直接是对象
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. <style>
  9. body {
  10. margin: 0
  11. }
  12. </style>
  13. </head>
  14. <body>
  15. <div id="root"></div>
  16. <script>
  17. (function () {
  18. // 重写 history 的方法
  19. const rawPushState = history.pushState
  20. history.pushState = function (data, unused, url) {
  21. document.getElementById('root').innerHTML = ''
  22. router[url] ? router[url]() : router['*']()
  23. rawPushState.call(this, data, unused, url)
  24. }
  25. const rawReplaceState = history.replaceState
  26. history.replaceState = function (data, unused, url) {
  27. document.getElementById('root').innerHTML = ''
  28. router[url] ? router[url]() : router['*']()
  29. rawReplaceState.call(this, data, unused, url)
  30. }
  31. })()
  32. const pathname = location.pathname
  33. // 不同路由对于的页面
  34. const router = {
  35. '/': () => {
  36. const fragment = document.createDocumentFragment()
  37. fragment.appendChild(document.createTextNode('首页'))
  38. const button = document.createElement('button')
  39. button.innerText = '跳转'
  40. button.onclick = () => {
  41. history.pushState({}, '', '/work')
  42. }
  43. fragment.appendChild(button)
  44. document.getElementById('root').appendChild(fragment)
  45. },
  46. '/work': () => {
  47. document.getElementById('root').innerHTML = 'work'
  48. },
  49. '/user': () => {
  50. document.getElementById('root').innerHTML = 'user'
  51. },
  52. '*': () => {
  53. document.getElementById('root').innerHTML = '404'
  54. }
  55. }
  56. // 首次渲染,渲染对于页面的资源
  57. router[pathname] ? router[pathname]() : router['*']()
  58. </script>
  59. </body>
  60. </html>

参考文章

「源码解析 」这一次彻底弄懂react-router路由原理