路由实现原理基本上每个人都能说出一点。最近也是被问到了回答的不是很好,所以准备好好整理一下。
前端单页应用实现路由的方式有两种。一种是基于hash,一种是基于History API。
通过将一个URL path部分用 # (Hash符号) 拆分。浏览器将 # 后面的部分视作虚拟片段。
早期的前端路由实现是基于 location.hash来实现的。他有如下特性:
下面是一个简易实现。设定了一个路由数组,有一个方法locationHandler,根据hash,通过路由数组,找到对应页面的内容。
监听hashchange事件,当hash改变时触发。并且在页面打开时也同样触发一次。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>spa route</title>
</head>
<body>
<nav>
<a href="#">Home</a>
<a href="#about">About</a>
<a href="#contact">Contact</a>
<a href="#other">Other</a>
</nav>
<div id="content"></div>
</body>
<script>
const routes = {
404: {
content: "404 Not Found",
title: "404",
},
"/": {
content: "Home Page",
title: "Home",
},
"about": {
content: "This is a route demo",
title: "About Us",
},
"contact": {
content: "This is a contact",
title: "Contact Us",
}
};
const locationHandler = async() => {
var location = window.location.hash.replace("#", "");
if (location.length == 0) {
location = "/";
}
const route = routes[location] || routes["404"];
const html = route.content;
document.getElementById("content").innerHTML = html;
document.title = route.title;
};
window.addEventListener("hashchange", locationHandler);
locationHandler();
</script>
</html>
普通的URL path (无 # 拆分) ,服务器需要拦截路径请求返回入口index.html文件。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>spa route</title>
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
<a href="/other">Other</a>
</nav>
<div id="content"></div>
</body>
<script>
const routes = {
404: {
content: "404 Not Found",
title: "404",
},
"/": {
content: "Home Page",
title: "Home",
},
"/about": {
content: "This is a route demo",
title: "About Us",
},
"/contact": {
content: "This is a contact",
title: "Contact Us",
}
};
const route = (event) => {
event = event || window.event;
event.preventDefault();
window.history.pushState({}, "", event.target.href);
locationHandler();
};
document.addEventListener("click", (e) => {
const {
target
} = e;
if (!target.matches("nav a")) {
return;
}
e.preventDefault();
route();
});
const locationHandler = async() => {
var location = window.location.pathname.;
if (location.length == 0) {
location = "/";
}
const route = routes[location] || routes["404"];
const html = route.content;
document.getElementById("content").innerHTML = html;
document.title = route.title;
};
window.onpopstate = locationHandler;
window.route = route;
locationHandler();
</script>
</html>
对比两种实现,其实代码逻辑基本上是一致的
基于location.hash的实现比较简单,直接通过监听hashchange来改变页面内容。
基于History API 的实现,主要是利用了 h5 提供的 pushState, replaceState方法。去改变当前页面的 URL, 同时,利用点击事件 结合 window.onpopstate监听事件触发页面的更新渲染逻辑。
此外History API的实现服务器通常需要做一些配置。
因为由于单页应用路由的实现是前端实现的, 可以理解为是 “伪路由”, 路由的跳转逻辑都是前端代码完成的,这样就存在一个问题, 例如上面的实现中, http://127.0.0.1:5500/about 这个页面用户点击了页面刷新,就会找不到页面。 因为浏览器会向服务器 “http://127.0.0.1:5500/about” 这个地址发送 GET 请求, 希望请求到一个单独的 index.html 文件, 而实际上这个文件我们服务器上是不存在的。 我们需要将其处理为:
http://127.0.0.1:5500/ server 返回首页
http://127.0.0.1:5500/about server 返回首页, 然后前端路由跳转到 about 页
http://127.0.0.1:5500/contact server 返回首页, 然后前端路由跳转到 contact 页
为了做到这点,所以我们需要对服务器做一些转发处理。
优势:
劣势:
优势:
劣势:
已经了解了基本原理,那么Angular的路由又是怎么实现的呢。
我到github上下载了angular路由实现的源码。
https://github.com/angular/angular/tree/main/packages/router
我们直接在router目录下搜索路由跳转的方法navigate。
commands是命令数组,比较常见的用法是在里面填写要导航到的路由,extras里设置路由的参数,以及其他扩展属性,第一步是校验数组里的成员是否均合法。
不是null即是合法。
值得注意的是Navigation这个类里,触发方式有三种,imperative即通过router.navigate触发,popstate event即history api,hashchange就是hash改变。
下一步构建UrlTree,queryParams即路由参数,会根据路由方式选择是否和原路由的参数合并。
最终返回是一个构建完成的Url。通过构建的url和扩展参数开始导航。值得一提的是这个NgZone。之前做过一个前端获取ip的需求,封装的getUserIP方法入参是一个回调函数,我在回调函数里调用navigate调用失败,后面也是通过设置ngZone.run()来解决的,这下原理终于搞清楚了,原来是执行上下文的问题。
后面实际处理路由请求时,还会对路由进行合并,路由守卫校验,设置活动路由等操作。这些都是angular提供的进阶的路由能力。基本的路由功能的实现看起来还是非常简单清晰的。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。