日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > HTML >内容正文

HTML

监听router_深入揭秘前端路由本质,手写 mini-router

發(fā)布時(shí)間:2024/7/23 HTML 58 豆豆
生活随笔 收集整理的這篇文章主要介紹了 监听router_深入揭秘前端路由本质,手写 mini-router 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

前端路由一直是一個(gè)很經(jīng)典的話題,不管是日常的使用還是面試中都會(huì)經(jīng)常遇到。本文通過(guò)實(shí)現(xiàn)一個(gè)簡(jiǎn)單版的 react-router 來(lái)一起揭開路由的神秘面紗。

通過(guò)本文,你可以學(xué)習(xí)到:

  • 前端路由本質(zhì)上是什么。
  • 前端路由里的一些坑和注意點(diǎn)。
  • hash 路由和 history 路由的區(qū)別。
  • Router 組件和 Route 組件分別是做什么的。

路由的本質(zhì)

簡(jiǎn)單來(lái)說(shuō),瀏覽器端路由其實(shí)并不是真實(shí)的網(wǎng)頁(yè)跳轉(zhuǎn)(和服務(wù)器沒有任何交互),而是純粹在瀏覽器端發(fā)生的一系列行為,本質(zhì)上來(lái)說(shuō)前端路由就是:

對(duì) url 進(jìn)行改變和監(jiān)聽,來(lái)讓某個(gè) dom 節(jié)點(diǎn)顯示對(duì)應(yīng)的視圖。

僅此而已。新手不要被路由這個(gè)概念給嚇到。

路由的區(qū)別

一般來(lái)說(shuō),瀏覽器端的路由分為兩種:

  • hash 路由,特征是 url 后面會(huì)有 # 號(hào),如 baidu.com/#foo/bar/baz。
  • history 路由,url 和普通路徑?jīng)]有差異。如 baidu.com/foo/bar/baz。
  • 我們已經(jīng)講過(guò)了路由的本質(zhì),那么實(shí)際上只需要搞清楚兩種路由分別是如何 改變,并且組件是如何監(jiān)聽并完成視圖的展示,一切就真相大白了。

    不賣關(guān)子,先分別談?wù)剝煞N路由用什么樣的 api 實(shí)現(xiàn)前端路由:

    hash

    通過(guò) location.hash = 'foo' 這樣的語(yǔ)法來(lái)改變,路徑就會(huì)由 baidu.com 變更為 baidu.com/#foo。

    通過(guò) window.addEventListener('hashchange') 這個(gè)事件,就可以監(jiān)聽到 hash 值的變化。

    history

    其實(shí)是用了 history.pushState 這個(gè) API 語(yǔ)法改變,它的語(yǔ)法乍一看比較怪異,先看下 mdn 文檔里對(duì)它的定義:

    history.pushState(state, title[, url])

    其中 state 代表狀態(tài)對(duì)象,這讓我們可以給每個(gè)路由記錄創(chuàng)建自己的狀態(tài),并且它還會(huì)序列化后保存在用戶的磁盤上,以便用戶重新啟動(dòng)瀏覽器后可以將其還原。

    title 當(dāng)前沒啥用。

    url 在路由中最重要的 url 參數(shù)反而是個(gè)可選參數(shù),放在了最后一位。

    通過(guò) history.pushState({}, '', foo),可以讓 baidu.com 變化為baidu.com/foo。

    為什么路徑更新后,瀏覽器頁(yè)面不會(huì)重新加載?

    這里我們需要思考一個(gè)問(wèn)題,平常通過(guò) location.href = 'baidu.com/foo' 這種方式來(lái)跳轉(zhuǎn),是會(huì)讓瀏覽器重新加載頁(yè)面并且請(qǐng)求服務(wù)器的,但是 history.pushState 的神奇之處就在于它可以讓 url 改變,但是不重新加載頁(yè)面,完全由用戶決定如何處理這次 url 改變。

    因此,這種方式的前端路由必須在支持 histroy API 的瀏覽器上才可以使用。

    為什么刷新后會(huì) 404?

    本質(zhì)上是因?yàn)樗⑿乱院笫菐е?baidu.com/foo 這個(gè)頁(yè)面去請(qǐng)求服務(wù)端資源的,但是服務(wù)端并沒有對(duì)這個(gè)路徑進(jìn)行任何的映射處理,當(dāng)然會(huì)返回 404,處理方式是讓服務(wù)端對(duì)于"不認(rèn)識(shí)"的頁(yè)面,返回 index.html,這樣這個(gè)包含了前端路由相關(guān)js代碼的首頁(yè),就會(huì)加載你的前端路由配置表,并且此時(shí)雖然服務(wù)端給你的文件是首頁(yè)文件,但是你的 url 上是 baidu.com/foo,前端路由就會(huì)加載 /foo 這個(gè)路徑相對(duì)應(yīng)的視圖,完美的解決了 404 問(wèn)題。

    history 路由的監(jiān)聽也有點(diǎn)坑,瀏覽器提供了window.addEventListener('popstate') 事件,但是它只能監(jiān)聽到瀏覽器回退和前進(jìn)所產(chǎn)生的路由變化,對(duì)于主動(dòng)的 pushState 卻監(jiān)聽不到。解決方案當(dāng)然有,下文實(shí)現(xiàn) react-router 的時(shí)候再細(xì)講~

    實(shí)現(xiàn) react-mini-router

    本文實(shí)現(xiàn)的 react-router 基于 history 版本,用最小化的代碼還原路由的主要功能,所以不會(huì)有正則匹配或者嵌套子路由等高階特性,回歸本心,從零到一實(shí)現(xiàn)最簡(jiǎn)化的版本。

    實(shí)現(xiàn) history

    對(duì)于 history 難用的官方 API,我們專門抽出一個(gè)小文件對(duì)它進(jìn)行一層封裝,對(duì)外提供:

  • history.push。
  • history.listen。
  • 這兩個(gè) API,減輕用戶的心智負(fù)擔(dān)。

    我們利用觀察者模式封裝了一個(gè)簡(jiǎn)單的 listen API,讓用戶可以監(jiān)聽到history.push 所產(chǎn)生的路徑改變。

    //?存儲(chǔ)?history.listen?的回調(diào)函數(shù)let?listeners:?Listener[]?=?[];function?listen(fn:?Listener)?{??listeners.push(fn);??return?function()?{????listeners?=?listeners.filter(listener?=>?listener?!==?fn);??};}

    這樣外部就可以通過(guò):

    history.listen(location?=>?{??console.log('changed',?location);});

    這樣的方式感知到路由的變化了,并且在 location 中,我們還提供了 state、pathname、search 等關(guān)鍵的信息。

    實(shí)現(xiàn)改變路徑的核心方法 push 也很簡(jiǎn)單:

    function?push(to:?string,?state?:?State)?{??//?解析用戶傳入的?url??//?分解成?pathname、search?等信息??location?=?getNextLocation(to,?state);??//?調(diào)用原生?history?的方法改變路由??window.history.pushState(state,?'',?to);??//?執(zhí)行用戶傳入的監(jiān)聽函數(shù)??listeners.forEach(fn?=>?fn(location));}

    在 history.push('foo') 的時(shí)候,本質(zhì)上就是調(diào)用了 window.history.pushState 去改變路徑,并且通知 listen 所掛載的回調(diào)函數(shù)去執(zhí)行。

    當(dāng)然,別忘了用戶點(diǎn)擊瀏覽器后退前進(jìn)按鈕的行為,也需要用 popstate 這個(gè)事件來(lái)監(jiān)聽,并且執(zhí)行同樣的處理:

    //?用于處理瀏覽器前進(jìn)后退操作window.addEventListener('popstate',?()?=>?{??location?=?getLocation();??listeners.forEach(fn?=>?fn(location));});

    接下來(lái)我們需要實(shí)現(xiàn) Router 和 Route 組件,你就會(huì)看到它們是如何和這個(gè)簡(jiǎn)單的 history 庫(kù)結(jié)合使用了。

    實(shí)現(xiàn) Router

    Router 的核心原理就是通過(guò) Provider 把 location 和 history 等路由關(guān)鍵信息傳遞給子組件,并且在路由發(fā)生變化的時(shí)候要讓子組件可以感知到:

    import?React,?{?useState,?useEffect,?ReactNode?}?from?'react';import?{?history,?Location?}?from?'./history';interface?RouterContextProps?{??history:?typeof?history;??location:?Location;}export?const?RouterContext?=?React.createContext(??null,);export?const?Router:?React.FC?=?({?children?})?=>?{??const?[location,?setLocation]?=?useState(history.location);??//?初始化的時(shí)候?訂閱?history?的變化??//?一旦路由發(fā)生改變?就會(huì)通知使用了?useContext(RouterContext)?的子組件去重新渲染??useEffect(()?=>?{????const?unlisten?=?history.listen(location?=>?{??????setLocation(location);????});????return?unlisten;??},?[]);??return?(??????????{children}??????);};

    注意看注釋的部分,我們?cè)诮M件初始化的時(shí)候利用 history.listen 監(jiān)聽了路由的變化,一旦路由發(fā)生改變,就會(huì)調(diào)用 setLocation 去更新 location 并且通過(guò)Provider 傳遞給子組件。

    并且這一步也會(huì)觸發(fā) Provider 的 value 值的變化,通知所有用 useContext 訂閱了history 和 location 的子組件去重新 render。

    實(shí)現(xiàn) Route

    Route 組件接受 path 和 children 兩個(gè) prop,本質(zhì)上就決定了在某個(gè)路徑下需要渲染什么組件,我們又可以通過(guò) Router 的 Provider 傳遞下來(lái)的 location 信息拿到當(dāng)前路徑,所以這個(gè)組件需要做的就是判斷當(dāng)前的路徑是否匹配,渲染對(duì)應(yīng)組件。

    import?{?ReactNode?}?from?'react';import?{?useLocation?}?from?'./hooks';interface?RouteProps?{??path:?string;??children:?ReactNode;}export?const?Route?=?({?path,?children?}:?RouteProps)?=>?{??const?{?pathname?}?=?useLocation();??const?matched?=?path?===?pathname;??if?(matched)?{????return?children;??}??return?null;};

    這里的實(shí)現(xiàn)比較簡(jiǎn)單,路徑直接用了全等,實(shí)際上真正的實(shí)現(xiàn)考慮的情況比較復(fù)雜,使用了 path-to-regexp 這個(gè)庫(kù)去處理動(dòng)態(tài)路由等情況,但是核心原理其實(shí)就是這么簡(jiǎn)單。

    實(shí)現(xiàn) useLocation、useHistory

    這里就很簡(jiǎn)單了,利用 useContext 簡(jiǎn)單封裝一層,拿到 Router 傳遞下來(lái)的history 和 location 即可。

    import?{?useContext?}?from?'react';import?{?RouterContext?}?from?'./Router';export?const?useHistory?=?()?=>?{??return?useContext(RouterContext)!.history;};export?const?useLocation?=?()?=>?{??return?useContext(RouterContext)!.location;};

    實(shí)現(xiàn)驗(yàn)證 demo

    至此為止,以下的路由 demo 就可以跑通了:

    import?React,?{?useEffect?}?from?'react';import?{?Router,?Route,?useHistory?}?from?'react-mini-router';const?Foo?=?()?=>?'foo';const?Bar?=?()?=>?'bar';const?Links?=?()?=>?{??const?history?=?useHistory();??const?go?=?(path:?string)?=>?{????const?state?=?{?name:?path?};????history.push(path,?state);??};??return?(???????????go('foo')}>foo???????go('bar')}>bar??????);};export?default?()?=>?{??return?(????

    結(jié)語(yǔ)

    通過(guò)本文的學(xué)習(xí),相信小伙伴們已經(jīng)搞清楚了前端路由的原理,其實(shí)它只是對(duì)瀏覽器提供 API 的一個(gè)封裝,以及在框架層去聯(lián)動(dòng)做對(duì)應(yīng)的渲染,換個(gè)框架 vue-router 也是類似的原理。

    喜歡的老鐵,加個(gè)關(guān)注!今后會(huì)分享更多的前端干貨,歡迎點(diǎn)贊轉(zhuǎn)發(fā)關(guān)注[比心]

    本文源碼地址:https://github.com/sl1673495/react-mini-router

    總結(jié)

    以上是生活随笔為你收集整理的监听router_深入揭秘前端路由本质,手写 mini-router的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。