前端路由原理和React Router

前端路由原理

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

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

Hash模式

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

hash 有下面幾個(gè)特性:

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

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

  1. 通過 a 標(biāo)簽,并設(shè)置 href 屬性,當(dāng)用戶點(diǎn)擊這個(gè)標(biāo)簽后,URL 就會發(fā)生改變,也就會觸發(fā) hashchange 事件了:
<a href="#search">search</a>
  1. 直接使用 JavaScript來對 loaction.hash 進(jìn)行賦值,從而改變 URL,觸發(fā) hashchange 事件:
location.hash="#search"

History模式

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

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

history 有下面幾個(gè)特性:

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

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


React Router

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

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

下面逐一展開介紹:

路由組件

路由組件分為兩種:BrowserRouter(對應(yīng)前端路由history 模式) 和 HashRouter(對應(yīng)前端路由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 是用于聲明路由映射到應(yīng)用程序的組件層。
    路由Route的參數(shù)介紹如下:
  • path
    string 類型,用來指定路由跳轉(zhuǎn)路徑,根據(jù) path 和 url 匹配到對應(yīng)的頁面;如下所示
// 路由配置
<Route path="/page1" exact component={Page1}></Route>
// 頁面訪問 http://localhost:3000/page1
  • exact
    boolean 類型,用來精確匹配路由,如果為 true 則精確匹配,否則為正常匹配;如下所示
// exact = true 時(shí)
<Route path="/page1" exact component={Page1}></Route>
// 瀏覽器通過 http://localhost:3000/page1/one 訪問不到 page1 頁面

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

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

// strict = fasle 時(shí)
<Route path="/page1/" strict={false} component={Page1}></Route>
// 瀏覽器通過以下 url 可以訪問到 page1 頁面
http://localhost:3000/page1
http://localhost:3000/page1/one
  • component
    設(shè)置路由對應(yīng)渲染的組件,如下所示Page1為要渲染的組件
<Route path="/page1/" exact component={Page1}></Route>
  • render
    通過render函數(shù)返回路由對應(yīng)渲染的組件或者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 時(shí),路由匹配到對應(yīng)的組件后,就不會繼續(xù)渲染其他組件了。但是如果外部不包裹 Switch 時(shí),所有路由組件會先渲染一遍,然后選擇所有匹配的路由進(jìn)行顯示。
// 當(dāng)沒有使用 Switch 時(shí)
<BrowserRouter>
  <Route path="/page1" component={Page1}></Route>
  <Route path="/" component={Page2}></Route>
  <Route path="/page3/:id" exact component={Page3}></Route>
</BrowserRouter>
// 當(dāng)面訪問 http://localhost:3000/page1 時(shí),瀏覽器會同時(shí)顯示 page1 和 page2 頁面的內(nèi)容 

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

導(dǎo)航

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

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

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

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

withRouter

withRouter 可以將一個(gè)非路由組件包裹為路由組件,使這個(gè)非路由組件也能訪問到當(dāng)前路由的 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);

參數(shù)傳遞

傳遞參數(shù)有三種方式:

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

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

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

嵌套路由

嵌套路由就是在子頁面中再設(shè)置一層新的路由導(dǎo)航規(guī)則。重點(diǎn)在于不能在父級加 exact(精準(zhǔn)匹配),因?yàn)橄纫ヅ涓讣壢缓蟛拍芷ヅ渥蛹?/p>

// 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>

為什么我能夠看得更遠(yuǎn),那是因?yàn)槲艺驹诰奕说募缟希陨蟽?nèi)容來源:
深度剖析:前端路由原理
一文搞定 React 路由

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容