路由Vue-router 及 异步组件

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 更新

壹. 如何实现前端路由

[```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 路由

routerView
</body>

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> -->

<script> /** * @Author: 花生 * @description: 基于 history 实现前端路由 * @return {*} */ let routerView = document.getElementById("routerView"); window.addEventListener("popstate", () => { console.log("popstate", location.pathname); let hash = location.hash.slice(1); routerView.innerHTML = location.pathname; // 更新视图 }); window.addEventListener("DOMContentLoaded", () => { routerView.innerHTML = location.pathname; // 更新视图 var linkList = document.querySelectorAll("a[href]"); // 获取所有链接 // 遍历所有链接,给每个链接添加点击事件 for (var i = 0; i < linkList.length; i++) { linkList[i].onclick = function (e) { e.preventDefault(); // 阻止默认行为 var href = this.getAttribute("href"); history.pushState({}, "", href); // 更新浏览器地址栏 routerView.innerHTML = href; // 更新视图 console.log("href", href); // 打印当前路由 }; } }); </script> </html>
复制代码
### 贰. 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;    

面试题

  1. 路由的 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()三个方法,对应浏览器的前进,后退,跳转操作。
  • 虽然 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 模式都有各自的优势和缺陷,还是要根据实际情况选择性的使用。
  1. 如何获取页面的 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 的值可读可写,读取来判断状态是否改变,写入时可以在不重载网页的前提下,添加一条历史访问记录。
  1. routerouter的区别
  • route是"路由信息对象",包括path,params,hash,query,fullPath,matched,name等路由信息参数。
  • router是"路由实例对象",包括了路由的跳转方法(pushreplace)、钩子函数等。
相关推荐
kite01212 分钟前
浏览器工作原理06 [#]渲染流程(下):HTML、CSS和JavaScript是如何变成页面的
javascript·css·html
крон4 分钟前
【Auto.js例程】华为备忘录导出到其他手机
开发语言·javascript·智能手机
coding随想2 小时前
JavaScript ES6 解构:优雅提取数据的艺术
前端·javascript·es6
年老体衰按不动键盘2 小时前
快速部署和启动Vue3项目
java·javascript·vue
小小小小宇2 小时前
一个小小的柯里化函数
前端
灵感__idea3 小时前
JavaScript高级程序设计(第5版):无处不在的集合
前端·javascript·程序员
小小小小宇3 小时前
前端双Token机制无感刷新
前端
小小小小宇3 小时前
重提React闭包陷阱
前端
小小小小宇3 小时前
前端XSS和CSRF以及CSP
前端
UFIT3 小时前
NoSQL之redis哨兵
java·前端·算法