隨着React Vue
前端框架的興起,出現了Vue-router,react-router-dom等前端路由管理庫
,利用他們構建出來的單頁面應用,也是愈來愈接近原生的體驗,不再是之前的點擊標籤跳轉頁面,刷新整個頁面了,那麼他們的原理是什麼呢?
gitHub
開源練手項目:MPA
多頁面應用:文末還有新建的QQ以及微信羣哦~ 歡迎你們加入~~
jsp
,jade
,'ejs','tempalte'等技術在後臺先拼接成對應的HTML
結構,而後轉換成字符串,在每一個對應的路由返回對應的數據(文件)便可
Jade
模版服務端渲染,代碼實現:
const express= require('express') const app =express() const jade = require('jade') const result = *** const url path = *** const html = jade.renderFile(url, { data: result, urlPath })//傳入數據給模板引擎 app.get('/',(req,res)=>{ res.send(html)//直接吐渲染好的`html`文件拼接成字符串返回給客戶端 }) //RestFul接口 app.listen(3000,err=>{ //do something })
jQuery
等傳統庫繪製的前端頁面SEO
友好,由於返回給前端的是渲染好的HTML
結構,裏面的內容均可以被爬蟲抓取到。HTML
結構jQuery
寫的,若是註釋和文檔不是很是齊全,那麼真的會無從下手vue,react
等框架基礎上,他們都有一套本身的運行機制,有本身的生命週期,而且不像傳統的應用,還加上了一層虛擬DOM
以及diff
算法Ant-Design-pro
這樣的開箱即用的庫已經不少,單頁面應用的學習和開發成本已經很低很低,若是還在使用傳統的技術去開發新的應用,對於開發人員多心裏來講也是一種折磨。這裏並非說多頁面應用很差,只能說各有各自的好,單頁面應用若是經過大量的極致優化手段,是能夠從很多方面跟原生一拼。
DIV
標籤,其餘都是js
動態生態的內容index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <div id="root"></div> </body> <script> </script> </html>
vue react
框架的入口文件中指定對應的渲染元素:import React from 'react; import ReactDOM from 'react-dom'; ReactDOM.render( <App/>, document.querySelector("#root") )
react-router或者 react-router-dom,dva
等路由跳轉的庫<HashRouter>//這裏使用HashRouter <ErrorBoundary>//React錯誤邊界 <Switch> <Route path="/login" component={Login} /> <Route path="/home" component={Home} /> <Route path="/" component={NotFound} />//404路由或者重定向均可以 </Switch> </ErrorBoundary> </HashRouter>
url
地址發生變化,可是其實並無發送請求,也沒有刷新整個頁面url
地址欄會變化HashRouter
和BrowserRouter
兩種模式Hash
模式跳轉:hash 就是指 url 後的 # 號以及後面的字符。例如www.baidu.com/#segmentfault
,那麼#segmentfault
就是hash
值
須要用到的幾個知識點:css
window.location.hash
= '**'; // 設置當前的hash值const hash = window.location.hash
獲取當前的hash值hash
改變會觸發window
的hashchange
事件window.onhashchange=function(e){ let newURL = e.newURL; // 改變後的新 url地址 let oldURL = e.oldURL; // 改變前的舊 url地址 }
這裏特別注意,
hash
改變並不會發送請求
Hash
模式跳轉:ES6
的class實現:hash
值,對應不一樣的函數調用處理。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); } }
<div id="app"> <ul> <li> <a href="#/">home</a> </li> <li> <a href="#/about">about</a> </li> <li> <a href="#/topics">topics</a> </li> </ul> <div id="content"></div> </div> <script src="js/router.js"></script> <script> const router = new Router(); router.init(); router.route('/', function () { document.getElementById('content').innerHTML = 'Home'; }); router.route('/about', function () { document.getElementById('content').innerHTML = 'About'; }); router.route('/topics', function () { document.getElementById('content').innerHTML = 'Topics'; }); </script>
這樣一個簡單的
hash
模式路由就作好了,剩下的就是路由嵌套,以及錯誤邊界的處理
History
模式實現:History
來自Html5
的規範History
模式,url
地址欄的改變並不會觸發任何事件History
模式,可使用history.pushState
,history.replaceState
來控制url
地址,history.pushState() 和 history.replaceState() 的區別在於:html
history.pushState()
在保留現有歷史記錄的同時,將 url 加入到歷史記錄中。history.replaceState()
會將歷史記錄中的當前頁面歷史替換爲 url。History
模式下,刷新頁面會404,須要後端配合匹配一個任意路由,重定向到首頁,特別是加上Nginx
反向代理服務器的時候咱們須要換個思路,咱們能夠羅列出全部可能觸發 history 改變的狀況,而且將這些方式一一進行攔截,變相地監聽 history 的改變。
只要對上述三種狀況進行攔截,就能夠變相監聽到 history 的改變而作出調整。針對狀況 1,HTML5 規範中有相應的 onpopstate 事件,經過它能夠監聽到前進或者後退按鈕的點擊,值得注意的是,調用 history.push(replace)State 並不會觸發 onpopstate 事件。
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); } }
<div id="app"> <ul> <li><a data-href="/" href="#">home</a></li> <li><a data-href="/about" href="#">about</a></li> <li><a data-href="/topics" href="#">topics</a></li> </ul> <div id="content"></div> </div> <script src="js/router.js"></script> <script> const router = new Router(); router.init(); router.route('/', function() { document.getElementById('content').innerHTML = 'Home'; }); router.route('/about', function() { document.getElementById('content').innerHTML = 'About'; }); router.route('/topics', function() { document.getElementById('content').innerHTML = 'Topics'; }); </script>
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; } }
hash change
原生事件,將要卸載時候移除事件監聽防止內存泄漏hash
改變,就觸發全部對應hash
的回掉,全部的Router
都去更新視圖Router
組件中,都去對比當前的hash
值和這個組件的path
屬性,若是不同,那麼就返回null
,·不然就渲染這個組件對應的視圖History
模式的實現:這裏想多留些時間寫其餘源碼,這篇文章寫得很是好,你們也能夠去看看,本文不少借鑑他的。
withRouter
高階函數的源碼: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; };
若是以爲寫得好,記得點個贊哦,另外新建了微信和QQ羣,歡迎各位小哥哥小姐姐入駐~