自己動手實現一個前端路由
單頁面應用利用了JavaScript動態變換網頁內容,避免了頁面過載,提供了流暢的使用者體驗;路由則提供了瀏覽器地址變化,網頁內容也跟隨變化,兩者結合起來則為我們提供了體驗良好的單頁面web應用
前端路由實現方式
前端路由需要實現三個功能:①瀏覽器地址變化,切換頁面;②點選瀏覽器【後退】、【前進】按鈕,網頁內容跟隨變化;③重新整理瀏覽器,網頁載入當前路由對應內容
主要有兩種實現方式:
hash
路由: 監聽瀏覽器地址hash值變化,執行相應的js切換網頁history
路由: 利用history API實現url地址改變,網頁內容改變
hash路由
首先定義一個Router
類
class Router { constructor(obj) { // 路由模式 this.mode = obj.mode // 配置路由 this.routes = { '/index': 'views/index/index', '/index/detail': 'views/index/detail/detail', '/index/detail/more': 'views/index/detail/more/more', '/subscribe': 'views/subscribe/subscribe', '/proxy': 'views/proxy/proxy', '/state': 'views/state/stateDemo', '/state/sub': 'views/state/components/subState', '/dom': 'views/visualDom/visualDom', '/error': 'views/error/error' } } } 複製程式碼
路由初始化時監聽load
,hashchange
兩個事件:
window.addEventListener('load', this.hashRefresh.bind(this), false); window.addEventListener('hashchange', this.hashRefresh.bind(this), false); 複製程式碼
瀏覽器地址hash值變化直接通過a標籤連結實現
<nav id="nav" class="nav-tab"> <ul class='tab'> <li><a class='nav-item' href="#/index">首頁</a></li> <li><a class='nav-item' href="#/subscribe">觀察者</a></li> <li><a class='nav-item' href="#/proxy">代理</a></li> <li><a class='nav-item' href="#/state">狀態管理</a></li> <li><a class='nav-item' href="#/dom">虛擬DOM</a></li> </ul> </nav> <div id="container" class='container'> <div id="main" class='main'></div> </div> 複製程式碼
hash值變化後,回撥方法:
/** * hash路由重新整理執行 */ hashRefresh() { // 獲取當前路徑,去掉查詢字串,預設'/index' var currentURL = location.hash.slice(1).split('?')[0] || '/index'; this.name = this.routes[this.currentURL] this.controller(this.name) } /** * 元件控制器 * @param {string} name */ controller(name) { // 獲得相應元件 var Component = require('../' + name).default; // 判斷是否已經配置掛載元素,預設為$('#main') var controller = new Component($('#main')) } 複製程式碼
考慮到存在多級頁面巢狀路由的存在,需要對巢狀路由進行處理:
- 直接子頁面路由時,按父路由到子路由的順序載入頁面
- 父頁面已經載入,再載入子頁面時,父頁面保留,只加載子頁面
改造後的路由重新整理方法為:
hashRefresh() { // 獲取當前路徑,去掉查詢字串,預設'/index' var currentURL = location.hash.slice(1).split('?')[0] || '/index'; // 多級連結拆分為陣列,遍歷依次載入 this.currentURLlist = currentURL.slice(1).split('/') this.url = "" this.currentURLlist.forEach((item, index) => { // 導航選單啟用顯示 if (index === 0) { this.navActive(item) } this.url += "/" + item this.name = this.routes[this.url] // 404頁面處理 if (!this.name) { location.href = '#/error' return false } // 對於巢狀路由的處理 if (this.oldURL && this.oldURL[0]==this.currentURLlist[0]) { this.handleSubRouter(item,index) } else { this.controller(this.name) } }); // 記錄連結陣列,後續處理子級元件 this.oldURL = JSON.parse(JSON.stringify(this.currentURLlist)) } /** * 處理巢狀路由 * @param {string} item 連結list中當前項 * @param {number} index 連結list中當前索引 */ handleSubRouter(item,index){ // 新路由是舊路由的子級 if (this.oldURL.length < this.currentURLlist.length) { // 相同路由部分不重新載入 if (item !== this.oldURL[index]) { this.controller(this.name) } } // 新路由是舊路由的父級 if (this.oldURL.length > this.currentURLlist.length) { var len = Math.min(this.oldURL.length, this.currentURLlist.length) // 只重新載入最後一個路由 if (index == len - 1) { this.controller(this.name) } } } 複製程式碼
這樣,一個hash路由元件就實現了
使用時,只需new一個Router例項即可:new Router({mode:'hash'})
history 路由
window.history
屬性指向 History 物件,是瀏覽器的一個屬性,表示當前視窗的瀏覽歷史,History 物件儲存了當前視窗訪問過的所有頁面地址,出於安全原因,瀏覽器不允許指令碼直接讀取這些地址,只允許在地址見導航前進後退。更多瞭解History物件,可參考阮一峰老師的介紹:History 物件
webpack開發環境下,需要在devServer物件新增以下配置:
historyApiFallback: { rewrites: [ { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, ], } 複製程式碼
history路由主要是通過history.pushState()
方法向瀏覽記錄中新增一條歷史記錄,並同時觸發js回撥載入頁面
當【前進】、【後退】時,會觸發history.popstate
事件,載入history.state
中存放的路徑
history路由實現與hash路由的步驟類似,由於需要配置路由模式切換,頁面中所有的a連結都採用了hash型別連結,history路由初始化時,需要攔截a標籤的預設跳轉:
/** * history模式劫持 a連結 */ bindLink() { $('#nav').on('click', 'a.nav-item', this.handleLink.bind(this)) } /** * history 處理a連結 * @parame 當前物件Event */ handleLink(e) { e.preventDefault(); // 獲取元素路徑屬性 let href = $(e.target).attr('href') // 對非路由連結直接跳轉 if (href.slice(0, 1) !== '#') { window.location.href = href } else { let path = href.slice(1) history.pushState({ path: path }, null, path) // 載入相應頁面 this.loadView(path.split('?')[0]) } } 複製程式碼
history路由初始化需要繫結load
、popstate
事件
this.bindLink() window.addEventListener('load', this.loadView.bind(this, location.pathname)); window.addEventListener('popstate', this.historyRefresh.bind(this)); 複製程式碼
瀏覽是【前進】或【後退】時,觸發popstate
事件,執行回撥函式
/** * history模式重新整理頁面 * @parame當前物件Event */ historyRefresh(e) { const state = e.state || {} const path = state.path.split('?')[0] || null if (path) { this.loadView(path) } } 複製程式碼
history路由模式首次載入頁面時,可以預設一個頁面,這是可以用history.replaceState
方法
if (this.mode === 'history' && currentURL === '/') { history.replaceState({path: '/'}, null, '/') currentURL = '/index' } 複製程式碼
對於404頁面的處理,也類似
history.replaceState({path: '/error'}, null, '/error') this.loadView('/error') 複製程式碼
使用時new一個Router例項:new Router({mode:'history'})
ofollow,noindex">點選預覽
更多原始碼請訪問Github