前端路由
路由的概念原本来源于服务端,在服务端中路由描述的是 URL 与处理函数之间的映射关系。
在 Web 前端单页应用 SPA(Single Page Application)中,路由描述的是 URL 与 UI 之间的映射关系,这种映射是单向的,即 URL 变化引起 UI 更新(无需刷新页面)
传统路由模式
每一次页面跳转的时候,后台服务器都会给返回一个新的html文档。 比如下面的例子
html 文档
<!-- home页面 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li><a href='/home'>home</a></li>
<li><a href='/about'>about</a></li>
<div id="routeView">
Home
</div>
</ul>
</body>
</html>
<!-- About页面 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li><a href='/home'>home</a></li>
<li><a href='/about'>about</a></li>
<div id="routeView">
About
</div>
</ul>
</body>
</html>
服务端配置
这里采用nodejs ,KOA框架
const Koa = require("koa");
const Router = require("koa-router");
const path = require("path");
const views = require("koa-views");
let app = new Koa();
let router = new Router();
//home.html 和 about.html 放置在 views 文件加下
app.use(views(path.join(__dirname, "views/"), { extension: "html" }));
//路由匹配规则
router.get("/home", index);
router.get("/", index);
router.get("/about", about);
//对应的渲染函数
async function index(ctx) {
await ctx.render("home");
}
async function about(ctx) {
await ctx.render("about");
}
app.use(router.routes());
app.listen(3333);
以上便是传统的路由跳转实现,需要服务端配合,每次路径改变时后端会去匹配,成功后返回一个新的html文档,因此也叫做多页应用。
前端路由实现
不同于传统路由,前端路并不由服务端控制,url改变时并不刷新页面,渲染的仍然是同一个html文件,因此也叫做SPA应用。(Single Page Application)
- 前端路由实现的要点
- 如何改变 URL 却不引起页面刷新?
- 如何检测 URL 变化了? 下面分别使用 hash 和 history 两种实现方式回答上面的两个核心问题。
Hash 模式
- hash 是 URL 中 hash (#) 及后面的那部分,常用作锚点在页面内进行导航,改变 URL 中的 hash 部分不会引起页面刷新
- 通过 hashchange 事件监听 URL 的变化,改变 URL 的方式只有这几种:通过浏览器前进后退改变 URL、通过
<a>标签改变 URL、通过window.location改变URL,这几种情况改变 URL 都会触发 hashchange 事件
html部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<!-- 定义路由 -->
<li><a href="#/home">home</a></li>
<li><a href="#/about">about</a></li>
<!-- 渲染路由对应的 UI -->
<div id="routeView"></div>
</ul>
</body>
</html>
js部分
// 页面加载完不会触发 hashchange,这里主动触发一次 hashchange 事件
window.addEventListener('DOMContentLoaded', onLoad)
// 监听路由变化
window.addEventListener('hashchange', onHashChange)
// 路由视图
let routerView = null
function onLoad () {
routerView = document.querySelector('#routeView')
onHashChange()
}
// 路由变化时,根据路由渲染对应 UI
function onHashChange () {
switch (location.hash) {
case '#/home':
routerView.innerHTML = 'Home'
break
case '#/about':
routerView.innerHTML = 'About'
break
default:
break
}
}
从以上的例子中可以看到,通过监听页面的hash(url)变化,可以通过js动态的渲染视图内容。
history模式
这种类型是通过html5的最新history api来实现的。
- history 提供了 pushState 和 replaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新
- history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同:通过浏览器前进后退改变 URL 时会触发 popstate 事件,通过pushState/replaceState或
<a>标签改变 URL 不会触发 popstate 事件。好在我们可以拦截 pushState/replaceState的调用和<a>标签的点击事件来检测 URL 变化,所以监听 URL 变化可以实现
html部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li><a href='/history/home'>home</a></li>
<li><a href='/history/about'>about</a></li>
<div id="routeView"></div>
</ul>
</body>
</html>
js部分:
window.addEventListener('DOMContentLoaded', onLoad)
// 监听路由变化
window.addEventListener('popstate', onPopState)
// 路由视图
let routerView = null
function onLoad () {
routerView = document.querySelector('#routeView')
onPopState()
// 拦截 <a> 标签点击事件默认行为, 点击时使用 pushState 修改 URL并更新手动 UI,从而实现点击链接更新 URL 和 UI 的效果。
const linkList = document.querySelectorAll('a[href]')
linkList.forEach(el => el.addEventListener('click', function (e) {
e.preventDefault()
history.pushState(null, '', el.getAttribute('href'))
onPopState()
}))
}
// 路由变化时,根据路由渲染对应 UI
function onPopState () {
switch (location.pathname) {
case '/history/home':
routerView.innerHTML = 'Home'
return
case '/history/about':
routerView.innerHTML = 'About'
return
default:
return
}
}
和普通的url一样,但是也有缺点 ,就是一刷新页面 页面就会丢,
因为只要刷新 这个url就会请求服务器,然而服务器上根本没有这个资源,所以就会报404,解决方案就需要配置一下服务器端
const Koa = require("koa");
const Router = require("koa-router");
const path = require("path");
const views = require("koa-views");
let app = new Koa();
let router = new Router();
app.use(views(path.join(__dirname, "views/"), { extension: "html" }));
//正确匹配 http://localhost:8888/history,渲染html
router.get("/history", index)
async function index(ctx) {
await ctx.render("history");
}
app.use(router.routes());
//如果没有匹配到 比如 http://localhost:8888/history/home,则又返回初始页面(首页)
app.use(async (ctx,next)=>{
await next();
if(parseInt(ctx.status)==404){
ctx.response.redirect("/history")
}
})
app.listen(8888);
小结
以上通过 hash模式和histroy 简单介绍了前端路由的原理和实现方法,均是原生写法,不依赖任何框架。 下次我们简单探讨一下vue-router的实现原理