各位观众老爷们,大家好!今天咱们来聊聊前端路由这个磨人的小妖精。别害怕,咱们的目标是把它驯服,让它乖乖听话,而不是被它搞得焦头烂额。
一、什么是路由?为啥我们需要它?
想象一下你正在浏览一个网站。你点击了“首页”,页面内容变成了首页;点击了“关于我们”,页面内容又变成了关于我们的介绍。这个切换过程,就是路由在背后默默地工作。
简单来说,路由就是根据 URL 的变化,来决定显示什么内容。
在单页应用 (SPA) 中,路由尤为重要。因为 SPA 的特点是只有一个 HTML 页面,所有的页面切换都在这个页面内部完成。如果没有路由,整个应用就会像一锅粥,所有内容都堆在一起,用户体验简直是灾难。
二、手撸一个简单的路由:思路先行
要实现一个基本的路由功能,我们可以大致分为以下几个步骤:
- 监听 URL 的变化: 浏览器提供了
hashchange
和popstate
两个事件,可以用来监听 URL 的变化。 - 解析 URL: 获取 URL 中的路径 (path),例如
/home
、/about
。 - 匹配路由: 将解析出的路径与我们预先定义的路由规则进行匹配。
- 渲染对应的组件: 根据匹配结果,渲染对应的页面内容。
三、代码实现:一步一个脚印
接下来,咱们就用代码来实现这个过程。
1. HTML 骨架
先来一个简单的 HTML 结构,作为我们路由的容器:
<!DOCTYPE html>
<html>
<head>
<title>Simple Router</title>
</head>
<body>
<nav>
<a href="#/home">Home</a> |
<a href="#/about">About</a> |
<a href="#/contact">Contact</a>
</nav>
<div id="app">
<!-- 这里的内容会根据路由变化 -->
</div>
<script src="router.js"></script>
</body>
</html>
这里使用了 hash
路由,也就是 URL 中带有 #
符号。 这种路由方式兼容性好,实现起来也简单。
2. JavaScript 路由核心
// router.js
class Router {
constructor() {
this.routes = {}; // 存储路由规则
this.app = document.getElementById('app'); // 获取路由容器
this.currentRoute = null;
// 监听 hashchange 事件
window.addEventListener('hashchange', this.handleRouteChange.bind(this));
// 页面加载时,也要执行一次路由
window.addEventListener('load', this.handleRouteChange.bind(this));
}
// 添加路由规则
addRoute(path, callback) {
this.routes[path] = callback;
}
// 处理路由变化
handleRouteChange() {
const path = this.getHashPath();
if (this.currentRoute === path) {
return; // 防止重复渲染
}
this.currentRoute = path;
const routeHandler = this.routes[path] || this.routes['*']; // 匹配路由,如果没有匹配到,就使用默认路由
if (routeHandler) {
routeHandler();
} else {
this.app.innerHTML = '<h1>404 Not Found</h1>';
}
}
// 获取 hash 中的路径
getHashPath() {
const hash = window.location.hash;
return hash.slice(1); // 去掉 # 符号
}
// 渲染内容到容器
render(content) {
this.app.innerHTML = content;
}
}
// 创建 Router 实例
const router = new Router();
// 定义路由规则
router.addRoute('/home', () => {
router.render('<h1>Home Page</h1><p>Welcome to the home page!</p>');
});
router.addRoute('/about', () => {
router.render('<h1>About Us</h1><p>This is the about us page.</p>');
});
router.addRoute('/contact', () => {
router.render('<h1>Contact Us</h1><p>You can contact us at [email protected]</p>');
});
// 添加一个默认路由,处理 404 情况
router.addRoute('*', () => {
router.render('<h1>404 Not Found</h1><p>The requested page was not found.</p>');
});
这段代码的核心在于 Router
类。它负责存储路由规则,监听 URL 变化,匹配路由,以及渲染对应的页面内容。
代码解释:
constructor()
: 构造函数,初始化路由信息,绑定事件监听器。addRoute(path, callback)
: 添加路由规则,path
是 URL 路径,callback
是当路径匹配时要执行的回调函数。handleRouteChange()
: 处理 URL 变化的核心函数。它会获取当前 URL 的路径,并根据路径匹配对应的回调函数。getHashPath()
: 从 URL 的 hash 中提取路径。render(content)
: 将内容渲染到指定的容器中。
3. 运行效果
现在,打开你的 HTML 文件,点击导航链接,就可以看到页面内容随着 URL 的变化而改变了。
四、进阶:使用 History API
虽然 hash 路由简单易用,但 URL 中带有 #
符号,看起来不够优雅。 我们可以使用 History API 来实现更美观的 URL。
1. 修改 HTML
首先,修改 HTML 中的链接,去掉 #
符号:
<!DOCTYPE html>
<html>
<head>
<title>Simple Router</title>
</head>
<body>
<nav>
<a href="/home">Home</a> |
<a href="/about">About</a> |
<a href="/contact">Contact</a>
</nav>
<div id="app">
<!-- 这里的内容会根据路由变化 -->
</div>
<script src="router.js"></script>
</body>
</html>
2. 修改 JavaScript
然后,修改 JavaScript 代码,使用 pushState
和 popstate
:
// router.js
class Router {
constructor() {
this.routes = {};
this.app = document.getElementById('app');
this.currentRoute = null;
// 监听 popstate 事件
window.addEventListener('popstate', this.handleRouteChange.bind(this));
// 拦截链接点击事件
document.addEventListener('click', (event) => {
if (event.target.tagName === 'A') {
event.preventDefault(); // 阻止默认的链接跳转行为
const path = event.target.getAttribute('href');
this.navigateTo(path);
}
});
// 页面加载时,也要执行一次路由
this.handleRouteChange();
}
addRoute(path, callback) {
this.routes[path] = callback;
}
handleRouteChange() {
const path = this.getPath();
if (this.currentRoute === path) {
return; // 防止重复渲染
}
this.currentRoute = path;
const routeHandler = this.routes[path] || this.routes['*'];
if (routeHandler) {
routeHandler();
} else {
this.app.innerHTML = '<h1>404 Not Found</h1>';
}
}
// 获取当前路径
getPath() {
return window.location.pathname;
}
// 导航到指定路径
navigateTo(path) {
history.pushState(null, null, path); // 修改 URL,但不刷新页面
this.handleRouteChange(); // 手动触发路由处理
}
render(content) {
this.app.innerHTML = content;
}
}
// 创建 Router 实例
const router = new Router();
// 定义路由规则
router.addRoute('/home', () => {
router.render('<h1>Home Page</h1><p>Welcome to the home page!</p>');
});
router.addRoute('/about', () => {
router.render('<h1>About Us</h1><p>This is the about us page.</p>');
});
router.addRoute('/contact', () => {
router.render('<h1>Contact Us</h1><p>You can contact us at [email protected]</p>');
});
// 添加一个默认路由,处理 404 情况
router.addRoute('*', () => {
router.render('<h1>404 Not Found</h1><p>The requested page was not found.</p>');
});
代码解释:
popstate
事件: 当用户点击浏览器的前进/后退按钮时,会触发popstate
事件。history.pushState(state, title, url)
: 修改 URL,但不刷新页面。state
可以用来存储一些状态数据,title
是页面的标题(通常浏览器会忽略),url
是新的 URL。navigateTo(path)
: 导航到指定路径的函数,它会调用pushState
修改 URL,并手动触发handleRouteChange
函数。- 拦截链接点击事件: 监听
click
事件,如果点击的是<a>
标签,就阻止默认的链接跳转行为,然后调用navigateTo
函数。
3. 运行效果
现在,打开你的 HTML 文件,点击导航链接,就可以看到 URL 变成了 /home
、/about
等,而且页面内容也会相应地变化。
五、总结:路由的本质与扩展
咱们今天手撸了一个简单的路由,虽然功能还比较基础,但已经足以理解路由的本质:监听 URL 变化,匹配路由规则,渲染对应内容。
总结一下,我们今天涉及到的知识点:
知识点 | 描述 | 示例代码 |
---|---|---|
hashchange 事件 |
监听 URL 中 hash 的变化 | window.addEventListener('hashchange', this.handleRouteChange.bind(this)); |
popstate 事件 |
监听浏览器的前进/后退按钮点击事件 | window.addEventListener('popstate', this.handleRouteChange.bind(this)); |
history.pushState() |
修改 URL,但不刷新页面 | history.pushState(null, null, path); |
window.location.hash |
获取 URL 中的 hash 部分 | const hash = window.location.hash; |
window.location.pathname |
获取 URL 中的路径部分 | return window.location.pathname; |
event.preventDefault() |
阻止事件的默认行为 | event.preventDefault(); |
扩展方向:
- 路由参数: 可以支持带参数的路由,例如
/user/:id
,其中:id
是一个参数。 - 嵌套路由: 可以支持嵌套的路由结构,例如
/admin/users
、/admin/products
。 - 路由守卫: 可以在路由切换前后执行一些逻辑,例如权限验证、数据加载等。
- 更灵活的匹配规则: 可以使用正则表达式进行路由匹配,提供更强大的灵活性。
当然,实际开发中,我们通常会使用现成的路由库,例如 react-router
、vue-router
等。 这些库提供了更丰富的功能和更好的性能。 但是,理解路由的原理,才能更好地使用这些库,并在遇到问题时能够快速定位和解决。
好了,今天的讲座就到这里。希望大家有所收获,下次再见!