Vue 异步组件
- defineAsyncComponent 函数
js
import { defineAsyncComponent } from "vue";
const AsyncComp = defineAsyncComponent(
() =>
new Promise((res, rej) => {
res({
template: "<div>hello</div>",
});
})
);
/* *** */
import { defineAsyncComponent } from "vue";
const LoginPopup = defineAsyncComponent(() => import("./LoginPopup.vue"));
/* *** */
const asyncPopup = defineAsyncComponent({
loader: () => import("./LoginPopup.vue"),
// 加载异步组件时要调用的组件
loadingComponent: LoadingComponent,
// 加载失败时的组件
errorComponent: errorComponent,
delay: 1000,
timeout: 1000 * 3,
});
- 使用 defineAsyncComponent 函数,可以定义一个异步组件,当组件加载完成时,会自动渲染组件.实现异步加载组件
- 如何与异步的
setup
函数结合使用
js
/* Vue3 中使用 */
<template>
<Suspense>
<login-popup />
<template #fallback>
<div>loading...</div>
</template>
</Suspense>
</template>;
const LoginPopup = defineAsyncComponent(() => import("./LoginPopup.vue"));
const getArticleInfo = async () => {
await new Promise((res) => setTimeout(res, 2000));
const article = {
title: "xxx",
};
return article;
};
export default {
async setup() {
const article = await getArticleInfo();
return {
article,
}
},
};
vue-router
- 什么是前端路由?
- 在 SPA 应用,描述的是 URL 和 UI 的映射关系,这种映射是单向的,即 URL 变,UI 变(无需刷新页面)
- 前端路由原理
- 如何改变 URL,不引起页面刷新
- 如何检测 URL 变化,同时触发 UI 更新
壹. 如何实现前端路由
-
hash 实现
- hash 是 URL 中 hash(#)及后面的内容,常用于锚点在页面内进行导航,改变 URL 的 hash 部分不会引起页面刷新
- 改变 URL 的方式
- 通过浏览器的前进、后退改变 URL
- 通过 a 标签改变 URL
- 通过 window.location.hash = xxx 改变 URL
-
history 实现
-
history 提供了 pushState 和 replaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新
-
history 提供了类似 hashchange 事件的 popState 事件
- popState 事件只会在浏览器前进、后退时触发,并不会在 pushState、replaceState 时触发
- 通过
pushState/replaceState
或者<a>
标签改变 URL 不会触发 popState 。好在我们可以拦截 pushState、replaceState 的调用及标签的点击事件来检测 URL 变化,在触发这个方法时,执行 hashchange 事件处理函数
* 通过 js 调用 history 的back
,go
,forward
方法或浏览器的前进、后退按钮时,会触发 popState 事件
-
[```html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Router</title> </head> <body>history 路由
window.addEventListener("hashchange", () => {
console.log("hashchange", location.hash);
let hashStr = window.location.hash.slice(1);
routerView.innerHTML = hashStr; // 更新视图
});
window.addEventListener("DOMContentLoaded", () => {
if (!location.hash) {
location.hash = "#/home"; // 默认路由为 home
} else {
let hashStr = window.location.hash.slice(1);
routerView.innerHTML = hashStr;
}
});
</script> -->
### 贰. vue-router 的使用](https://link.juejin.cn?target= "")
[](https://link.juejin.cn?target= "")[Vue Router](https://link.juejin.cn?target=https%3A%2F%2Frouter.vuejs.org%2Fzh%2Fguide%2F "https://router.vuejs.org/zh/guide/")
#### 实现一个 vue-router
* 分析 vue-router 的实现原理
* 首先是一个 class
* 其次是一个 vue 插件
* 实现 Vue.use(router)
* 需要判断插件是否已经注册过,如果已经注册过,则直接返回终止方法执行
* 最后需要将插件加入到 installedPlugins 中,保证插件不会被重复注册
* `$route`和`$router`: `$router`是`VueRouter`的实例对象,`$route`是当前路由对象;即`$route`是`$router`的`current`属性。注意每个组件
* 实现 install 方法
* 就是给 Vue 实例添加一些属性或方法
* 完善 VueRouter 类
* 有`$route`、`$router`
* 路由的映射表
* 实现 $router
* 我们通过`defineReactive`实现响应式更新,当`history`更改的时候,自动触发视图的修改,进而完成路由切换
* [johniexu.github.io/xx-blog/%E6...](https://link.juejin.cn?target=https%3A%2F%2Fjohniexu.github.io%2Fxx-blog%2F%25E6%2596%2587%25E7%25AB%25A0%25E4%25B8%2593%25E6%25A0%258F%2F%25E5%25B8%25A6%25E4%25BD%25A0%25E5%2585%25A8%25E9%259D%25A2%25E5%2588%2586%25E6%259E%2590vue-router%25E6%25BA%2590%25E7%25A0%2581%25EF%25BC%2588%25E4%25B8%2587%25E5%25AD%2597%25E9%2595%25BF%25E6%2596%2587%25EF%25BC%2589.html "https://johniexu.github.io/xx-blog/%E6%96%87%E7%AB%A0%E4%B8%93%E6%A0%8F/%E5%B8%A6%E4%BD%A0%E5%85%A8%E9%9D%A2%E5%88%86%E6%9E%90vue-router%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%87%E5%AD%97%E9%95%BF%E6%96%87%EF%BC%89.html")
```js
let Vue = null;
// 定义一个历史记录类
class HistoryRoute {
constructor(router) {
this.current = null; // 当前路由
// this.router = router; // 路由实例
}
}
// 定义一个VueRouter类
class VueRouter {
constructor(options) {
this.mode = options.mode || 'hash'; // 默认使用 hash 模式
this.routes = options.routes || []; // 传递的是一个数组表示的路由表
this.routesMap = this.createMap(this.routes); // 创建路由映射表
this.history = new HistoryRoute(this); // 创建历史记录对象
}
createMap(routes) {
return routes.reduce((memo, route) => {
memo[route.path] = route; // 将路由路径作为键,路由对象作为值
return memo;
}, {});
}
init() {
if (this.mode === 'hash') {
// 先判断用户打开的时候,有没有 hash 值,如果有就设置当前路由为 hash 值,没有就设置为根路径 '/'
// 监听 hashchange 事件,更新当前路由
location.hash ? '' : (location.hash = '/');
window.addEventListener('load', () => {
this.history.current = location.hash.slice(1);
});
window.addEventListener('hashchange', () => {
this.history.current = location.hash.slice(1);
});
} else {
location.pathname ? '' : (location.pathname = '/');
// 监听 popstate 事件,更新当前路由
window.addEventListener('load', () => {
this.history.current = location.pathname;
});
window.addEventListener('popstate', () => {
this.history.current = location.pathname;
});
}
}
}
VueRouter.install = function (v) {
Vue = v;
Vue.mixin({
beforeCreate() {
if (this.$options && this.$options.router) {
this._root = this; // 把当前实例挂载到 _root (根)上
this._router = this.$options.router; // 将 router 实例挂载到当前实例上
Vue.util.defineReactive(this, 'current', this._router.history); // 将当前路由对象设置为响应式
/**
* 1. 监听路由变化
* 2. 根据当前路由,渲染对应的组件
* 当我们第一次渲染 router-view的时候,可以获取到 this._router.history 对象,从而就会被监听到
* 就会把 router-view 组件的依赖 watcher 收集到 this._router.history 对象的 deps 中
* 这样当路由变化时,就会触发 this._router.history 的 notify 方法,从而触发 watcher 的 update 方法,从而重新渲染 router-view 组件
* */
} else {
this._root = this.$parent && this.$parent._root; // 如果没有 router 实例,则将根实例赋值给 _root
}
// 返回 $router 对象
Object.defineProperty(this, '$router', {
get() {
return this._root._router; // 获取当前路由实例
},
});
// 返回 $route 对象
Object.defineProperty(this, '$route', {
get() {
return this._root._router.history.current; // 获取当前路由
},
});
},
});
Vue.component('router-link', {
props: {
to: String, // 路由路径
},
render(h) {
let mode = this._self._root._router.mode;
let to = mode === 'hash' ? `#${this.to}` : this.to; // 根据路由模式生成链接
return h('a', { attrs: { href: to } }, this.$slots.default); // 渲染一个链接
},
});
Vue.component('router-view', {
render(h) {
// render 函数中的 this 指向的是一个 Proxy 对象,代理当前 Vue 组件
let current = this._self._root._router.history.current; // 获取当前路由
let routerMap = this._self._root._router.routesMap; // 获取路由映射表
return h(routerMap[current]);
},
});
};
export default VueRouter;
面试题
- 路由的 hash 和 history 模式的区别 Vue-Router 有两种模式:hash 模式和 history 模式。默认的路由模式是 hash 模式。
- hash 模式:
- hash 模式是开发中默认的模式,它的 URL 带着一个
#
,例如:http://www.abc.com/#/vue
,它的 hash 值就是#/vue
。 - hash 值会出现在 URL 里面,但是不会出现在 HTTP 请求中,对后端完全没有影响。所以改变 hash 值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的 IE 浏览器也支持这种模式。hash 路由被称为是前端路由,已经成为 SPA(单页面应用)的标配。
- 原理:hash 模式的主要原理就是
onhashchange()
事件
js
window.onhashchange = function (event) {
console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1); // 获取 hash 值
document.body.style.color = hash; // 改变 body 颜色
};
-
使用 onhashchange()事件的好处就是,在页面的 hash 值发生变化时,无需向后端发起请求,window 就可以监听事件的改变,并按规则加载相应的代码。除此之外,hash 值变化对应的 URL 都会被浏览器记录下来,这样浏览器就能实现页面的前进和后退。虽然是没有请求后端服务器,但是页面的 hash 值和对应的 URL 关联起来了。
-
history 模式:
-
history 模式的 URL 中没有#,它使用的是传统的路由分发模式,即用户在输入一个 URL 时,服务器会接收这个请求,并解析这个 URL,然后做出相应的逻辑处理。
-
当使用 history 模式时,URL 就像这样:
http://abc.com/user/id
。相比 hash 模式更加好看。但是,history 模式需要后台配置支持。如果后台没有正确配置,访问时会返回 404。 -
API:history api 可以分为两大部分,切换历史状态和修改历史状态:
- 修改历史状态:包括了 HTML5 History Interface 中新增的
pushState()
和replaceState()
方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了 url,但浏览器不会立即向后端发送请求。如果要做到改变 url 但又不刷新页面的效果,就需要前端用上这两个 API。 - 切换历史状态:包括
forward()
、back()
、go()
三个方法,对应浏览器的前进,后退,跳转操作。
- 修改历史状态:包括了 HTML5 History Interface 中新增的
-
虽然 history 模式丢弃了丑陋的
#
。但是,它也有自己的缺点,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出 404 来。 -
如果想要切换到 history 模式,就要进行以下配置(后端也要进行配置):
js
const router = new VueRouter({
mode:'history',
routes: [...]
})
- 两种模式对比:
- 调用
history.pushState()
相比于直接修改hash
,存在以下优势:pushState()
设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改#
后面的部分,因此只能设置与当前 URL 同文档的 URL;- pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
- pushState() 通过
stateObject
参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串; - pushState() 可额外设置 title 属性供后续使用。
- hash 模式下,仅 hash 符号之前的 url 会被包含在请求中,后端如果没有做到对路由的全覆盖,也不会返回 404 错误;history 模式下,前端的 url 必须和实际向后端发起请求的 url 一致,如果没有对用的路由处理,将返回 404 错误。
- hash 模式和 history 模式都有各自的优势和缺陷,还是要根据实际情况选择性的使用。
- 如何获取页面的 hash 变化
- 监听
$route
的变化
js
// 当路由发生变化的时候执行
watch:{
$route:{
handler(val,oldVal){
console.info(val,oldVal)
}
},
deep:true
}
window.location.hash
读取 hash 值,window.location.hash = xxx
设置 hash 值。window.location.hash
的值可读可写,读取来判断状态是否改变,写入时可以在不重载网页的前提下,添加一条历史访问记录。
route
和router
的区别
route
是"路由信息对象",包括path
,params
,hash
,query
,fullPath
,matched
,name
等路由信息参数。router
是"路由实例对象",包括了路由的跳转方法(push
、replace
)、钩子函数等。