隨著React Vue前端框架的興起,出現(xiàn)了Vue-router,react-router-dom等前端路由管理庫,利用他們構(gòu)建出來的單頁面應用,也是越來越接近原生的體驗,再也不是以前的點擊標簽跳轉(zhuǎn)頁面,刷新整個頁面了,那么他們的原理是什么呢?優(yōu)質(zhì)gitHub開源練手項目:
React移動端從零開始手寫一個優(yōu)化版腳手架
Electron跨平臺應用DEMO
手寫的Node.js靜態(tài)資源服務器
React 同構(gòu)ssr
Node.js爬蟲輸出PDF文件
先說說原始的MPA多頁面應用:文末還有新建的QQ以及微信群哦~ 歡迎大家加入~~傳統(tǒng)的多頁面應用構(gòu)建方式:
純服務端渲染,前后端不分離,使用jsp,jade,"ejs","tempalte"等技術(shù)在后臺先拼接成對應的HTML結(jié)構(gòu),然后轉(zhuǎn)換成字符串,在每個對應的路由返回對應的數(shù)據(jù)(文件)即可
Jade模版服務端渲染,代碼實現(xiàn):
const express= require("express") const app =express() const jade = require("jade") const result = *** const url path = *** const html = jade.renderFile(url, { data: result, urlPath })//傳入數(shù)據(jù)給模板引擎 app.get("/",(req,res)=>{ res.send(html)//直接吐渲染好的`html`文件拼接成字符串返回給客戶端 }) //RestFul接口 app.listen(3000,err=>{ //do something })
使用jQuery等傳統(tǒng)庫繪制的前端頁面
傳統(tǒng)前后端不分離,服務端渲染的優(yōu)缺點: 優(yōu)點:SEO友好,因為返回給前端的是渲染好的HTML結(jié)構(gòu),里面的內(nèi)容都可以被爬蟲抓取到。
對于一些應用性能等要求不高的項目,比如某個公司的靜態(tài)網(wǎng)頁,內(nèi)容很少的情況下,直接一把梭就好,不用再搭建工程化的環(huán)境等
對于后端程序員(全干工程師)來說,不用去特意學習前端框架,公司也不用特意去招聘前端
兼容性好,傳統(tǒng)服務端渲染多頁面應用吐出來的都是字符串,HTML結(jié)構(gòu)
缺點:如果項目很大,不利于維護,據(jù)我所知,目前很多云計算公司,還有不少都是使用非單頁面應用,例如一個幾十萬行的項目是用jQuery寫的,如果注釋和文檔不是非常齊全,那么真的會無從下手
性能和用戶體驗,不能跟單頁面應用相比
后期迭代,升級空間不大,目前大部分寫得比較好的庫,都建立vue,react等框架基礎上,他們都有一套自己的運行機制,有自己的生命周期,并且不像傳統(tǒng)的應用,還加上了一層虛擬DOM以及diff算法
現(xiàn)在類似Ant-Design-pro這樣的開箱即用的庫已經(jīng)很多,單頁面應用的學習和開發(fā)成本已經(jīng)很低很低,如果還在使用傳統(tǒng)的技術(shù)去開發(fā)新的應用,對于開發(fā)人員多內(nèi)心來說也是一種折磨。
這里并不是說多頁面應用不好,只能說各有各自的好,單頁面應用如果通過大量的極致優(yōu)化手段,是可以從不少方面跟原生一拼。目前的單頁面應用:
只有一張Web頁面的應用,是一種從Web服務器加載的富客戶端,單頁面跳轉(zhuǎn)僅刷新局部資源 ,公共資源(js、css等)僅需加載一次,常用于PC端官網(wǎng)、購物等網(wǎng)站
其實只有一個空的DIV標簽,其他都是js動態(tài)生態(tài)的內(nèi)容
單頁面應用實現(xiàn)步驟: 代碼實現(xiàn):首先是一個靜態(tài)模板文件 index.html
Document
在vue react框架的入口文件中指定對應的渲染元素:
import React from "react; import ReactDOM from "react-dom"; ReactDOM.render(, document.querySelector("#root") )
引入react-router或者 react-router-dom,dva等路由跳轉(zhuǎn)的庫
配置路由跳轉(zhuǎn)
單頁面應用所謂路由跳轉(zhuǎn),其實最終結(jié)果就是://這里使用HashRouter //React錯誤邊界 //404路由或者重定向都可以
瀏覽器的url地址發(fā)生變化,但是其實并沒有發(fā)送請求,也沒有刷新整個頁面
根據(jù)我們配置的路由信息,每次點擊切換路由,會切換到不同的組件顯示,類似于選項卡功能的實現(xiàn),但是同時url地址欄會變化
分為HashRouter和BrowserRouter兩種模式
自己實現(xiàn)一個粗略的路由跳轉(zhuǎn): 自己實現(xiàn)傳統(tǒng)的Hash模式跳轉(zhuǎn):hash 就是指 url 后的 # 號以及后面的字符。例如www.baidu.com/#segmentfault,那么#segmentfault就是hash值
需要用到的幾個知識點:
window.location.hash = "**"; // 設置當前的hash值
const hash = window.location.hash 獲取當前的hash值
hash改變會觸發(fā)window的hashchange事件
window.onhashchange=function(e){ let newURL = e.newURL; // 改變后的新 url地址 let oldURL = e.oldURL; // 改變前的舊 url地址 }
這里特別注意,hash改變并不會發(fā)送請求開始實現(xiàn)Hash模式跳轉(zhuǎn): 使用類似發(fā)布訂閱模式的方式,使用ES6的class實現(xiàn):
初始訂閱,每個不同的hash值,對應不同的函數(shù)調(diào)用處理。
class Router { constructor() { this.routes = {}; this.currentUrl = ""; } route(path, callback) { this.routes[path] = callback || function() {}; } updateView() { this.currentUrl = location.hash.slice(1) || "/"; this.routes[this.currentUrl] && this.routes[this.currentUrl](); } init() { window.addEventListener("load", this.updateView.bind(this), false); window.addEventListener("hashchange", this.updateView.bind(this), false); } }
routes 用來存放不同路由對應的回調(diào)函數(shù)
init 用來初始化路由,在 load 事件發(fā)生后刷新頁面,并且綁定 hashchange 事件,當 hash 值改變時觸發(fā)對應回調(diào)函數(shù)
開始使用:這樣一個簡單的hash模式路由就做好了,剩下的就是路由嵌套,以及錯誤邊界的處理History模式實現(xiàn):
History來自Html5的規(guī)范
History模式,url地址欄的改變并不會觸發(fā)任何事件
History模式,可以使用history.pushState,history.replaceState來控制url地址,history.pushState() 和 history.replaceState() 的區(qū)別在于:
history.pushState() 在保留現(xiàn)有歷史記錄的同時,將 url 加入到歷史記錄中。
history.replaceState() 會將歷史記錄中的當前頁面歷史替換為 url。
History模式下,刷新頁面會404,需要后端配合匹配一個任意路由,重定向到首頁,特別是加上Nginx反向代理服務器的時候
我們需要換個思路,我們可以羅列出所有可能觸發(fā) history 改變的情況,并且將這些方式一一進行攔截,變相地監(jiān)聽 history 的改變。對于一個應用而言,url 的改變(不包括 hash 值得改變)只能由下面三種情況引起:
點擊瀏覽器的前進或后退按鈕
點擊 a 標簽
在 JS 代碼中觸發(fā) history.push(replace)State 函數(shù)
只要對上述三種情況進行攔截,就可以變相監(jiān)聽到 history 的改變而做出調(diào)整。針對情況 1,HTML5 規(guī)范中有相應的 onpopstate 事件,通過它可以監(jiān)聽到前進或者后退按鈕的點擊,值得注意的是,調(diào)用 history.push(replace)State 并不會觸發(fā) onpopstate 事件。開始實現(xiàn):
class Router { constructor() { this.routes = {}; this.currentUrl = ""; } route(path, callback) { this.routes[path] = callback || function() {}; } updateView(url) { this.currentUrl = url; this.routes[this.currentUrl] && this.routes[this.currentUrl](); } bindLink() { const allLink = document.querySelectorAll("a[data-href]"); for (let i = 0, len = allLink.length; i < len; i++) { const current = allLink[i]; current.addEventListener( "click", e => { e.preventDefault(); const url = current.getAttribute("data-href"); history.pushState({}, null, url); this.updateView(url); }, false ); } } init() { this.bindLink(); window.addEventListener("popstate", e => { this.updateView(window.location.pathname); }); window.addEventListener("load", () => this.updateView("/"), false); } }Router 跟之前 Hash 路由很像,不同的地方在于:
init 初始化函數(shù),首先需要獲取所有特殊的鏈接標簽,然后監(jiān)聽點擊事件,并阻止其默認事件,觸發(fā) history.pushState 以及更新相應的視圖。
另外綁定 popstate 事件,當用戶點擊前進或者后退的按鈕時候,能夠及時更新視圖,另外當剛進去頁面時也要觸發(fā)一次視圖更新。
實際使用:跟之前的 html 基本一致,區(qū)別在于用 data-href 來表示要實現(xiàn)軟路由的鏈接標簽。
當然上面還有情況 3,就是你在 JS 直接觸發(fā) pushState 函數(shù),那么這時候你必須要調(diào)用視圖更新函數(shù),否則就是出現(xiàn)視圖內(nèi)容和 url 不一致的情況。
setTimeout(() => { history.pushState({}, null, "/about"); router.updateView("/about"); }, 2000);React-router-dom源碼: Router組件:
export class Route extends Component { componentWillMount() { window.addEventListener("hashchange", this.updateView, false); } componentWillUnmount() { window.removeEventListener("hashchange", this.updateView, false); } updateView = () => { this.forceUpdate(); } render() { const { path, exact, component } = this.props; const match = matchPath(window.location.hash, { exact, path }); if (!match) { return null; } if (component) { return React.createElement(component, { match }); } return null; } }
組件掛載監(jiān)聽hash change原生事件,將要卸載時候移除事件監(jiān)聽防止內(nèi)存泄漏
每次hash改變,就觸發(fā)所有對應hash的回掉,所有的Router都去更新視圖
每個Router組件中,都去對比當前的hash值和這個組件的path屬性,如果不一樣,那么就返回null,·否則就渲染這個組件對應的視圖
History模式的實現(xiàn):實現(xiàn)History
這里想多留些時間寫其他源碼,這篇文章寫得非常好,大家也可以去看看,本文很多借鑒他的。withRouter高階函數(shù)的源碼:
var withRouter = function withRouter(Component) { var C = function C(props) { var wrappedComponentRef = props.wrappedComponentRef, remainingProps = _objectWithoutProperties(props, ["wrappedComponentRef"]); return _react2.default.createElement(_Route2.default, { children: function children(routeComponentProps) { return _react2.default.createElement(Component, _extends({}, remainingProps, routeComponentProps, { ref: wrappedComponentRef })); } }); }; C.displayName = "withRouter(" + (Component.displayName || Component.name) + ")"; C.WrappedComponent = Component; C.propTypes = { wrappedComponentRef: _propTypes2.default.func }; return (0, _hoistNonReactStatics2.default)(C, Component); };
傳入一個組件,返回一個新的組件,并且給這個組件賦予全局屬性,擁有路由組件的三大屬性
Switch組件:Switch.prototype.render = function render() { var route = this.context.router.route; var children = this.props.children; var location = this.props.location || route.location; var match = void 0, child = void 0; _react2.default.Children.forEach(children, function (element) { if (match == null && _react2.default.isValidElement(element)) { var _element$props = element.props, pathProp = _element$props.path, exact = _element$props.exact, strict = _element$props.strict, sensitive = _element$props.sensitive, from = _element$props.from; var path = pathProp || from; child = element; match = (0, _matchPath2.default)(location.pathname, { path: path, exact: exact, strict: strict, sensitive: sensitive }, route.match); } }); return match ? _react2.default.cloneElement(child, { location: location, computedMatch: match }) : null; };
遍歷所以傳入的子元素
如果有符合的路由對應的元素,那么就返回,而且只匹配這一個路由。不再繼續(xù)往下匹配
如果第二條沒有找到符合的元素,那么拋出錯誤
如果覺得寫得好,記得點個贊哦,另外新建了微信和QQ群,歡迎各位小哥哥小姐姐入駐~
微信群:
QQ群
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/116162.html
showImg(https://segmentfault.com/img/bVbvOmp?w=1612&h=888); 隨著React Vue前端框架的興起,出現(xiàn)了Vue-router,react-router-dom等前端路由管理庫,利用他們構(gòu)建出來的單頁面應用,也是越來越接近原生的體驗,再也不是以前的點擊標簽跳轉(zhuǎn)頁面,刷新整個頁面了,那么他們的原理是什么呢? 優(yōu)質(zhì)gitHub開源練手項目: ...
showImg(https://segmentfault.com/img/bVbvOmp?w=1612&h=888); 隨著React Vue前端框架的興起,出現(xiàn)了Vue-router,react-router-dom等前端路由管理庫,利用他們構(gòu)建出來的單頁面應用,也是越來越接近原生的體驗,再也不是以前的點擊標簽跳轉(zhuǎn)頁面,刷新整個頁面了,那么他們的原理是什么呢? 優(yōu)質(zhì)gitHub開源練手項目: ...
摘要:一,什么是單頁面應用通俗的來講,就是一個應用只有一個頁面,用戶通過切換路由和動態(tài)獲取數(shù)據(jù)達到頁面更新的目的,整個應用的使用過程中,頁面只是局部刷新。 一, 什么是單頁面應用 通俗的來講,就是一個應用只有一個頁面,用戶通過切換路由和動態(tài)獲取數(shù)據(jù)達到頁面更新的目的,整個應用的使用過程中,頁面只是局部刷新。在整個應用初始加載時,會一次性加載所有靜態(tài)文件或所有公共靜態(tài)文件(切換頁面時,加載相應...
閱讀 2511·2021-09-26 10:18
閱讀 3386·2021-09-22 10:02
閱讀 3183·2019-08-30 15:44
閱讀 3326·2019-08-30 15:44
閱讀 1831·2019-08-29 15:25
閱讀 2572·2019-08-26 14:04
閱讀 2035·2019-08-26 12:15
閱讀 2437·2019-08-26 11:43