Vue.js?前端路由和異步組件介紹
文章目標
P6
- 針對
react/vue能夠根據(jù)業(yè)務需求口噴router的關鍵配置,包括但不限于:路由的匹配規(guī)則、路由守衛(wèi)、路由分層等; - 能夠描述清楚
history的主要模式,知道history和router的邊界;
P6+ ~ P7
- 在沒有路由的情況下,也可以根據(jù)也無需求,實現(xiàn)一個簡單的路由;
- 讀過
router底層的源碼,不要求每行都讀,可以口噴關鍵代碼即可;
一、背景
遠古時期,當時前后端還是不分離的,路由全部都是由服務端控制的,前端代碼和服務端代碼過度融合在一起。
客戶端 --> 前端發(fā)起 http 請求 --> 服務端 --> url 路徑去匹配不同的路由 --> 返回不同的數(shù)據(jù)。
這種方式的缺點和優(yōu)點都非常明顯:
- 優(yōu)點:因為直接返回一個
html,渲染了頁面結(jié)構(gòu)。SEO的效果非常好,首屏時間特別快;- 在瀏覽器輸入一個
url開始到頁面任意元素加載出來/渲染出來-->首屏時間;
- 在瀏覽器輸入一個
- 缺點:前端代碼和服務端代碼過度融合在一起,開發(fā)協(xié)同非常的亂。服務器壓力大,因為把構(gòu)建
html的工作放在的服務端;
后來 ...隨之 ajax 的流行,異步數(shù)據(jù)請求可以在瀏覽器不刷新的情況下進行。
后來 ...出現(xiàn)了更高級的體驗 —— 單頁應用。
- 頁
-->HTML文件 - 單頁
-->單個HTML文件
在單頁應用中,不僅在頁面中的交互是不刷新頁面的,就連頁面跳轉(zhuǎn)也都是不刷新頁面的。
單頁應用的特點:
- 頁面中的交互是不刷新的頁面的,比如點擊按鈕,比如點擊出現(xiàn)一個彈窗;
- 多個頁面間的交互,不需要刷新頁面(
a/b/c,a-> b -> c);加載過的公共資源,無需再重復加載;
而支持起單頁應用這種特性的,就是 前端路由。
二、前端路由特性
前端路由的需求是什么?
- 根據(jù)不同的
url渲染不同內(nèi)容; - 不刷新頁面;
也就是可以在改變 url 的前提下,保證頁面不刷新。
三、面試?。?!
Hash 路由和 History 路由的區(qū)別?
hash有#,history沒有#;hash的#部分內(nèi)容不會給服務端,主要一般是用于錨點,history的所有內(nèi)容都會給服務端;hash路由是不支持SSR的,history路由是可以的;hash通過hashchange監(jiān)聽變化,history通過popstate監(jiān)聽變化;
四、Hash 原理及實現(xiàn)
1、特性
hash 的出現(xiàn)滿足了這個需求,他有以下幾種特征:
url中帶有一個#符號,但是#只是瀏覽器端/客戶端的狀態(tài),不會傳遞給服務端;- 客戶端路由地址
www.baidu.com/#/user-->通過http請求-->服務端接收到的www.baidu.com/ - 客戶端路由地址
www.baidu.com/#/list/detail/1-->通過http請求-->服務端接收到的www.baidu.com/
- 客戶端路由地址
hash值的更改,不會導致頁面的刷新;
location.hash = '#aaa'; location.hash = '#bbb'; // 從 #aaa 到 #bbb,頁面是不會刷新的
- 不同
url會渲染不同的頁面; hash值的更改,會在瀏覽器的訪問歷史中添加一條記錄,所以我們才可以通過瀏覽器的返回、前進按鈕來控制hash的切換;hash值的更改,會觸發(fā)hashchange事件;
location.hash = '#aaa';
location.hash = '#bbb';
window.addEventLisenter('hashchange', () => {});2、如何更改 hash
我們同樣有兩種方式來控制 hash 的變化:
location.hash的方式:
location.hash = '#aaa'; location.hash = '#bbb';
html標簽的方式:
<a href="#user" rel="external nofollow" > 點擊跳轉(zhuǎn)到 user </a> <!-- 等同于下面的寫法 --> location.hash = '#user';
3、手動實現(xiàn)一個基于 hash 的路由
./index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./index.css" rel="external nofollow" />
</head>
<body>
<div class="container">
<a href="#gray" rel="external nofollow" >灰色</a>
<a href="#green" rel="external nofollow" >綠色</a>
<a href="#" rel="external nofollow" >白色</a>
<button onclick="window.history.go(-1)">返回</button>
</div>
<script type="text/javascript" src="index.js"></script>
</body>
</html>./index.css
.container {
width: 100%;
height: 60px;
display: flex;
justify-content: space-around;
align-items: center;
font-size: 18px;
font-weight: bold;
background: black;
color: white;
}
a:link,
a:hover,
a:active,
a:visited {
text-decoration: none;
color: white;
}./index.js
/*
期望看到的效果:點擊三個不同的 a 標簽,頁面的背景顏色會隨之變化
*/
class BaseRouter {
constructor() {
this.routes = {}; // 存儲 path 以及 callback 的對應關系
this.refresh = this.refresh.bind(this); // 如果不 bind 的話,refresh 方法中的 this 指向 window
// 處理頁面 hash 變化,可能存在問題:頁面首次進來可能是 index.html,并不會觸發(fā) hashchange 方法
window.addEventListener('hashchange', this.refresh);
// 處理頁面首次加載
window.addEventListener('load', this.refresh);
}
/**
* route
* @param {*} path 路由路徑
* @param {*} callback 回調(diào)函數(shù)
*/
route(path, callback) {
console.log('========= route 方法 ========== ', path);
// 向 this.routes 存儲 path 以及 callback 的對應關系
this.routes[path] = callback || function () {};
}
refresh() {
// 刷新頁面
const path = `/${location.hash.slice(1) || ''}`;
console.log('========= refresh 方法 ========== ', path);
this.routes[path]();
}
}
const body = document.querySelector('body');
function changeBgColor(color) {
body.style.backgroundColor = color;
}
const Router = new BaseRouter();
Router.route('/', () => changeBgColor('white'));
Router.route('/green', () => changeBgColor('green'));
Router.route('/gray', () => changeBgColor('gray'));五、History 原理及實現(xiàn)
hash 有個 # 符號,不美觀,服務端無法接受到 hash 路徑和參數(shù)。
歷史的車輪無情攆過 hash,到了 HTML5 時代,推出了 History API。
1、HTML5 History 常用的 API
window.history.back(); // 后退 window.history.forward(); // 前進 window.history.go(-3); // 接收 number 參數(shù),后退 N 個頁面 window.history.pushState(null, null, path); window.history.replaceState(null, null, path);
其中最主要的兩個 API 是 pushState 和 replaceState,這兩個 API 都可以在不刷新頁面的情況下,操作瀏覽器歷史記錄。
不同的是,pushState 會增加歷史記錄,replaceState 會直接替換當前歷史記錄。
2、pushState/replaceState 的參數(shù)
pushState:頁面的瀏覽記錄里添加一個歷史記錄;replaceState:替換當前歷史記錄;
他們的參數(shù)是?樣的,三個參數(shù)分別是:
state:是一個對象,是一個與指定網(wǎng)址相關的對象,當popstate事件觸發(fā)的時候,該對象會傳入回調(diào)函數(shù);title:新頁面的標題,瀏覽器支持不一,建議直接使用null;url:頁面的新地址;
3、History 的特性
History API 有以下幾個特性:
- 沒有
#; history.pushState()或history.replaceState()不會觸發(fā)popstate事件,這時我們需要手動觸發(fā)頁面渲染;- 可以使用
history.popstate事件來監(jiān)聽url的變化; - 只有用戶點擊瀏覽器 倒退按鈕 和 前進按鈕,或者使用
JavaScript調(diào)用back、forward、go方法時才會觸發(fā)popstate;
4、面試?。?!
pushState時,會觸發(fā)popstate嗎?pushState/replaceState并不會觸發(fā)popstate事件,這時我們需要手動觸發(fā)頁面的重新渲染;
我們可以使用
popstate來監(jiān)聽url的變化;popstate到底什么時候才能觸發(fā):- 點擊瀏覽器后退按鈕;
- 點擊瀏覽器前進按鈕;
js調(diào)用back方法;js調(diào)用forward方法;js調(diào)用go方法;
5、手動實現(xiàn)一個基于 History 的路由
./index.html
./index.css
.container {
width: 100%;
height: 60px;
display: flex;
justify-content: space-around;
align-items: center;
font-size: 18px;
font-weight: bold;
background: black;
color: white;
}
a:link,
a:hover,
a:active,
a:visited {
text-decoration: none;
color: white;
}./index.js
class BaseRouter {
constructor() {
this.routes = {};
// location.href; => hash 的方式
console.log('location.pathname ======== ', location.pathname); // http://127.0.0.1:8080/green ==> /green
this.init(location.pathname);
this._bindPopState();
}
init(path) {
// pushState/replaceState 不會觸發(fā)頁面的渲染,需要我們手動觸發(fā)
window.history.replaceState({ path }, null, path);
const cb = this.routes[path];
if (cb) {
cb();
}
}
route(path, callback) {
this.routes[path] = callback || function () {};
}
// ! 跳轉(zhuǎn)并執(zhí)行對應的 callback
go(path) {
// pushState/replaceState 不會觸發(fā)頁面的渲染,需要我們手動觸發(fā)
window.history.pushState({ path }, null, path);
const cb = this.routes[path];
if (cb) {
cb();
}
}
// ! 演示一下 popstate 事件觸發(fā)后,會發(fā)生什么
_bindPopState() {
window.addEventListener('popstate', e => {
/*
觸發(fā)條件:
1、點擊瀏覽器前進按鈕
2、點擊瀏覽器后退按鈕
3、js 調(diào)用 forward 方法
4、js 調(diào)用 back 方法
5、js 調(diào)用 go 方法
*/
console.log('popstate 觸發(fā)了');
const path = e.state && e.state.path;
console.log('path >>> ', path);
this.routes[path] && this.routes[path]();
});
}
}
const Router = new BaseRouter();
const body = document.querySelector('body');
const container = document.querySelector('.container');
function changeBgColor(color) {
body.style.backgroundColor = color;
}
Router.route('/', () => changeBgColor('white'));
Router.route('/gray', () => changeBgColor('gray'));
Router.route('/green', () => changeBgColor('green'));
container.addEventListener('click', e => {
if (e.target.tagName === 'A') {
e.preventDefault();
console.log(e.target.getAttribute('href')); // /gray /green 等等
Router.go(e.target.getAttribute('href'));
}
});六、Vue-Router
1、router 使用
使用 Vue.js,我們已經(jīng)可以通過組合組件來組成應用程序,當你要把 Vue Router 添加進來,我們需要做的是,將組件(components)映射到路由(routes),然后告訴 Vue Router 在哪里渲染它們。
舉個例子:
<!-- 路由匹配到的組件將渲染在這里 --> <div id="app"> <router-view></router-view> </div>
// 如果使用模塊化機制編程,導入 Vue 和 VueRouter,要調(diào)用 Vue.use(VueRouter)
// 1、定義(路由)組件
// 可以從其他文件 import 進來
const Foo = { template: '<div>foo</div>' };
const Bar = { template: '<div>bar</div>' };
// 2、定義路由
//每個路由應該映射一個組件,其中 component 可以是通過 Vue.extend() 創(chuàng)建的組件構(gòu)造器,或者只是一個組件配置對象
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar },
];
// 3、創(chuàng)建 router 實例,然后傳 routes 配置
const router = new VueRouter({
routes,
});
// 4、創(chuàng)建和掛載根實例
// 記得要通過 router 配置參數(shù)注入路由,從而讓整個應用都有路由功能
const app = new Vue({
router,
}).$mount('#app');2、動態(tài)路由匹配
我們經(jīng)常需要把某種模式匹配到的所有路由,全部映射到同個組件,比如用戶信息組件,不同用戶使用同一個組件。
可以通過 $route.params.id 或者參數(shù)。
const router = new VueRouter({
routes: [
// 動態(tài)路徑參數(shù),以冒號開頭
{ path: '/user/:id', component: User },
],
});
const User = {
template: '<div>User: {{ $route.params.id }}</div>',
};3、響應路由參數(shù)的變化
復用組件時,想對 路由參數(shù) 的變化作出響應的話,可以使用 watch 或者 beforeRouteUpdate:
舉個例子:
const User = {
template: '...',
watch: {
$route(to, from) {
// 對路由變化作出響應...
},
},
};
const User = {
template: '...',
beforeRouteUpdate(to, from, next) {
// 對路由變化作出響應...
// don't forget to call next()
},
};4、捕獲所有路由或 404 Not found 路由
當時用通配符路由時,請確保路由的順序是正確的,也就是說含有通配符的路由應該在 最后。
舉個例子:
5、導航守衛(wèi)
vue-router 提供的導航守衛(wèi)主要用來通過跳轉(zhuǎn)或取消的方式守衛(wèi)導航。有多種方式植入路由導航過程中:
- 全局的
- 全局前置守衛(wèi):
router.beforeEach - 全局解析守衛(wèi):
router.beforeResolve - 全局后置鉤子:
router.afterEach
- 全局前置守衛(wèi):
- 單個路由獨享的
- 路由獨享守衛(wèi):
beforeEnter
- 路由獨享守衛(wèi):
- 組件級的
beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave
6、完整的導航解析流程
- 導航被觸發(fā);
- 在失活的組件里調(diào)用離開守衛(wèi)(前一個組件的
beforeRouteLeave); - 調(diào)用全局的
beforeEach守衛(wèi); - 在重用的組件里調(diào)用
beforeRouteUpdate守衛(wèi); - 在路由配置里調(diào)用
beforeEnter; - 解析異步路由組件;
- 在被激活的組件里調(diào)用
beforeRouterEnter; - 調(diào)用全局的
beforeResolve守衛(wèi); - 導航被確認;
- 調(diào)用全局的
afterEach鉤子; - 觸發(fā)
DOM更新; - 用創(chuàng)建好的實例調(diào)用
beforeRouterEnter守衛(wèi)中傳給next的回調(diào)函數(shù);
舉個例子:
// 全局
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes,
});
// 全局的導航守衛(wèi)
router.beforeEach((to, from, next) => {
console.log(`Router.beforeEach => from=${from.path}, to=${to.path}`);
// 可以設置頁面的 title
document.title = to.meta.title || '默認標題';
// 執(zhí)行下一個路由導航
next();
});
router.afterEach((to, from) => {
console.log(`Router.afterEach => from=${from.path}, to=${to.path}`);
});
// 路由獨享
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// 配置數(shù)組里針對單個路由的導航守衛(wèi)
console.log(`TestComponent route config beforeEnter => from=${from.path}, to=${to.path}`);
next();
},
},
],
});
// 組件
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染該組件的對應路由被 comfirm 前調(diào)用
// 不!能!獲取組件實例 this,因為當守衛(wèi)執(zhí)行前,組件實例還沒被調(diào)用
},
beforeRouteUpdate(to, from, next) {
// 在當前路由改變,但是該組件被復用時調(diào)用
// 舉個例子來說,對于一個帶有動態(tài)參數(shù)的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉(zhuǎn)的時候
// 由于會渲染同樣的 Foo 組件,因此組件實例會被復用。而這個鉤子就會在這個情況下被調(diào)用
// 可以訪問組件實例 this
},
beforeRouteLeave(to, from, next) {
// 導航離開該組件的對應路由時調(diào)用
// 可以訪問組件實例 this
},
};next 必須調(diào)用:
next():進行管道中的下一個鉤子。如果全部鉤子執(zhí)行完了,則導航的狀態(tài)就是confirmed(確認的)。next(false):中斷當前的導航。如果瀏覽器的URL改變了(可能是用戶手動或者瀏覽器后退按鈕),那么URL地址會重置到from路由對應的地址。next("/")或者next({ path: "/" }):跳轉(zhuǎn)到一個不同的地址。當前的導航被中斷,然后進行一個新的導航??梢韵?nbsp;next傳遞任意位置對象,且允許設置諸如replace: true、name: "home"之類的選項以及任何用在router-link的to prop或router.push中的選項。next(error):如果傳入next的參數(shù)是一個Error實例,則導航會被終止且該錯誤會被傳遞給router.onError()注冊過的回調(diào)。
7、導航守衛(wèi)執(zhí)行順序(面試?。。。?/h3>
- 【組件】前一個組件的
beforeRouteLeave - 【全局】的
router.beforeEach- 【組件】如果是路由參數(shù)變化,觸發(fā)
beforeRouteUpdate
- 【配置文件】里,下一個的
beforeEnter - 【組件】內(nèi)部聲明的
beforeRouteEnter - 【全局】的
router.afterEach
beforeRouteLeaverouter.beforeEach- 【組件】如果是路由參數(shù)變化,觸發(fā)
beforeRouteUpdate
beforeEnterbeforeRouteEnterrouter.afterEach8、滾動行為(面試?。。。?/h3>
vue-router 里面,怎么記住前一個頁面的滾動條的位置???
使用前端路由,當切換到新路由時,想要頁面滾動到頂部,或者是保持原先的滾動位置,就像重新加載頁面那樣。
Vue-router 能做到,而且更好,它讓你可以自定義路由切換時頁面如何滾動。
【注意】:這個功能只在支持 history.pushState 的瀏覽器中可用。
scrollBehavior 生效的條件:
- 瀏覽器支持
history API; - 頁面間的交互是通過
go,forward,back或者 瀏覽器的前進/返回按鈕;
window.history.back(); // 后退 window.history.forward(); // 前進 window.history.go(-3); // 接收 number 參數(shù),后退 N 個頁面
舉個例子:
// 1. 記?。菏謩狱c擊瀏覽器返回或者前進按鈕,記住滾動條的位置,基于 history API 的,其中包括:go、back、forward、手動點擊瀏覽器返回或者前進按鈕
// 2. 沒記?。簉outer-link,并沒有記住滾動條的位置
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes,
scrollBehavior: (to, from, savedPosition) => {
console.log(savedPosition); // 已保存的位置信息
return savedPosition;
},
});9、路由懶加載
當打包構(gòu)建應用時,JavaScript 包會變得非常大,影響頁面加載。如果我們能把不同路由對應的組件分割成不同的代碼塊,然后當路由被訪問的時候才加載對應組件,這樣就更加高效了。
舉個例子:
const Foo = () => import(/* webpackChunkName: "foo" */ './Foo.vue');
const router = new VueRouter({
routes: [{ path: '/foo', component: Foo }],
});到此這篇關于Vue.js 前端路由和異步組件介紹的文章就介紹到這了,更多相關Vue.js 異步組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue前端和Django后端如何查詢一定時間段內(nèi)的數(shù)據(jù)
這篇文章主要給大家介紹了關于vue前端和Django后端如何查詢一定時間段內(nèi)的數(shù)據(jù)的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-02-02
vue實現(xiàn)輸入框的模糊查詢的示例代碼(節(jié)流函數(shù)的應用場景)
這篇文章主要介紹了vue實現(xiàn)輸入框的模糊查詢的示例代碼(節(jié)流函數(shù)的應用場景),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-09-09
antd upload上傳組件如何獲取服務端返回數(shù)據(jù)
這篇文章主要介紹了antd upload上傳組件如何獲取服務端返回數(shù)據(jù)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02
Vue使用Proxy監(jiān)聽所有接口狀態(tài)的方法實現(xiàn)
這篇文章主要介紹了Vue使用Proxy監(jiān)聽所有接口狀態(tài)的方法實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-06-06
vue+element?ui表格添加多個搜索條件篩選功能(前端查詢)
這篇文章主要給大家介紹了關于vue+element?ui表格添加多個搜索條件篩選功能的相關資料,最近在使用element-ui的表格組件時,遇到了搜索框功能的實現(xiàn)問題,需要的朋友可以參考下2023-08-08
Springboot+Vue-Cropper實現(xiàn)頭像剪切上傳效果
這篇文章主要為大家詳細介紹了Springboot+Vue-Cropper實現(xiàn)頭像剪切上傳效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08
vue.js使用v-model實現(xiàn)父子組件間的雙向通信示例
這篇文章主要介紹了vue.js使用v-model實現(xiàn)父子組件間的雙向通信,結(jié)合實例形式分析了vue.js基于v-model父子組件間的雙向通信的具體實現(xiàn)技巧,需要的朋友可以參考下2020-02-02

