前端路由原理
前端三大框架 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 事件)呢?
- 通過 a 標簽,并設置 href 屬性,當用戶點擊這個標簽后,URL 就會發生改變,也就會觸發 hashchange 事件了:
<a href="#search">search</a>
- 直接使用 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>
路由匹配器
- 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"
}
}
- 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 頁面的內容
導航
- 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 時,表示用新地址替換掉上一次訪問的地址;
- NavLink
這是 Link 的特殊版,顧名思義這就是為頁面導航準備的,因為導航需要有 “激活狀態”。除Link的固定參數外,還有如下可選參數:
- activeClassName: string,導航選中激活時候應用的樣式名
- activeStyle: object,如果不想使用樣式名就直接寫 style,
- exact: bool,若為 true,只有當訪問地址嚴格匹配時激活樣式才會應用
- strict: bool,若為 true,只有當訪問地址后綴斜杠嚴格匹配(有或無)時激活樣式才會應用
- isActive: func,決定導航是否激活,或者在導航激活時候做點別的事情。不管怎樣,它不能決定對應頁面是否可以渲染。
- 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);
參數傳遞
傳遞參數有三種方式:
- 動態路由的方式
假如/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"}
- 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}
- 隱式傳參
所傳遞的參數不可見
//數據定義
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 路由