單頁面應用利用了JavaScript動態變換網頁內容,避免了頁面重載;路由則提供了瀏覽器地址變化,網頁內容也跟隨變化,二者結合起來則爲咱們提供了體驗良好的單頁面web應用javascript
路由須要實現三個功能:html
①瀏覽器地址變化,切換頁面;前端
②點擊瀏覽器【後退】、【前進】按鈕,網頁內容跟隨變化;vue
③刷新瀏覽器,網頁加載當前路由對應內容java
在單頁面web網頁中,單純的瀏覽器地址改變,網頁不會重載,如單純的hash網址改變網頁不會變化,所以咱們的路由主要是經過監聽事件,並利用js實現動態改變網頁內容,有兩種實現方式:webpack
hash
路由: 監聽瀏覽器地址hash值變化,執行相應的js切換網頁 history
路由: 利用history API實現url地址改變,網頁內容改變git
首先定義一個Router
類github
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'
}
this.init()
}
}
複製代碼
路由初始化init()
時監聽load
,hashchange
兩個事件:web
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'))
}
複製代碼
有位同窗留言要實現路由懶加載,參考vue的實現方式,這裏貼出來,但願你們多提意見:
/** * 懶加載路由組件控制器 * @param {string} name */
controller(name) {
// import 函數會返回一個 Promise對象,屬於es7範疇,須要配合babel的syntax-dynamic-import插件使用
var Component = ()=>import('../'+ name);
Component().then(resp=>{
var controller = new resp.default($('#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[index]==this.currentURLlist[index]) {
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'})
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連接 * @param e 當前對象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模式刷新頁面 * @param e 當前對象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')
複製代碼
更多源碼請訪問Github