摘要:回調(diào)函數(shù)將在更新時(shí)觸發(fā),回調(diào)中的起到了新的的作用。注冊回調(diào)在中使用注冊的回調(diào)函數(shù),最終放在模塊的回調(diào)函數(shù)數(shù)組中。
原文地址:https://github.com/joeyguo/blog/issues/2
在單頁應(yīng)用上,前端路由并不陌生。很多前端框架也會有獨(dú)立開發(fā)或推薦配套使用的路由系統(tǒng)。那么,當(dāng)我們在談前端路由的時(shí)候,還可以談些什么?本文將簡要分析并實(shí)現(xiàn)一個(gè)的前端路由,并對 react-router 進(jìn)行分析。
一個(gè)極簡前端路由實(shí)現(xiàn)說一下前端路由實(shí)現(xiàn)的簡要原理,以 hash 形式(也可以使用 History API 來處理)為例,當(dāng) url 的 hash 發(fā)生變化時(shí),觸發(fā) hashchange 注冊的回調(diào),回調(diào)中去進(jìn)行不同的操作,進(jìn)行不同的內(nèi)容的展示。直接看代碼或許更直觀。
function Router() { 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 = new Router(); window.Router.init();
上面路由系統(tǒng) Router 對象實(shí)現(xiàn),主要提供三個(gè)方法
init 監(jiān)聽瀏覽器 url hash 更新事件
route 存儲路由更新時(shí)的回調(diào)到回調(diào)數(shù)組routes中,回調(diào)函數(shù)將負(fù)責(zé)對頁面的更新
refresh 執(zhí)行當(dāng)前url對應(yīng)的回調(diào)函數(shù),更新頁面
Router 調(diào)用方式以及呈現(xiàn)效果如下:點(diǎn)擊觸發(fā) url 的 hash 改變,并對應(yīng)地更新內(nèi)容(這里為 body 背景色)
var content = document.querySelector("body"); // change Page anything function changeBgColor(color) { content.style.backgroundColor = color; } Router.route("/", function() { changeBgColor("white"); }); Router.route("/blue", function() { changeBgColor("blue"); }); Router.route("/green", function() { changeBgColor("green"); });
以上為一個(gè)前端路由的簡單實(shí)現(xiàn),點(diǎn)擊查看完整代碼,雖然簡單,但實(shí)際上很多路由系統(tǒng)的根基都立于此,其他路由系統(tǒng)主要是對自身使用的框架機(jī)制的進(jìn)行配套及優(yōu)化,如與 react 配套的 react-router。
react-router 分析 react-router 與 history 結(jié)合形式react-router 是基于 history 模塊提供的 api 進(jìn)行開發(fā)的,結(jié)合的形式本文記為 包裝方式。所以在開始對其分析之前,先舉一個(gè)簡單的例子來說明如何進(jìn)行對象的包裝。
// 原對象 var historyModule = { listener: [], listen: function (listener) { this.listener.push(listener); console.log("historyModule listen..") }, updateLocation: function(){ this.listener.forEach(function(listener){ listener("new localtion"); }) } } // Router 將使用 historyModule 對象,并對其包裝 var Router = { source: {}, init: function(source){ this.source = source; }, // 對 historyModule的listen進(jìn)行了一層包裝 listen: function(listener) { return this.source.listen(function(location){ console.log("Router listen tirgger."); listener(location); }) } } // 將 historyModule 注入進(jìn) Router 中 Router.init(historyModule); // Router 注冊監(jiān)聽 Router.listen(function(location){ console.log(location + "-> Router setState."); }) // historyModule 觸發(fā)回調(diào) historyModule.updateLocation();
返回:
可看到 historyModule 中含有機(jī)制:historyModule.updateLocation() -> listener( ),Router 通過對其進(jìn)行包裝開發(fā),針對 historyModule 的機(jī)制對 Router 也起到了作用,即historyModule.updateLocation() 將觸發(fā) Router.listen 中的回調(diào)函數(shù) 。點(diǎn)擊查看完整代碼
這種包裝形式能夠充分利用原對象(historyModule )的內(nèi)部機(jī)制,減少開發(fā)成本,也更好的分離包裝函數(shù)(Router)的邏輯,減少對原對象的影響。
react-router 以 react component 的組件方式提供 API, 包含 Router,Route,Redirect,Link 等等,這樣能夠充分利用 react component 提供的生命周期特性,同時(shí)也讓定義路由跟寫 react component 達(dá)到統(tǒng)一,如下
render((), document.body)
就這樣,聲明了一份含有 path to component 的各個(gè)映射的路由表。
react-router 還提供的 Link 組件(如下),作為提供更新 url 的途徑,觸發(fā) Link 后最終將通過如上面定義的路由表進(jìn)行匹配,并拿到對應(yīng)的 component 及 state 進(jìn)行 render 渲染頁面。
"joey"
這里不細(xì)講 react-router 的使用,詳情可見:https://github.com/reactjs/react-router
從點(diǎn)擊 Link 到 render 對應(yīng) component ,路由中發(fā)生了什么 為何能夠觸發(fā) render component ?主要是因?yàn)橛|發(fā)了 react setState 的方法從而能夠觸發(fā) render component。
從頂層組件 Router 出發(fā)(下面代碼從 react-router/Router 中摘取),可看到 Router 在 react component 生命周期之組件被掛載前 componentWillMount 中使用 this.history.listen 去注冊了 url 更新的回調(diào)函數(shù)。回調(diào)函數(shù)將在 url 更新時(shí)觸發(fā),回調(diào)中的 setState 起到 render 了新的 component 的作用。
Router.prototype.componentWillMount = function componentWillMount() { // .. 省略其他 var createHistory = 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 操作便是對其做一層包裝,所以調(diào)用的 this.history 實(shí)際為包裝以后的對象,該對象含有 _useRoutes2 中的 listen 方法,如下
function listen(listener) { return history.listen(function (location) { // .. 省略其他 match(location, function (error, redirectLocation, nextState) { listener(null, nextState); }); }); }
可看到,上面代碼中,主要分為兩部分
使用了 history 模塊的 listen 注冊了一個(gè)含有 setState 的回調(diào)函數(shù)(這樣就能使用 history 模塊中的機(jī)制)
回調(diào)中的 match 方法為 react-router 所特有,match 函數(shù)根據(jù)當(dāng)前 location 以及前面寫的 Route 路由表匹配出對應(yīng)的路由子集得到新的路由狀態(tài)值 state,具體實(shí)現(xiàn)可見 react-router/matchRoutes ,再根據(jù) state 得到對應(yīng)的 component ,最終執(zhí)行了 match 中的回調(diào) listener(null, nextState) ,即執(zhí)行了 Router 中的監(jiān)聽回調(diào)(setState),從而更新了展示。
以上,為起始注冊的監(jiān)聽,及回調(diào)的作用。
如何觸發(fā)監(jiān)聽的回調(diào)函數(shù)的執(zhí)行?這里還得從如何更新 url 說起。一般來說,url 更新主要有兩種方式:簡單的 hash 更新或使用 history api 進(jìn)行地址更新。在 react-router 中,其提供了 Link 組件,該組件能在 render 中使用,最終會表現(xiàn)為 a 標(biāo)簽,并將 Link 中的各個(gè)參數(shù)組合放它的 href 屬性中。可以從 react-router/ Link 中看到,對該組件的點(diǎn)擊事件進(jìn)行了阻止了瀏覽器的默認(rèn)跳轉(zhuǎn)行為,而改用 history 模塊的 pushState 方法去觸發(fā) url 更新。
Link.prototype.render = function render() { // .. 省略其他 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 = function handleClick(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,調(diào)用哪個(gè)是根據(jù)我們一開始創(chuàng)建 history 的方式。
更新 url 的顯示是一部分,另一部分是根據(jù) url 去更新展示,也就是觸發(fā)前面的監(jiān)聽。這是在前面 finishTransition 更新 url 之后實(shí)現(xiàn)的,調(diào)用的是 history/createHistory 中的 updateLocation 方法,changeListeners 中為 history/createHistory 中的 listen 中所添加的,如下
function updateLocation(newLocation) { // 示意代碼 location = newLocation; changeListeners.forEach(function (listener) { listener(location); }); } function listen(listener) { // 示意代碼 changeListeners.push(listener); }總結(jié)
可以將以上 react-router 的整個(gè)包裝閉環(huán)總結(jié)為
回調(diào)函數(shù):含有能夠更新 react UI 的 react setState 方法。
注冊回調(diào):在 Router componentWillMount 中使用 history.listen 注冊的回調(diào)函數(shù),最終放在 history 模塊的 回調(diào)函數(shù)數(shù)組 changeListeners 中。
觸發(fā)回調(diào):Link 點(diǎn)擊觸發(fā) history 中回調(diào)函數(shù)數(shù)組 changeListeners 的執(zhí)行,從而觸發(fā)原來 listen 中的 setState 方法,更新了頁面
至于前進(jìn)與后退的實(shí)現(xiàn),是通過監(jiān)聽 popstate 以及 hashchange 的事件,當(dāng)前進(jìn)或后退 url 更新時(shí),觸發(fā)這兩個(gè)事件的回調(diào)函數(shù),回調(diào)的執(zhí)行方式 Link 大致相同,最終同樣更新了 UI ,這里就不再說明。
react-router 主要是利用底層 history 模塊的機(jī)制,通過結(jié)合 react 的架構(gòu)機(jī)制做一層包裝,實(shí)際自身的內(nèi)容并不多,但其包裝的思想筆者認(rèn)為很值得學(xué)習(xí),有興趣的建議閱讀下源碼,相信會有其他收獲。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/79509.html
摘要:通過前端路由可以實(shí)現(xiàn)單頁應(yīng)用本文首先從前端路由的原理出發(fā),詳細(xì)介紹了前端路由原理的變遷。接著從的源碼出發(fā),深入理解是如何實(shí)現(xiàn)前端路由的。執(zhí)行上述的賦值后,頁面的發(fā)生改變。 ??react-router等前端路由的原理大致相同,可以實(shí)現(xiàn)無刷新的條件下切換顯示不同的頁面。路由的本質(zhì)就是頁面的URL發(fā)生改變時(shí),頁面的顯示結(jié)果可以根據(jù)URL的變化而變化,但是頁面不會刷新。通過前端路由可以實(shí)現(xiàn)...
摘要:介紹為提供路由管理為基于格式的系統(tǒng)提供了方便的切換頁面功能。它在前端提供給了種方式通過或者瀏覽器原生的進(jìn)行地址更新上一篇介紹了的方式本文則以的形式切入分析。代碼剖析路由配置本生為組建內(nèi)部組建如等。 前言 在前端單頁面應(yīng)用里面,路由是比較重要的部分,筆者的上一篇博文簡單的路由介紹了簡單的路由內(nèi)部機(jī)制,本文則將分析react-router的內(nèi)部機(jī)制。 介紹 react-router為rea...
摘要:一般情況下,都是作為等其他子路由的上層路由,使用了,接收一個(gè)屬性,傳遞給消費(fèi)子組件。創(chuàng)建對象,兼容老瀏覽器,其他和沒有大區(qū)別總結(jié)分為四個(gè)包,分別為,其中是瀏覽器相關(guān),是相關(guān),是核心也是共同部分,是一些配置相關(guān)。 這篇文章主要講的是分析 react-router 源碼,版本是 v5.x,以及 SPA 路由實(shí)現(xiàn)的原理。 文章首發(fā)地址 單頁面應(yīng)用都用到了路由 router,目前來看實(shí)現(xiàn)路由有...
摘要:另外,內(nèi)置的函數(shù)在經(jīng)過一系列校驗(yàn)后,觸發(fā),之后被更改,之后依次調(diào)用監(jiān)聽,完成整個(gè)狀態(tài)樹的更新。總而言之,遵守這套規(guī)范并不是強(qiáng)制性的,但是項(xiàng)目一旦稍微復(fù)雜一些,這樣做的好處就可以充分彰顯出來。 這一篇是接上一篇react進(jìn)階漫談的第二篇,這一篇主要分析redux的思想和應(yīng)用,同樣參考了網(wǎng)絡(luò)上的大量資料,但代碼同樣都是自己嘗試實(shí)踐所得,在這里分享出來,僅供一起學(xué)習(xí)(上一篇地址:個(gè)人博客/s...
閱讀 1960·2021-09-04 16:45
閱讀 747·2019-08-30 15:44
閱讀 893·2019-08-30 13:07
閱讀 456·2019-08-29 16:06
閱讀 1375·2019-08-29 13:43
閱讀 1269·2019-08-26 17:00
閱讀 1526·2019-08-26 13:51
閱讀 2294·2019-08-26 11:48