在 jQuery 时代,对于大部分 Web 项目而言,前端都是不能控制路由的,而是需要依赖后端项目的路由系统。通常,前端项目也会部署在后端项目的模板里,对于每次的页面跳转,都由后端开发人员来负责重新渲染模板。
前端依赖后端,并且前端不需要负责路由的这种开发方式:
目前的前端开发中,用户访问页面后代码执行的过程。
用户访问路由后,无论是什么 URL 地址,都直接渲染一个前端的入口文件 index.html,然后就会在 index.html 文件中加载 JS 和 CSS。之后,JavaScript 获取当前的页面地址,以及当前路由匹配的组件,再去动态渲染当前页面即可。用户在页面上进行点击操作时,也不需要刷新页面,而是直接通过 JS 重新计算出匹配的路由渲染即可。
在新架构下,前端获得了路由的控制权,在 JavaScript 中控制路由系统。也因此,页面跳转的时候就不需要刷新页面,网页的浏览体验也得到了提高。这种所有路由都渲染一个前端入口文件的方式,是单页面应用程序(SPA,single page application)应用的雏形。
通过 JavaScript 动态控制数据去提高用户体验的方式并不新奇,Ajax 让数据的获取不需要刷新页面,SPA 应用让路由跳转也不需要刷新页面。这种开发的模式在 jQuery 时代就出来了,浏览器路由的变化可以通过 pushState 来操作,这种纯前端开发应用的方式,以前称之为 Pjax (pushState+ Ajax)。之后,这种开发模式在 MVVM 框架的时代大放异彩,现在大部分使用 Vue/React/Angular 的应用都是这种架构。
SPA 应用相比于模板的开发方式,对前端更加友好,比如:前端对项目的控制权更大了、交互体验也更加丝滑,更重要的是,前端项目终于可以独立出来单独部署了。
单页应用在页面交互、页面跳转上都是无刷新的,这极大地提高了用户访问网页的体验。为了实现单页应用,前端路由的需求也变得重要了起来。
类似于服务端路由,前端路由实现起来其实也很简单,就是匹配不同的 URL 路径,进行解析,然后动态地渲染出区域 HTML 内容。但是这样存在一个问题,就是 URL 每次变化的时候,都会造成页面的刷新。解决这一问题的思路便是在改变 URL 的情况下,保证页面的不刷新。
通过 URL 区分路由的机制上,有两种实现方式:
#
后面的内容做区分在 vue-router 中对应两个函数,分别是 createWebHashHistory 和 createWebHistory:
在 2014 年之前,大家是通过 hash 来实现前端路由,URL hash 中的 #
就是类似于下面代码中的这种 #
:
http://cellinlab.xyz/#/login
在进行页面跳转的操作时,hash 值的变化并不会导致浏览器页面的刷新,只是会触发 hashchange 事件。通过对 hashchange 事件的监听,就可以进行动态地页面切换:
window.addEventListener('hashchange',fn)
HTML5 标准发布,浏览器多了两个 API:pushState 和 replaceState。通过这两个 API ,可以改变 URL 地址,并且浏览器不会向后端发送请求,同时还会触发 popstate 事件。
通过这两个 API 和 popstate 事件,就能用另外一种方式实现前端路由。
window.addEventListener('popstate', fn)
src/router/ToyRouterView.vue
<template>
<component :is="comp"></component>
</template>
<script setup>
import { computed } from 'vue';
import { useRouter } from './toyrouter';
let router = useRouter();
const comp = computed(() => {
const route = router.routes.find((route) => {
return route.path === router.current.value;
});
return route? route.component : null;
});
</script>
src/router/ToyRouterLink.vue
<template>
<a :href="'#'+props.to">
<slot />
</a>
</template>
<script setup>
import { defineProps } from 'vue';
let props = defineProps({
to: {
type: String,
required: true
}
});
</script>
src/router/toyrouter.js
import { ref, inject } from 'vue';
import ToyRouterLinkVue from './ToyRouterLink.vue';
import ToyRouterViewVue from './ToyRouterView.vue';
const ROUTE_KEY = '__router__';
function createRouter(options) {
return new Router(options);
}
function useRouter () {
return inject(ROUTE_KEY);
}
function createWebHashHistory () {
function bindEvents(fn) {
window.addEventListener('hashchange', fn);
}
return {
bindEvents,
url: window.location.hash.slice(1) || '/',
}
}
class Router {
constructor(options) {
this.history = options.history;
this.routes = options.routes;
this.current = ref(this.history.url);
this.history.bindEvents(() => {
this.current.value = window.location.hash.slice(1);
});
}
install (app) {
app.provide(ROUTE_KEY, this);
app.component('ToyRouterLink', ToyRouterLinkVue);
app.component('ToyRouterView', ToyRouterViewVue);
}
}
export {
createRouter,
useRouter,
createWebHashHistory,
};
在 src/App.vue
使用
<template>
<div>
<toy-router-link to="/">Home</toy-router-link>
<toy-router-link to="/about">About</toy-router-link>
</div>
<toy-router-view></toy-router-view>
</template>
在路由匹配的语法上,vue-router 支持动态路由。
例如有一个用户页面,这个页面使用的是 User 组件,但是每个用户的信息都不一样,需要给每一个用户配置单独的路由入口。
const routes = [
{
path: '/user/:id',
component: User,
},
]
对于有些页面来说,只有管理员才可以访问,普通用户访问时,会提示没有权限。可以使用 vue-router 的导航守卫功能了,在访问路由页面之前进行权限认证,这样可以做到对页面的控制。
在项目庞大之后,如果首屏加载文件太大,那么就可能会影响到性能。可以使用 vue-router 的动态导入功能,把不常用的路由组件单独打包,当访问到这个路由的时候再进行加载。