頁面中全部的內容都是組件化的,只須要把路徑跟組件對應,在頁面中把組件渲染出來。html
頁面實現:在vue-router中, 它定義了兩個標籤 和來對應點擊和顯示部分。 就是定義頁面中點擊的部分, 定義顯示部分。前端
js中配置路由:首先要定義route,一條路由的實現,他是一個對象,由path和component組成。vue
這裏的兩條路由,組成routes:vue-router
const routes = [
{// 首頁
path: '/',
component: () => import('src/pages/home/index.vue'),
},
{// 首頁更多功能
path: '/functionManage',
component: () => import('src/pages/home/functionManage.vue'),
},
]
複製代碼
建立router對路由進行管理,它是由構造函數 new vueRouter() 建立,接受routes 參數。後端
router.js文件中數組
const router = new VueRouter({
routes,
})
複製代碼
配置完成後,把router 實例注入到 vue 根實例中。瀏覽器
main.js文件中bash
window.vm = new Vue({
router,
})
複製代碼
執行過程:當用戶點擊 router-link 標籤時,會去尋找它的 to 屬性, 它的 to 屬性和 js 中配置的路徑{ path: '/home', component: Home} path 一一對應,從而找到了匹配的組件, 最後把組件渲染到 標籤所在的地方。前端框架
前端路由是經過改變URL,在不從新請求頁面的狀況下,更新頁面視圖。服務器
目前在瀏覽器環境中這一功能的實現主要有2種:
vue-router 是 vue.js 框架的路由插件,它是經過 mode 這一參數控制路由的實現模式的。
const router = new VueRouter({
// HTML5 history 模式
mode: 'history',
base: process.env.NODE_ENV === 'production' ? process.env.PROXY_PATH : '',
routes,
})
複製代碼
在入口文件中須要實例化一個 VueRouter 的實例對象 ,而後將其傳入 Vue 實例的 options 中。
var VueRouter = function VueRouter (options) {
if ( options === void 0 ) options = {};
this.app = null;
this.apps = [];
this.options = options;
this.beforeHooks = [];
this.resolveHooks = [];
this.afterHooks = [];
// 建立 matcher 匹配函數
this.matcher = createMatcher(options.routes || [], this);
// 根據 mode 實例化具體的 History,默認爲'hash'模式
var mode = options.mode || 'hash';
// 經過 supportsPushState 判斷瀏覽器是否支持'history'模式
// 若是設置的是'history'可是若是瀏覽器不支持的話,'history'模式會退回到'hash'模式
// fallback 是當瀏覽器不支持 history.pushState 控制路由是否應該回退到 hash 模式。默認值爲 true。
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false;
if (this.fallback) {
mode = 'hash';
}
if (!inBrowser) {
// 不在瀏覽器環境下運行需強制爲'abstract'模式
mode = 'abstract';
}
this.mode = mode;
// 根據不一樣模式選擇實例化對應的 History 類
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base);
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback);
break
case 'abstract':
this.history = new AbstractHistory(this, options.base);
break
default:
{
assert(false, ("invalid mode: " + mode));
}
}
};
複製代碼
VueRouter.prototype.init = function init (app /* Vue component instance */) {
...
var history = this.history;
// 根據history的類別執行相應的初始化操做和監聽
if (history instanceof HTML5History) {
history.transitionTo(history.getCurrentLocation());
} else if (history instanceof HashHistory) {
var setupHashListener = function () {
history.setupListeners();
};
history.transitionTo(
history.getCurrentLocation(),
setupHashListener,
setupHashListener
);
}
history.listen(function (route) {
this$1.apps.forEach(function (app) {
app._route = route;
});
});
};
複製代碼
做爲參數傳入的字符串屬性mode只是一個標記,用來指示實際起做用的對象屬性history的實現類,二者對應關係:
modehistory:
'history': HTML5History;
'hash': HashHistory;
'abstract': AbstractHistory;
複製代碼
hash雖然出如今url中,但不會被包括在http請求中,它是用來指導瀏覽器動做的,對服務器端沒影響,所以,改變hash不會從新加載頁面。
能夠爲hash的改變添加監聽事件:
window.addEventListener("hashchange",funcRef,false)
每一次改變hash(window.location.hash),都會在瀏覽器訪問歷史中增長一個記錄。
function HashHistory (router, base, fallback) {
History$$1.call(this, router, base);
// 若是是從history模式降級來的,須要作降級檢查
if (fallback && checkFallback(this.base)) {
// 若是降級且作了降級處理,則返回
return
}
ensureSlash();
}
function checkFallback (base) {
// 獲得除去base的真正的 location 值
var location = getLocation(base);
if (!/^\/#/.test(location)) {
// 若是此時地址不是以 /# 開頭的
// 須要作一次降級處理,降爲 hash 模式下應有的 /# 開頭
window.location.replace(
cleanPath(base + '/#' + location)
);
return true
}
}
function ensureSlash () {
// 獲得 hash 值
var path = getHash();
// 若是是以 / 開頭的,直接返回便可
if (path.charAt(0) === '/') {
return true
}
// 不是的話,須要手動保證一次 替換 hash 值
replaceHash('/' + path);
return false
}
function getHash () {
// 由於兼容性的問題,這裏沒有直接使用 window.location.hash
// 由於 Firefox decode hash 值
var href = window.location.href;
var index = href.indexOf('#');
return index === -1 ? '' : href.slice(index + 1)
}
複製代碼
HashHistory.prototype.push = function push (location, onComplete, onAbort) {
var this$1 = this;
var ref = this;
var fromRoute = ref.current;
this.transitionTo(location, function (route) {
pushHash(route.fullPath);
handleScroll(this$1.router, route, fromRoute, false);
onComplete && onComplete(route);
}, onAbort);
};
複製代碼
transitionTo()方法是用來處理路由變化中的基礎邏輯的,push()方法最主要的是對window的hash進行了直接賦值:
function pushHash (path) {
window.location.hash = path
}
複製代碼
hash的改變會自動添加到瀏覽器的訪問歷史記錄中。 那麼視圖的更新是怎麼實現的呢,看下 transitionTo()方法:
History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort) {
var this$1 = this;
var route = this.router.match(location, this.current);
this.confirmTransition(route, function () {
this$1.updateRoute(route);
...
});
};
History.prototype.updateRoute = function updateRoute (route) {
var prev = this.current;
this.current = route;
this.cb && this.cb(route);
this.router.afterHooks.forEach(function (hook) {
hook && hook(route, prev);
});
};
History.prototype.listen = function listen (cb) {
this.cb = cb;
};
複製代碼
能夠看到,當路由變化時,調用this.cb方法,而this.cb方法是經過History.listen(cb)進行設置的,在init()中對其進行了設置:
Vue做爲漸進式的前端框架,自己的組件定義中應該是沒有有關路由內置屬性_route,若是組件中要有這個屬性,應該是在插件加載的地方,即VueRouter的install()方法中混入Vue對象的,install.js的源碼:
function install (Vue) {
...
Vue.mixin({
beforeCreate: function beforeCreate () {
if (isDef(this.$options.router)) {
this._routerRoot = this;
this._router = this.$options.router;
this._router.init(this);
Vue.util.defineReactive(this, '_route', this._router.history.current);
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
}
registerInstance(this, this);
},
destroyed: function destroyed () {
registerInstance(this);
}
});
}
複製代碼
經過Vue.mixin()方法,全局註冊一個混合,影響註冊以後全部建立的每一個Vue實例,該混合在beforeCreate鉤子中經過Vue.util.defineReactive()定義了響應式的_route屬性。所謂響應式屬性,即當_route值改變時,會自動調用Vue實例的render()方法,更新視圖。
$router.push()-->HashHistory.push()-->History.transitionTo()-->History.updateRoute()-->{app._route=route}-->vm.render()
replace()方法與push()方法不一樣之處在於,它並非將新路由添加到瀏覽器訪問歷史棧頂,而是替換掉當前的路由:
HashHistory.prototype.replace = function replace (location, onComplete, onAbort) {
var this$1 = this;
var ref = this;
var fromRoute = ref.current;
this.transitionTo(location, function (route) {
replaceHash(route.fullPath);
handleScroll(this$1.router, route, fromRoute, false);
onComplete && onComplete(route);
}, onAbort);
};
function replaceHash (path) {
const i = window.location.href.indexOf('#')
// 直接調用 replace 強制替換 以免產生「多餘」的歷史記錄
// 主要是用戶初次跳入 且hash值不是以 / 開頭的時候直接替換
// 其他時候和push沒啥區別 瀏覽器老是記錄hash記錄
window.location.replace(
window.location.href.slice(0, i >= 0 ? i : 0) + '#' + path
)
}
複製代碼
能夠看出,它與push()的實現結構基本類似,不一樣點它不是直接對window.location.hash進行賦值,而是調用window.location.replace方法將路由進行替換。
上面的VueRouter.push()和VueRouter.replace()是能夠在vue組件的邏輯代碼中直接調用的,除此以外在瀏覽器中,用戶還能夠直接在瀏覽器地址欄中輸入改變路由,所以還須要監聽瀏覽器地址欄中路由的變化 ,並具備與經過代碼調用相同的響應行爲,在HashHistory中這一功能經過setupListeners監聽hashchange實現:
setupListeners () {
window.addEventListener('hashchange', () => {
if (!ensureSlash()) {
return
}
this.transitionTo(getHash(), route => {
replaceHash(route.fullPath)
})
})
}
複製代碼
該方法設置監聽了瀏覽器事件hashchange,調用的函數爲replaceHash,即在瀏覽器地址欄中直接輸入路由至關於代碼調用了replace()方法。
History interface是瀏覽器歷史記錄棧提供的接口,經過back(),forward(),go()等方法,咱們能夠讀取瀏覽器歷史記錄棧的信息,進行各類跳轉操做。
HTML5引入了history.pushState()和history.replaceState()方法,他們分別能夠添加和修改歷史記錄條目。這些方法一般與window.onpopstate配合使用。
window.history.pushState(stateObject,title,url)
window.history,replaceState(stateObject,title,url)
複製代碼
pushState和replaceState兩種方法的共同特色:當調用他們修改瀏覽器歷史棧後,雖然當前url改變了,但瀏覽器不會當即發送請求該url,這就爲單頁應用前端路由,更新視圖但不從新請求頁面提供了基礎。
export function pushState (url?: string, replace?: boolean) {
saveScrollPosition()
// 加了 try...catch 是由於 Safari 有調用 pushState 100 次限制
// 一旦達到就會拋出 DOM Exception 18 錯誤
const history = window.history
try {
if (replace) {
// replace 的話 key 仍是當前的 key 不必生成新的
history.replaceState({ key: _key }, '', url)
} else {
// 從新生成 key
_key = genKey()
// 帶入新的 key 值
history.pushState({ key: _key }, '', url)
}
} catch (e) {
// 達到限制了 則從新指定新的地址
window.location[replace ? 'replace' : 'assign'](url)
}
}
// 直接調用 pushState 傳入 replace 爲 true
export function replaceState (url?: string) {
pushState(url, true)
}
複製代碼
代碼結構以及更新視圖的邏輯與hash模式基本相似,只不過將對window.location.hash()直接進行賦值window.location.replace()改成了調用history.pushState()和history.replaceState()方法。
在HTML5History中添加對修改瀏覽器地址欄URL的監聽popstate是直接在構造函數中執行的:
constructor (router: Router, base: ?string) {
window.addEventListener('popstate', e => {
const current = this.current
this.transitionTo(getLocation(this.base), route => {
if (expectScroll) {
handleScroll(router, route, current, true)
}
})
})
}
複製代碼
以上就是'hash'和'history'兩種模式,都是經過瀏覽器接口實現的。
通常的需求場景中,hash模式與history模式是差很少的,根據MDN的介紹,調用history.pushState()相比於直接修改hash主要有如下優點:
'abstract'模式,不涉及和瀏覽器地址的相關記錄,流程跟'HashHistory'是同樣的,其原理是經過數組模擬瀏覽器歷史記錄棧的功能
// 對於 go 的模擬
go (n: number) {
// 新的歷史記錄位置
const targetIndex = this.index + n
// 超出返回了
if (targetIndex < 0 || targetIndex >= this.stack.length) {
return
}
// 取得新的 route 對象
// 由於是和瀏覽器無關的 這裏獲得的必定是已經訪問過的
const route = this.stack[targetIndex]
// 因此這裏直接調用 confirmTransition 了
// 而不是調用 transitionTo 還要走一遍 match 邏輯
this.confirmTransition(route, () => {
// 更新
this.index = targetIndex
this.updateRoute(route)
})
}
複製代碼
hash模式僅改變hash部分的內容,而hash部分是不會包含在http請求中的(hash帶#):
http://oursite.com/#/user/id //如請求,只會發送http://oursite.com/
因此hash模式下遇到根據url請求頁面不會有問題
而history模式則將url修改的就和正常請求後端的url同樣(history不帶#)
http://oursite.com/user/id
若是這種向後端發送請求的話,後端沒有配置對應/user/id的get路由處理,會返回404錯誤。
官方推薦的解決辦法是在服務端增長一個覆蓋全部狀況的候選資源:若是 URL 匹配不到任何靜態資源,則應該返回同一個 index.html 頁面,這個頁面就是你 app 依賴的頁面。同時這麼作之後,服務器就再也不返回 404 錯誤頁面,由於對於全部路徑都會返回 index.html 文件。爲了不這種狀況,在 Vue 應用裏面覆蓋全部的路由狀況,而後在給出一個 404 頁面。或者,若是是用 Node.js 做後臺,可使用服務端的路由來匹配 URL,當沒有匹配到路由的時候返回 404,從而實現 fallback。