十六、「深入React源码」--- React路由原理

253 阅读2分钟

经过前一段时间的学习,我们已经可以掌握React基础方面的代码实现逻辑。那么接下来的时间,让我们一起学习另一个模块:react 路由 吧!

React路由原理

一、 HashRouter

根据路径的不同,页面渲染不同的内容。

下面写个简单的小例子:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>hash</title>
</head>

<body>
  <div id="root"></div>
  <ul>
    <li> 
      <a href='#/a'> aaa </a>
    </li>
    <li>
      <a href='#/b'> bbb </a>
    </li>
  </ul>

  <script>
    window.addEventListener('hashchange', () => {
      console.log(window.location.hash);
      let pathname = window.location.hash.slice(1) // #/a ==> /a
      root.innerHTML = pathname
    })
  </script>
  
</body>
</html>

image.png

我们通过切换路由,去控制页面的内容。

二、 BrowserRouter

本质是利用h5的api实现路由切换。

思考运行以下代码,页面最终展示的结果是什么?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="root"></div>
    <script>
      // h5提供的history对象
      var historyObj = window.history;

      // 自定义属性:监听路径改变事件 表示将当前的状态变更了,弹出了
      window.onpushstate = (event) => {
          console.log(event.type,event.detail.state);
          root.innerHTML = window.location.pathname;
      }

      // 调用go back forward会触发onpopstate
      window.addEventListener('popstate', (event) => {
        // state 用来存放数据的自定义属性
          console.log(event.type,event.state);
          root.innerHTML = window.location.pathname;
      });

      (function (historyObj) {
        let oldPushState = history.pushState;//缓存原生的pushState
        historyObj.pushState = function (state, title, pathname) {
            let result = oldPushState.apply(history, arguments);
            if (typeof window.onpushstate === 'function') {
                window.onpushstate(new CustomEvent('pushstate',{detail:{pathname,  state}}));
            }
            return result;
        }
      })(historyObj);

      let oldHistoryLength = historyObj.length;

      setTimeout(() => {
        historyObj.pushState({ page: 1 }, { title: 'page1' }, '/page1');//page1
        console.log(historyObj.length-oldHistoryLength);
      }, 1000);

      setTimeout(() => {
        historyObj.pushState({ page: 2 }, { title: 'page2' }, '/page2');//page2
        console.log(historyObj.length-oldHistoryLength);
      }, 2000);

      setTimeout(() => {
        historyObj.pushState({ page: 3 }, { title: 'page3' }, '/page3');//page3
        console.log(historyObj.length-oldHistoryLength);
      }, 3000);

      setTimeout(() => {
        historyObj.back();//historyObj.go(-1);//page2
        setTimeout(()=>console.log(historyObj.length-oldHistoryLength),100);
      }, 4000);

      setTimeout(() => {
        historyObj.pushState({ page:4 }, { title: 'page4' }, '/page4');//page4
        console.log(historyObj.length-oldHistoryLength);
      }, 5000);
      
      setTimeout(() => {
        historyObj.go(1);
        console.log(historyObj.length-oldHistoryLength);//page4
      }, 6000);
    </script>
</body>
</html>

运行过程及结果:

操作:            push1 --> push2 --> push3 --> back --> push4 --> go1

页面展示:        page1 --> page2 --> page3 --> page2 --> page4 --> page4

history栈长度:   1         2         3         3         3         3

1. history

html5规范给我们提供了一个history接口(js原生对象)。history的api:history.pushState() 放置/添加状态、history.replaceState()替换当前状态。history的事件:window.onpopstate

可以理解为浏览器内部维护了一个history栈结构

  1. 我们调用history.pushState()方法添加一个条目,包含两个属性:pathname路径、state状态。栈中指针初始指向栈顶,每次调用pushState()方法,指针就会 指向新的条目
  2. 调用history.replaceState()方法,会把最后入栈的条目更新为本次新加的条目。
  3. 当操作浏览器前进、后退以及执行history.forward()history.back()history.go等方法,都会触发onpopstate事件,并且指针指向当前的条目。也就是说,当栈指针发生移动,就会触发onpopstate事件。
1-1. pushState

history.pushState(stateObject, title, url),包括三个参数:

  1. 第一个参数用于存储该url对应的状态对象,该对象可在onpopstate事件中获取,也可在history对象中获取
  2. 第二个参数是标题,目前浏览器并未实现
  3. 第三个参数则是设定的url

pushState函数向浏览器的历史堆栈压入一个url为设定值的记录,并改变历史堆栈的当前指针至栈顶

1-2. replaceState
  1. 该接口与pushState参数相同,含义也相同
  2. 唯一的区别在于replaceState是替换浏览器历史堆栈的当前历史记录为设定的url
  3. 需要注意的是replaceState不会改动浏览器历史堆栈的当前指针
1-3. onpopstate
  1. 该事件是window的属性
  2. 该事件会在调用浏览器的前进、后退以及执行history.forwardhistory.back、和history.go触发,因为这些操作有一个共性,即修改了历史堆栈的当前指针
  3. 在不改变 document 的前提下,一旦当前指针改变则会触发onpopstate事件

image.png

当执行backforward方法时,history栈的大小、长度等不会改变,即之前的条目不会弹出栈,改变的只是当前指针的位置。

image.png

若当前指针在history栈的中间位置(非栈顶),此时执行pushState会在指针当前的位置添加此条目,并成为新的栈顶。