<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          瀏覽器歷史記錄是如何留下來的?

          共 7484字,需瀏覽 15分鐘

           ·

          2023-08-17 16:35

          今天給朋友們分享的是我們編程導(dǎo)航知識(shí)星球的嘉賓 - 前端大佬神光的關(guān)于 圖解 history api 和 React Router 實(shí)現(xiàn)原理的文章,希望能對(duì)大家有所幫助。

          Router 是開發(fā) React 應(yīng)用的必備功能,那 React Router 是怎么實(shí)現(xiàn)的呢?

          今天我們就來讀一下 React Router 的源碼吧!

          首先,我們來學(xué)一下 History API,這是基礎(chǔ)。

          什么是 history 呢?

          就是這個(gè)東西:

          2cfba9ddd30894fcb88d737d77c194ef.webp

          我打開了一個(gè)新的標(biāo)簽頁、然后訪問 baidu.com、sougou.com、taobao.com。

          長按后退按鈕,就會(huì)列出歷史記錄,這就是 history。

          現(xiàn)在在這里:

          5f00d1f6775d56142aec591c2a561ba1.webp

          history.length 是 5

          3b8b49fd15182401d40ac495da10c212.webp

          點(diǎn)擊兩次后退按鈕,或者執(zhí)行兩次 history.back()

          3cfbb4a757bdee27ea243b0df08675ae.webp

          就會(huì)回到這里:

          b6811302a3ec487adfd2118e70082e48.webp

          這時(shí)候 history.length 依然是 5

          25a91b2afc73bd63d5cc5dbefa38e623.webp

          因?yàn)榍昂蟮?history 都還保留著:

          06525c9b7e2ac2fc581391ba8b7398e8.webp 2887b788ae33b5fb1f26ec3cdc4245b2.webp

          除了用 history.back、history.forward 在 history 之間切換外,還可以用 history.go

          參數(shù)值是 delta:

          history.go(0) 是刷新當(dāng)前頁面。

          history.go(1) 是前進(jìn)一個(gè),相當(dāng)于 history.forward()

          history.go(-1) 是后退一個(gè),相當(dāng)于 history.back()

          當(dāng)然,你還可以 history.go(-2)、histroy.go(3) 這種。

          fa7304b195f2d710035945e4b7cf5272.webp

          比如當(dāng)我執(zhí)行 history.go(-2) 的時(shí)候,能直接從 taobao.com 跳到 sogou.com

          b019adad28467efa0ead20982e1a33d7.webp

          你還可以通過 history.replaceState 來替換當(dāng)前 history:

          9911e6ccbe5c143d70c96f0abe143897.webp
                
                history.replaceState({aaa:1}, '''https://www.baidu.com?wd=光')

          第一個(gè)參數(shù)是 state、第二個(gè)參數(shù)是 title,第三個(gè)是替換的 url。

          不過第二個(gè)參數(shù)基本都不支持,state 倒是能拿到。

          比如我在 https://www.baidu.com 那頁 replaceState 為一個(gè)新的 url:

          51af7c26fbdc0579e835996c60cf189a.webp

          前后 history 都沒變,只有當(dāng)前的變了:

          4ce4cff96b7c56daa86939212305efc8.webp

          也就是這樣:

          578c30511480bb471325f8b7a5b345c6.webp

          當(dāng)然,你還可以用 history.pushState 來添加一個(gè)新的 history:

                
                history.pushState({bbb:1}, '''https://www.baidu.com?wd=東');
          bcd529b4d35ddfe3294af9a3e15a9c38.webp

          但有個(gè)現(xiàn)象,就是之后的 history 都沒了:

          793cfb441fe71d348c62b2db386982b2.webp 0bc661373d8b221e8596ac46b4982243.webp

          也就是變成了這樣:

          d4d344ba5bba417e8eac512b67818148.webp

          為什么呢?

          因?yàn)槟闶?history.pushState 的時(shí)候,和后面的 history 沖突了,也就是分叉了:

          631f6e3320b5a701aa5a1978ef981869.webp

          這時(shí)候自然只能保留一個(gè)分支,也就是最新的那個(gè)。

          這時(shí)候 history.length 就是 3 了。

          73171fed32c5c618cc2b574f92007da3.webp

          至此,history 的 length、go、back、forward、pushState、replaceState、state 這些 api 我們就用了一遍了。

          還有個(gè) history.scrollRestoration 是用來保留滾動(dòng)位置的:

          有兩個(gè)值 auto、manual,默認(rèn)是 auto,也就是會(huì)自動(dòng)定位到上次滾動(dòng)位置,設(shè)置為 manual 就不會(huì)了。

          比如我訪問百度到了這個(gè)位置:

          9e869595d5c839cb2a1a0870adec20fe.webp

          打開個(gè)新頁面,再退回來:

          ffeeb95ade864a5a44406268b80e5a6c.webp


          依然是在上次滾動(dòng)到的位置。

          這是因?yàn)樗?history.scrollRestoration 是 auto

          80b631057dfaf6dd5d603ef9064e74b3.webp

          我們把它設(shè)置為 manual 試試看:

          03b29776d0d34251d0db0d458b86543e.webp

          49e85379cbc5ab3435f66a014f386270.webp

          這時(shí)候就算滾動(dòng)到了底部,再切回來也會(huì)回到最上面。

          此外,與 history 相關(guān)的還有個(gè)事件:popstate

          當(dāng)你在 history 中導(dǎo)航時(shí),popstate 就會(huì)觸發(fā),比如 history.forwad、histroy.back、history.go。

          但是 history.pushState、history.replaceState 這種并不會(huì)觸發(fā) popstate。

          我們測試下:

          7fe406f2cee311032fb3d4175a92456c.webp
                
                history.pushState({aaa:1}, '''https://www.baidu.com?#/aaa');

          history.pushState({bbb:2}, '''https://www.baidu.com?#/bbb');

          我在 www.baidu.com 這個(gè)頁面 pushState 添加了兩個(gè) history。

          加上導(dǎo)航頁一共 4 個(gè):

          57d023974a6775f1cdf62b9722d13a13.webp

          然后我監(jiān)聽 popstate 事件:

          0c99bd8190a87d6d4254932f2523b4ad.webp
                
                window.addEventListener('popstate', event => {console.log(event)});

          執(zhí)行 history.back 和 history.forward 都會(huì)觸發(fā) popstate 事件:

          af396bf88037c9104f903766ca97b696.webp

          事件包含 state,也可以從 target.location 拿到當(dāng)前 url

          08994130c7dcba4b76e925c134408ab7.webp

          但是當(dāng)你 history.pushState、history.replaceState 并不會(huì)觸發(fā)它:

          8c4a2a5ef088051df59345b0cf92bddd.webp

          也就是說添加、修改 history 不會(huì)觸發(fā) popstate,只有在 state 之間導(dǎo)航才會(huì)觸發(fā)。

          綜上,history api 和 popstate 事件我們都過了一遍。

          基于這些就可以實(shí)現(xiàn) React Router。

          有的同學(xué)說,不是還有個(gè) hashchange 事件么?

          確實(shí),那個(gè)就是監(jiān)聽 hash 變化的。

          b76742a9b6969e6de6b9e84be66b3b14.webp 1749a55d5a3a3c14b353419d2a62c7eb.webp

          基于它也可以實(shí)現(xiàn) router,但很明顯,hashchange 只能監(jiān)聽 hash 的變化,而 popstate 不只是 hash 變化,功能更多。

          所以用 popstate 事件就足夠了。

          其實(shí)在 react router 里,就只用到了 popstate 事件,沒用到 hashchange 事件:

          4ff91e94bcccfa0627bc28ddfb228d9e.webp 8bbee4556ae292e213a4c07a690ebf4d.webp

          接下來我們就具體來看下 React Router 是怎么實(shí)現(xiàn)的吧。

          創(chuàng)建個(gè) react 項(xiàng)目:

                
                npx create-react-app react-router-test
          6d66a44f2925b102b6f1e3b021ac8792.webp

          安裝 react-router 的包:

                
                npm install react-router-dom

          然后在 index.js 寫如下代碼:

                
                import React from 'react';
          import ReactDOM from 'react-dom/client';
          import {
            createBrowserRouter,
            Link,
            Outlet,
            RouterProvider,
          from "react-router-dom";

          function Aaa() {
            return <div>
              <p>aaa</p>
              <Link to={'/bbb/111'}>to bbb</Link>
              <br/>
              <Link to={'/ccc'}>to ccc</Link>
              <br/>
              <Outlet/>
            </div>
          ;
          }

          function Bbb() {
            return 'bbb';
          }

          function Ccc() {
            return 'ccc';
          }

          function ErrorPage() {
            return 'error';
          }

          const routes = [
            {
              path"/",
              element<Aaa/>,
              errorElement<ErrorPage/>,
              children: [
                {
                  path"bbb/:id",
                  element<Bbb />,
                },
                {
                  path"ccc",
                  element<Ccc />,
                }    
              ],
            }
          ];
          const router = createBrowserRouter(routes);

          const root = ReactDOM.createRoot(document.getElementById('root'));
          root.render(<RouterProvider router={router} />);

          通過 react-router-dom 包的 createBrowserRouter 創(chuàng)建 router,傳入 routes 配置。

          然后把 router 傳入 RouterProvider。

          有一個(gè)根路由 /、兩個(gè)子路由 /bbb/:id 和 /ccc

          把開發(fā)服務(wù)跑起來:

                
                npm run start

          測試下:

          45073e27e9ace00fd0b92a64fb62db81.webp

          子路由對(duì)應(yīng)的組件在  處渲染。

          當(dāng)沒有對(duì)應(yīng)路由的時(shí)候,會(huì)返回錯(cuò)誤頁面:

          ebb294e3c6be95a2ef07cadb13465a55.webp

          那它是怎么實(shí)現(xiàn)的呢?

          我們斷點(diǎn)調(diào)試下:

          5c793811f67386ea7652e7aae746c619.webp

          創(chuàng)建調(diào)試配置文件 launch.json,然后創(chuàng)建 chrome 類型的調(diào)試配置:

          c862e9b55f7ddf39a0615eb755ee4f8e.webp

          在 createBrowserRouter 的地方打個(gè)斷點(diǎn):

          39b21721026ac16a387406073d06a0f9.webp

          點(diǎn)擊 debug:

          1556b0eae3a982e812d9725eb99ee149.webp

          代碼會(huì)在這里斷住:

          f3815609c0de1a1b99227e4032c44c92.webp

          點(diǎn)擊 step into 進(jìn)入函數(shù)內(nèi)部:

          它調(diào)用了 createRouter:

          886d91b99840a3f3fca5ed6524bd71ed.webp

          這里傳入了 history。

          這個(gè)不是原生的 history api,而是包裝了一層之后的:

          關(guān)注 listen、push、replace、go 這 4 個(gè)方法就好了:

          509d212605f8629523634695de8c4056.webp f6375cd953e9feadfb4ff9e12f46def6.webp

          listen 就是監(jiān)聽 popstate 事件。

          而 push、replace、go 都是對(duì) history 的 api 的封裝:

          e8da8bdfc0bc9c24192c93a4aa3b2ab1.webp 08219851393983a1491dcf42c1c263b3.webp

          此外,history 還封裝了 location 屬性,不用自己從 window 取了。

          然后 createRouter 里會(huì)對(duì) routes 配置和當(dāng)前的 location 做一次 match:

          78dac80cc695058477396aa71fc67b92.webp 7fbe37f0caf04e290cb1c9b8117f3200.webp

          matchRoutes 會(huì)把嵌套路由拍平,然后和 location 匹配:

          a65c198a705f04b47e7100b29d89accd.webp

          然后就匹配到了要渲染的組件以及它包含的子路由:

          3313fd79f47a35e18e78571592a688f4.webp

          這樣當(dāng)組件樹渲染的時(shí)候,就知道渲染什么組件了:

          2f5674a2dcb088ab28f25e8601a39b7c.webp

          就是把 match 的這個(gè)結(jié)果渲染出來。

          這樣就完成了路由對(duì)應(yīng)的組件渲染:

          1460dcef01df59d81f44193b79e7f9ed.webp

          也就是這樣的流程:

          388337d6417ead4078d5a381c40640e6.webp

          當(dāng)點(diǎn)擊 link 切換路由的時(shí)候:

          f16dc2f3c314ec62b4fc1b29677e9cf7.webp

          會(huì)執(zhí)行 navigate 方法:

          9ff27ca400c201803b0afc1b381f7ad0.webp 7f098cdeda8eee759ce6865ef0ebbfb3.webp

          然后又到了 matchRoutes 的流程:

          107622f0f251ec7514aaf947fdf0a4a8.webp

          match 完會(huì) pushState 或者 replaceState 修改 history,然后更新 state:

          c1b8365d346c78a53cb9c6a6d874c634.webp

          然后觸發(fā)了 setState,組件樹會(huì)重新渲染:

          a230e5399ff9d89ec099dac7082b1a35.webp 80c228dd7e0d509c7b6c1aeb6eb527b9.webp

          也就是這樣的流程:

          5d6e1e3b2dacf4060684a9c9e069c658.webp

          router.navigate 會(huì)傳入新的 location,然后和 routes 做 match,找到匹配的路由。

          之后會(huì) pushState 修改 history,并且觸發(fā) react 的 setState 來重新渲染,重新渲染的時(shí)候通過 renderMatches 把當(dāng)前 match 的組件渲染出來。

          而渲染到 Outlet 的時(shí)候,會(huì)從 context 中取出當(dāng)前需要渲染的組件來渲染:

          d2b4c16c2e23261951964ff5fe704ee0.webp e05f7fa45ff3b7a89b5dbe8c35521beb.webp

          這就是 router 初次渲染和點(diǎn)擊 link 時(shí)的渲染流程。

          那點(diǎn)擊前進(jìn)后退按鈕的時(shí)候呢?

          這個(gè)就是監(jiān)聽 popstate,然后也做一次 navigate 就好了:

          62c7055ce86df0a452d61c6f65d704fe.webp 9c4f1dd775e6ea35f95c971b256ad8fb.webp 5376dcf630b06981df9ba1f1285ed83c.webp

          后續(xù)流程一樣。

          277f712531c9821c7155901d54b1c432.webp

          回過頭來,其實(shí) react router 的 routes 其實(shí)支持這兩種配置方式:

          4029e25602cbe73598a1102fa4105a8f.webp 2d70ae1d49e0335fc12a6d85b43a9604.webp

          效果一樣。

          看下源碼就知道為什么了:

          首先,這個(gè) Route 組件就是個(gè)空組件,啥也沒:

          6a65fe5e60b81019300f032fe12a4b76.webp

          而 Routes 組件里會(huì)從把所有子組件的參數(shù)取出來,變成一個(gè)個(gè) route 配置:

          2d9e302e1d7bc6e12604d393f3cdd363.webp b995267e1f753adfd120ccab51a4c75f.webp

          結(jié)果不就是和對(duì)象的配置方式一樣么?

          總結(jié)

          我們學(xué)習(xí)了 history api 和 React Router 的實(shí)現(xiàn)原理。

          history api 有這些:

          • length:history 的條數(shù)
          • forward:前進(jìn)一個(gè)
          • back:后退一個(gè)
          • go:前進(jìn)或者后退 n 個(gè)
          • pushState:添加一個(gè) history
          • replaceState:替換當(dāng)前 history
          • scrollRestoration:保存 scroll 位置,取值為 auto 或者 manual,manual 的話就要自己設(shè)置 scroll 位置了

          而且還有 popstate 事件可以監(jiān)聽到 history.go、history.back、history.forward 的導(dǎo)航,拿到最新的 location。

          這里要注意 pushState、replaceState 并不能觸發(fā) popstate 事件。也就是 history 之間導(dǎo)航(go、back、forward)可以觸發(fā) popstate,而修改 history (push、replace)不能觸發(fā)。

          React Router 就是基于這些 history api 實(shí)現(xiàn)的。

          首次渲染的時(shí)候,會(huì)根據(jù) location 和配置的 routes 做匹配,渲染匹配的組件。

          之后點(diǎn)擊 link 鏈接也會(huì)進(jìn)行 location 和 routes 的匹配,然后 history.pushState 修改 history,之后通過 react 的 setState 觸發(fā)重新渲染。

          前進(jìn)后退的時(shí)候,也就是執(zhí)行 history.go、history.back、history.forward 的時(shí)候,會(huì)觸發(fā) popstate,這時(shí)候也是同樣的處理,location 和 routes 的匹配,然后 history.pushState 修改 history,之后通過 react 的 setState 觸發(fā)重新渲染。

          渲染時(shí)會(huì)用到 Outlet組件 渲染子路由,用到 useXxx 來取一些匹配信息,這些都是通過 context 來傳遞的。

          這就是 React Router 的實(shí)現(xiàn)原理,它和 history api 是密不可分的。


          最后,歡迎學(xué)編程的朋友們加入魚皮的  編程知識(shí)星球  ,和上萬名學(xué)編程的同學(xué)共享知識(shí)、交流進(jìn)步,學(xué)習(xí)原創(chuàng)項(xiàng)目并享有答疑指導(dǎo)服務(wù)。

          往期推薦

          我的學(xué)習(xí)小圈子

          實(shí)習(xí)不少于 3 個(gè)月,以后年薪就能過 30 w?

          接入支付服務(wù),一定要知道的小知識(shí)

          魚皮原創(chuàng)實(shí)戰(zhàn)項(xiàng)目教程【系列】

          DNS 解析一個(gè)地址,會(huì)返回多個(gè) IP 嗎?

          老子寫個(gè)代碼而已,憑什么還要我寫文檔?

          瀏覽 65
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本一区二区电影久久精品 | 天天综合~91入口 | 91精品国产99久久久久久 | 青娱乐日韩 | 欧美大香蕉在线 |