前端路由原理和React Router

前端路由原理

前端三大框架 Angular、React、Vue ,它們的路由解決方案 angular/router、react-router、vue-router 都是基于前端路由原理進行封裝實現的,因此將前端路由原理進行了解和掌握是很有必要的。

路由的概念起源于服務端,在以前前后端不分離的時候,由后端來控制路由。但由于后端路由還存在著自己的不足,前端路由才有了發展空間。路由的映射函數通常是進行一些 DOM 的顯示和隱藏操作,當訪問不同的路徑的時候,會顯示不同的頁面組件。前端路由主要有兩種實現方案:Hash和History。

Hash模式

hash模式就是基于 location.hash 來實現的,原理很簡單,location.hash 的值就是 URL 中 # 后面的內容。比如https://www.xxxxxx.com#search的 location.hash 的值為 '#search'

hash 有下面幾個特性:

  • URL 中 hash 值只是客戶端的一種狀態,也就是說當向服務器端發出請求時,hash 部分不會被發送。
  • hash 值的改變,都會在瀏覽器的訪問歷史中增加一個記錄。因此我們能通過瀏覽器的回退、前進按鈕控制hash 的切換。
  • 我們可以使用 hashchange 事件來監聽 hash 的變化。

那么如何來觸發hash改變(hashchange 事件)呢?

  1. 通過 a 標簽,并設置 href 屬性,當用戶點擊這個標簽后,URL 就會發生改變,也就會觸發 hashchange 事件了:
<a href="#search">search</a>
  1. 直接使用 JavaScript來對 loaction.hash 進行賦值,從而改變 URL,觸發 hashchange 事件:
location.hash="#search"

History模式

由于hash模式下使用時都需要加上 #,并不是很美觀。到了 HTML5,又提供了 History API 來實現 URL 的變化。其中做最主要的 API 有以下兩個:history.pushState()history.repalceState()。這兩個 API可以在不進行刷新的情況下,操作瀏覽器的歷史紀錄。唯一不同的是,前者是新增一個歷史記錄,后者是直接替換當前的歷史記錄。代碼格式如下:

window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);

history 有下面幾個特性:

  • pushState 和 repalceState 的標題(title):一般瀏覽器會忽略,最好傳入 null ;
  • 我們可以使用 popstate 事件來監聽 url 的變化;
  • history.pushState() 或 history.replaceState() 不會觸發 popstate 事件,這時我們需要手動觸發頁面渲染;

在對前端路由原理有了基本的掌握后,就可以嘗試去閱讀 vue-router 和 react-router 的源碼實現。下面來看下react-router。


React Router

React Router 中的組件主要分為三類:

  • 路由組件,例如 BrowserRouter和 HashRouter
  • 路由匹配器,例如 Route 和 Switch
  • 導航,例如 Link,NavLink 和 Redirect

下面逐一展開介紹:

路由組件

路由組件分為兩種:BrowserRouter(對應前端路由history 模式) 和 HashRouter(對應前端路由hash 模式),用法一樣,只是 url 展示不同,其中 hash 模式帶有 # 號符。

BrowserRouter:http://localhost:3000/page2
HashRouter:http://localhost:3000/#/page2

// BrowserRouter:
<BrowserRouter>
    <Route path="/page1" exact component={Page1}></Route>
    <Route path="/page2/:id" exact component={Page2}></Route>
</BrowserRouter>
// HashRouter:
<HashRouter>
    <Route path="/page1" exact component={Page1}></Route>
    <Route path="/page2/:id" exact component={Page2}></Route>
</HashRouter>

路由匹配器

  1. Route
    Route 是用于聲明路由映射到應用程序的組件層。
    路由Route的參數介紹如下:
  • path
    string 類型,用來指定路由跳轉路徑,根據 path 和 url 匹配到對應的頁面;如下所示
// 路由配置
<Route path="/page1" exact component={Page1}></Route>
// 頁面訪問 http://localhost:3000/page1
  • exact
    boolean 類型,用來精確匹配路由,如果為 true 則精確匹配,否則為正常匹配;如下所示
// exact = true 時
<Route path="/page1" exact component={Page1}></Route>
// 瀏覽器通過 http://localhost:3000/page1/one 訪問不到 page1 頁面

// exact = fasle 時
<Route path="/page1" exact={false} component={Page1}></Route>
// 瀏覽器通過 http://localhost:3000/page1/one 可以訪問到 page1 頁面
  • sensitive
    boolean 類型,用來設置是否區分路由大小寫,如果為 true 則區分大小寫,否則不區分;如下所示
// sensitive = true 時
<Route path="/page1" sensitive component={Page1}></Route>
// 瀏覽器通過 http://localhost:3000/PAGE1 訪問不到 page1 頁面

// sensitive = fasle 時
<Route path="/page1" sensitive={false} component={Page1}></Route>
// 瀏覽器通過 http://localhost:3000/PAGE1 可以訪問到 page1 頁面
  • strict
    boolean 類型,對路徑末尾斜杠的匹配。如果為 true,path 為 '/page1/' 將不能匹配 '/page1' 但可以匹配 '/page1/one'。;如下所示
// strict = true 時
<Route path="/page1/" strict component={Page1}></Route>
// 瀏覽器通過以下 url 訪問不到 page1 頁面
http://localhost:3000/page1
// 瀏覽器通過以下 url 可以訪問到 page1 頁面
http://localhost:3000/page1/one

// strict = fasle 時
<Route path="/page1/" strict={false} component={Page1}></Route>
// 瀏覽器通過以下 url 可以訪問到 page1 頁面
http://localhost:3000/page1
http://localhost:3000/page1/one
  • component
    設置路由對應渲染的組件,如下所示Page1為要渲染的組件
<Route path="/page1/" exact component={Page1}></Route>
  • render
    通過render函數返回路由對應渲染的組件或者dom,好處是不僅可以通過 render 方法傳遞 props 屬性,并且可以傳遞自定義屬性。如下所示
<Route path='/about' exact render={(props) => {
    return <Page1 {...props} name={'name1'} />
}}></Route>

class Page1 extends React.Component {
  componentDidMount() {
    console.log(this.props) 
    // this.props:
    // history: {length: 9, action: "POP", location: {…}, createHref: ?, push: ?, …}
    // location: {pathname: "/home", search: "", hash: "", state: undefined, key: "ad7bco"}
    // match: {path: "/home", url: "/home", isExact: true, params: {…}}
    // name: "name1"
  }
}
  1. Switch
    如果路由 Route 外部包裹 Switch 時,路由匹配到對應的組件后,就不會繼續渲染其他組件了。但是如果外部不包裹 Switch 時,所有路由組件會先渲染一遍,然后選擇所有匹配的路由進行顯示。
// 當沒有使用 Switch 時
<BrowserRouter>
  <Route path="/page1" component={Page1}></Route>
  <Route path="/" component={Page2}></Route>
  <Route path="/page3/:id" exact component={Page3}></Route>
</BrowserRouter>
// 當面訪問 http://localhost:3000/page1 時,瀏覽器會同時顯示 page1 和 page2 頁面的內容 

// 當使用 Switch 時
<BrowserRouter>
  <Switch>
    <Route path="/page1" component={Page1}></Route>
    <Route path="/" component={Page2}></Route>
    <Route path="/page3/:id" exact component={Page3}></Route>
  </Switch>
</BrowserRouter>
// 當面訪問 http://localhost:3000/page1 時,瀏覽器只會顯示 page1 頁面的內容 

導航

  1. Link
    用來指定路由跳轉,Link的參數介紹如下:
  • to
    可以通過字符串執行路由跳轉,也可以通過對象指定路由跳轉,如下所示
// 通過字符串
<Link to='/page2'>
  <span>跳轉到 page2</span>
</Link>  

// 通過對象
<Link to={{
    pathname: '/page2',
    search: '?name=name1',
    hash: '#someHash',
    state: { age: 11 }
  }}>
  <span>跳轉到 page2</span>
</Link>

通過對象指定路由跳轉時,各字段的含義如下:
pathname: 表示跳轉的頁面路由 path
search: 表示查詢參數的字符串形式,即等同于 location 中的 search
hash: 放入網址的 hash,即等同于 location 中的 hash
state: 可以通過這個屬性,向新的頁面隱式傳參,在上面的例子中page2 中可以通過 this.props.location.state 可以拿到 age: 11;

  • replace
    如果設置 replace 為 true 時,表示用新地址替換掉上一次訪問的地址;
  1. NavLink
    這是 Link 的特殊版,顧名思義這就是為頁面導航準備的,因為導航需要有 “激活狀態”。除Link的固定參數外,還有如下可選參數:
  • activeClassName: string,導航選中激活時候應用的樣式名
  • activeStyle: object,如果不想使用樣式名就直接寫 style,
  • exact: bool,若為 true,只有當訪問地址嚴格匹配時激活樣式才會應用
  • strict: bool,若為 true,只有當訪問地址后綴斜杠嚴格匹配(有或無)時激活樣式才會應用
  • isActive: func,決定導航是否激活,或者在導航激活時候做點別的事情。不管怎樣,它不能決定對應頁面是否可以渲染。
  1. Redirect
    Redirect用于路由的重定向,它會執行跳轉到對應的to路徑中,其中參數to的規則同Link一樣。
<Redirect to='/' />

withRouter

withRouter 可以將一個非路由組件包裹為路由組件,使這個非路由組件也能訪問到當前路由的 match, location, history對象。

class Component1 extends React.Component<any> {
  handleClick () {
    this.props.history.push('/page2');
  }
  render () {
    return <div onClick={() => this.handleClick()}>this is component1</div>;
  }
}

export default withRouter(Component1);

參數傳遞

傳遞參數有三種方式:

  1. 動態路由的方式
    假如/detail的路徑對應 一個組件Detail,若將路徑中的Route匹配時寫成/detail/:id,那么/detail/123,/detail/xyz都可以匹配到該Route并進行顯示
// 路由配置
<Route path="/detail/:id" exact component={Detail}></Route>
// 路由跳轉
this.props.history.push('/detail/1000');
// 參數獲取
this.props.match.params;  // {id: "1000"}
  1. search傳遞參數
    根據參數to的不同書寫格式,search傳參可以分為兩種形式:
<Link to="/detail2?name=boge&age=20">詳情2</Link>
<Link to={{ pathname: '/detail2', search: '?name=boge&age=20'}}>詳情2</Link>

// 參數獲取
this.props.location.state;  // {name:'boge', age:20}
  1. 隱式傳參
    所傳遞的參數不可見
//數據定義
const data = {id:3,name:sam,age:36};
const path = {
    pathname: '/user',
    query: data,
}
this.props.history.push(path);

// 參數獲取
this.props.location.query;   // {id:3,name:sam,age:36};

嵌套路由

嵌套路由就是在子頁面中再設置一層新的路由導航規則。重點在于不能在父級加 exact(精準匹配),因為先要匹配父級然后才能匹配子集。

// appRouter.js
<BrowserRouter>
    <Route path="/" exact component={Index} />
    <Route path="/video/" component={Video} />
    <Route path="/workplace/" component={Workplace} />
</BrowserRouter>

// video.js
<div className="videoContent">
    <h3>視頻教程</h3>
    <Route path="/video/react/" component={React} />
    <Route path="/video/vue/" component={Vue} />
</div>

為什么我能夠看得更遠,那是因為我站在巨人的肩上,以上內容來源:
深度剖析:前端路由原理
一文搞定 React 路由

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容