1. 程式人生 > >前端路由實現與 react-router 原始碼分析

前端路由實現與 react-router 原始碼分析

在單頁應用上,前端路由並不陌生。很多前端框架也會有獨立開發或推薦配套使用的路由系統。那麼,當我們在談前端路由的時候,還可以談些什麼?本文將簡要分析並實現一個的前端路由,並對 react-router 進行分析。

一個極簡前端路由實現

說一下前端路由實現的簡要原理,以 hash 形式(也可以使用 History API 來處理)為例,當 url 的 hash 發生變化時,觸發 hashchange 註冊的回撥,回撥中去進行不同的操作,進行不同的內容的展示。直接看程式碼或許更直觀。

JavaScript
1234567891011121314151617 functionRouter(){this.routes={};this.currentUrl='';}Router.prototype.route=function(path,callback){this.routes[path]=callback||function(){};};Router.prototype.refresh=function(){this.currentUrl=location.hash.slice(1)||'/';this.routes
[this.currentUrl]();};Router.prototype.init=function(){window.addEventListener('load',this.refresh.bind(this),false);window.addEventListener('hashchange',this.refresh.bind(this),false);}window.Router=newRouter();window.Router.init();

上面路由系統 Router 物件實現,主要提供三個方法

  • init 監聽瀏覽器 url hash 更新事件
  • route 儲存路由更新時的回撥到回撥陣列routes中,回撥函式將負責對頁面的更新
  • refresh 執行當前url對應的回撥函式,更新頁面

Router 呼叫方式以及呈現效果如下:點選觸發 url 的 hash 改變,並對應地更新內容(這裡為 body 背景色)

XHTML
12345 <ul><li><a href="#/">turn white</a></li><li><a href="#/blue">turn blue</a></li><li><a href="#/green">turn green</a></li></ul>
JavaScript
1234567891011121314 varcontent=document.querySelector('body');// change Page anythingfunctionchangeBgColor(color){content.style.backgroundColor=color;}Router.route('/',function(){changeBgColor('white');});Router.route('/blue',function(){changeBgColor('blue');});Router.route('/green',function(){changeBgColor('green');});

以上為一個前端路由的簡單實現,點選檢視完整程式碼,雖然簡單,但實際上很多路由系統的根基都立於此,其他路由系統主要是對自身使用的框架機制的進行配套及優化,如與 react 配套的 react-router。

react-router 分析

react-router 與 history 結合形式

react-router 是基於 history 模組提供的 api 進行開發的,結合的形式本文記為 包裝方式。所以在開始對其分析之前,先舉一個簡單的例子來說明如何進行物件的包裝。

可看到 historyModule 中含有機制:historyModule.updateLocation() -> listener( ),Router 通過對其進行包裝開發,針對 historyModule 的機制對 Router 也起到了作用,即historyModule.updateLocation() 將觸發 Router.listen 中的回撥函式 。點選檢視完整程式碼
這種包裝形式能夠充分利用原物件(historyModule )的內部機制,減少開發成本,也更好的分離包裝函式(Router)的邏輯,減少對原物件的影響。

react-router 使用方式

react-router 以 react component 的元件方式提供 API, 包含 Router,Route,Redirect,Link 等等,這樣能夠充分利用 react component 提供的生命週期特性,同時也讓定義路由跟寫 react component 達到統一,如下

JavaScript
1234567891011 render((<Router history={browserHistory}><Route path="/"component={App}><Route path="about"component={About}/><Route path="users"component={Users}><Route path="/user/:userId"component={User}/></Route><Route path="*"component={NoMatch}/></Route></Router>),document.body)

就這樣,聲明瞭一份含有 path to component 的各個對映的路由表。
react-router 還提供的 Link 元件(如下),作為提供更新 url 的途徑,觸發 Link 後最終將通過如上面定義的路由表進行匹配,並拿到對應的 component 及 state 進行 render 渲染頁面。

XHTML
1 <Link to={`/user/89757`}>'joey'</Link>

從點選 Link 到 render 對應 component ,路由中發生了什麼

為何能夠觸發 render component ?

主要是因為觸發了 react setState 的方法從而能夠觸發 render component。
從頂層元件 Router 出發(下面程式碼從 react-router/Router 中摘取),可看到 Router 在 react component 生命週期之元件被掛載前 componentWillMount 中使用 this.history.listen 去註冊了 url 更新的回撥函式。回撥函式將在 url 更新時觸發,回撥中的 setState 起到 render 了新的 component 的作用。

JavaScript
1234567891011121314 Router.prototype.componentWillMount=functioncomponentWillMount(){// .. 省略其他varcreateHistory=this.props.history;this.history=_useRoutes2['default'](createHistory)({routes:_RouteUtils.createRoutes(routes||children),parseQueryString:parseQueryString,stringifyQuery:stringifyQuery});this._unlisten=this.history.listen(function(error,state){_this.setState(state,_this.props.onUpdate);});};

上面的 _useRoutes2 對 history 操作便是對其做一層包裝,所以呼叫的 this.history 實際為包裝以後的物件,該物件含有 _useRoutes2 中的 listen 方法,如下

JavaScript
12345678 functionlisten(listener){returnhistory.listen(function(location){// .. 省略其他match(location,function(error,redirectLocation,nextState){listener(null,nextState);});});}

可看到,上面程式碼中,主要分為兩部分
1. 使用了 history 模組的 listen 註冊了一個含有 setState 的回撥函式(這樣就能使用 history 模組中的機制)
2. 回撥中的 match 方法為 react-router 所特有,match 函式根據當前 location 以及前面寫的 Route 路由表匹配出對應的路由子集得到新的路由狀態值 state,具體實現可見 react-router/matchRoutes ,再根據 state 得到對應的 component ,最終執行了 match 中的回撥 listener(null, nextState) ,即執行了 Router 中的監聽回撥(setState),從而更新了展示。

以上,為起始註冊的監聽,及回撥的作用。

如何觸發監聽的回撥函式的執行?

這裡還得從如何更新 url 說起。一般來說,url 更新主要有兩種方式:簡單的 hash 更新或使用 history api 進行地址更新。在 react-router 中,其提供了 Link 元件,該元件能在 render 中使用,最終會表現為 a 標籤,並將 Link 中的各個引數組合放它的 href 屬性中。可以從 react-router/ Link 中看到,對該元件的點選事件進行了阻止了瀏覽器的預設跳轉行為,而改用 history 模組的 pushState 方法去觸發 url 更新。

JavaScript
1234567891011121314151617 Link.prototype.render=functionrender(){// .. 省略其他props.onClick=function(e){return_this.handleClick(e);};if(history){// .. 省略其他props.href=history.createHref(to,query);}return_react2['default'].createElement('a',props);};Link.prototype.handleClick=functionhandleClick(event){// .. 省略其他event.preventDefault();this.context.history.pushState(this.props.state,this.props.to,this.props.query);};

對 history 模組的 pushState 方法對 url 的更新形式,同樣分為兩種,分別在 history/createBrowserHistory 及 history/createHashHistory 各自的 finishTransition 中,如 history/createBrowserHistory 中使用的是 window.history.replaceState(historyState, null, path); 而 history/createHashHistory 則使用 window.location.hash = url,呼叫哪個是根據我們一開始建立 history 的方式。

更新 url 的顯示是一部分,另一部分是根據 url 去更新展示,也就是觸發前面的監聽。這是在前面 finishTransition 更新 url 之後實現的,呼叫的是 history/createHistory 中的 updateLocation 方法,changeListeners 中為 history/createHistory 中的 listen 中所新增的,如下

JavaScript
1234567891011 functionupdateLocation(newLocation){// 示意程式碼location=newLocation;changeListeners.forEach(function(listener){listener(location);});}functionlisten(listener){// 示意程式碼changeListeners.push(listener);}

總結

可以將以上 react-router 的整個包裝閉環總結為

  1. 回撥函式:含有能夠更新 react UI 的 react setState 方法。
  2. 註冊回撥:在 Router componentWillMount 中使用 history.listen 註冊的回撥函式,最終放在 history 模組的 回撥函式陣列 changeListeners 中。
  3. 觸發回撥:Link 點選觸發 history 中回撥函式陣列 changeListeners 的執行,從而觸發原來 listen 中的 setState 方法,更新了頁面

至於前進與後退的實現,是通過監聽 popstate 以及 hashchange 的事件,當前進或後退 url 更新時,觸發這兩個事件的回撥函式,回撥的執行方式 Link 大致相同,最終同樣更新了 UI ,這裡就不再說明。

react-router 主要是利用底層 history 模組的機制,通過結合 react 的架構機制做一層包裝,實際自身的內容並不多,但其包裝的思想筆者認為很值得學習,有興趣的建議閱讀下原始碼,相信會有其他收穫。

相關推薦

前端路由實現 react-router 原始碼分析

在單頁應用上,前端路由並不陌生。很多前端框架也會有獨立開發或推薦配套使用的路由系統。那麼,當我們在談前端路由的時候,還可以談些什麼?本文將簡要分析並實現一個的前端路由,並對 react-router 進行分析。 一個極簡前端路由實現 說一下前端路由實現的簡要原理,以 hash

阿里飛冰使用Link路由跳轉報錯之“react-routerreact-router-dom”

需求:首頁連結點選跳轉二級頁面 錯誤實現:引入路由模組,使用Link跳轉(如最下面的那張圖) 因為之前沒有接觸過react,飛冰也是新用。所以某度了很久,react路由跳轉,react官方文件,各種文章,未果。飛冰建立的路由配置檔案也跟大部分react路由檔案寫法不太一致,所以我找的很吃

前端迷思React.js

.html injection 參數 性能提升 bundles 人力 鄙視 編碼 bar 前端迷思與React.js 前端技術這幾年蓬勃發展, 這是當時某幾個項目需要做前端技術選型時, 相關資料整理, 部分評論引用自社區。 開始吧: 目前, Web 開發技術框

React 學習筆記 (七)(路由路由巢狀 react-router 4.x 基本配置及使用)

react-router 路由 根據使用者訪問的地址動態的載入不同的元件 1.安裝 npm install react-router-dom --save 2.引入 import { BrowserRouter as Router, Route, Link } fr

React Fiber原始碼分析 第一篇

 先附上流程圖一張       先由babel編譯, 呼叫reactDOM.render,入參為element, container, callback, 打印出來可以看到element,container,callback分別代表著re

React Fiber原始碼分析 第三篇(非同步狀態)

先附上流程圖~       呼叫setState時, 會呼叫classComponentUpdater的enqueueSetState方法, 同時將新的state作為payload引數傳進 enqueueSetState會先呼叫requestCurrentTime獲

React Fiber原始碼分析 (介紹)

寫了分析原始碼的文章後, 總覺得缺少了什麼, 在這裡補一個整體的總結,輸出個人的理解~ 文章的系列標題為Fiber原始碼分析, 那麼什麼是Fiber,官方給出的解釋是: React Fiber是對核心演算法的一次重新實現。 ummm, 這樣說實在是有點泛,詳細分析一下    

HTML5中history物件解析及前端路由實現封裝流程

HTML5 history新增了兩個API:history.pushState和history.replaceState 兩個Api都接收三個引數: 語法: window.history.pushState(state Object, title, URL); window.hist

React路由管理之React Router總結

React Router是做什麼的呢,官方的介紹是:讓UI元件和URL保持同步,通過簡單的API即可實現強大的特性如:程式碼懶載入,動態路由匹配,路徑過渡處理等。 下面是一些React Router的用法: 簡單渲染Route 有一點需要牢記於心,Router 是

深入出不來的React路由管理之React router

React Router是做什麼的呢,官方的介紹是:讓UI元件和URL保持同步,通過簡單的API即可實現強大的特性如:程式碼懶載入,動態路由匹配,路徑過渡處理等。 下面是一些React Router的用法: 簡單渲染Route 有一點需要牢記於心,Router 是作為一個Re

vue 單頁應用(spa)前端路由實現原理

寫在前面:通常 SPA 中前端路由有2種實現方式: window.history location.hash 下面就來介紹下這兩種方式具體怎麼實現的 一.history 1.history基本介紹 window.history 物件包含瀏覽器的

k8s網路--Flannel原始碼分析

前言 之前在k8s與網路--Flannel解讀一文中,我們主要講了Flannel整體的工作原理。今天主要針對Flannel v0.10.0版本進行原始碼分析。首先需要理解三個比較重要的概念: 網路(Network):整個叢集中分配給 flannel 要管理的網路地

前端框架Angularreact對比

本文主要討論兩種框架在應用上的特點對比。 Angular Angular 是基於 TypeScript 的 Javascript 框架。提到Angular,就不得不提它的前身AngularJS。AngularJS是Google釋出的第一個MVVM框架,帶來了許多新特性,為

React-Router 原始碼解析

前言 本系列將會根據一個簡單的專案來學習React-Router 原始碼,要到達的目的有如下: 學會使用React-Router 在使用的基礎上,分析React-Router 原始碼結構 可以下載專案原始碼,並按照如下步驟,將專案執行起來 git clone [ema

前端踩坑(一)--------------------------------------------------react-router-dom講解

react路由已經更新到4,網上的資料大多是舊版本,這裡我們重點講react-router-dom,它是用於dom繫結的react-router 常規匯入 import React from 'react' import { BrowserRouter as Rou

Java中HashMap底層實現原理(JDK1.8)原始碼分析

在JDK1.6,JDK1.7中,HashMap採用位桶+連結串列實現,即使用連結串列處理衝突,同一hash值的連結串列都儲存在一個連結串列裡。但是當位於一個桶中的元素較多,即hash值相等的元素較多時,通過key值依次查詢的效率較低。而JDK1.8中,HashMap採用位桶+

Web開發中 前端路由 實現的幾種方式和適用場景

故事從名叫Oliver的綠箭蝦`說起,這位大蝦酷愛社交網站,一天他打開了 Twitter ,從發過的tweets的選項卡一路切到followers選項卡,Oliver發現頁面的內容變化了,URL也變化了,但為什麼頁面沒有閃爍重新整理呢?於是Oliver開啟的網路監控器(沒錯,Oliver是個程式設計師),他驚

StringBuffer StringBuilder 區別聯絡及原始碼分析

StringBuffer和StringBuilder的共同點: 1、都是用於操作字串,使用這兩個而不使用String的原因是因為String是Final型別,當對字串操作較多時採用StringBuffer或者StringBuilder。 St

(轉載)Java中HashMap底層實現原理(JDK1.8)原始碼分析

近期在看一些java底層的東西,看到一篇分析hashMap不錯的文章,跟大家分享一下。 在JDK1.6,JDK1.7中,HashMap採用位桶+連結串列實現,即使用連結串列處理衝突,同一hash值的連結串列都儲存在一個連結串列裡。但是當位於一個桶中的元素較多,即hash值

基於react16 webpack3 搭建前端spa基礎框架 react-router的4種非同步載入方式

這兩天看了下react的文件,準備搭建一套適用的基本react開發架子。 由於我一直使用的是vue,很少使用過react進行專案的開發,因此此構建主要參考的是vue的專案經驗。 專案主要會涉及到的知識點 webpack 配置及其優化 react-router 升級為4.0之後的使用 react-ro