摘要:而应用便是基于前端路由实现的所以便有了前端路由。因为两种模式都需要调用一个方法来实现不同路由内容的刷新前端路由路由列表匹配当前的路由匹配不到则使用配置内容并渲染下面我们来实现两种模式。
什么是路由?
路由这概念最开始是在后端出现的,在以前前后端不分离的时候,由后端来控制路由,服务器接收客户端的请求,解析对应的url路径,并返回对应的页面/资源。前端路由的来源简单的说 路由就是根据不同的url地址来展示不同的内容或页面.
在很久很久以前~ 用户的每次更新操作都需要重新刷新页面,非常的影响交互体验,后来,为了解决这个问题,便有了Ajax(异步加载方案),Ajax给体验带来了极大的提升。前端路由的两种实现原理 1.Hash模式虽然Ajax解决了用户交互时体验的痛点,但是多页面之间的跳转一样会有不好的体验,所以便有了spa(single-page application)使用的诞生。而spa应用便是基于前端路由实现的,所以便有了前端路由。
如今比较火的vue-router/react-router 也是基于前端路由的原理实现的~
window对象提供了onhashchange事件来监听hash值的改变,一旦url中的hash值发生改变,便会触发该事件。
window.onhashchange = function(){ // hash 值改变 // do you want }2.History 模式
HTML5的History API 为浏览器的全局history对象增加的扩展方法。简单来说,history其实就是浏览器历史栈的一个接口。这里不细说history的每个API啦。具体可查阅 传送门
window对象提供了onpopstate事件来监听历史栈的改变,一旦历史栈信息发生改变,便会触发该事件。
需要特别注意的是,调用history.pushState()或history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件。
window.onpopstate = function(){ // 历史栈 信息改变 // do you want }
history提供了两个操作历史栈的API:history.pushState 和 history.replaceState
history.pushState(data[,title][,url]);//向历史记录中追加一条记录
history.replaceState(data[,title][,url]);//替换当前页在历史记录中的信息。
// data: 一个JavaScript对象,与用pushState()方法创建的新历史记录条目关联。无论何时用户导航到新创建的状态,popstate事件都会被触发,并且事件对象的state属性都包含历史记录条目的状态对象的拷贝。 //title: FireFox浏览器目前会忽略该参数,虽然以后可能会用上。考虑到未来可能会对该方法进行修改,传一个空字符串会比较安全。或者,你也可以传入一个简短的标题,标明将要进入的状态。 //url: 新的历史记录条目的地址。浏览器不会在调用pushState()方法后加载该地址,但之后,可能会试图加载,例如用户重启浏览器。新的URL不一定是绝对路径;如果是相对路径,它将以当前URL为基准;传入的URL与当前URL应该是同源的,否则,pushState()会抛出异常。该参数是可选的;不指定的话则为文档当前URL。两种模式优劣对比
对比 | Hash | History |
---|---|---|
观赏性 | 丑 | 美 |
兼容性 | >ie8 | >ie10 |
实用性 | 直接使用 | 需后端配合 |
命名空间 | 同一document | 同源 |
本demo只是想说帮助我们通过实践更进一步的理解前端路由这个概念,所以只做了简单的实现~history模式404
当我们使用history模式时,如果没有进行配置,刷新页面会出现404。文件结构原因是因为history模式的url是真实的url,服务器会对url的文件路径进行资源查找,找不到资源就会返回404。
这个问题的解决方案这里就不细说了,google一下,你就知道~ 我们在以下demo使用webpack-dev-server的里的historyApiFallback属性来支持HTML5 History Mode。
|-- package.json |-- webpack.config.js |-- index.html |-- src |-- index.js |-- routeList.js |-- base.js |-- hash.js |-- history.js1.搭建环境
废话不多说,直接上代码~
package.json
{ "name": "web_router", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "webpack-dev-server --config ./webpack.config.js" }, "author": "webfansplz", "license": "MIT", "devDependencies": { "html-webpack-plugin": "^3.2.0", "webpack": "^4.28.1", "webpack-cli": "^3.2.1", "webpack-dev-server": "^3.1.14" } }
webpack.config.js
"use strict"; const path = require("path"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { mode: "development", entry: "./src/index.js", output: { filename: "[name].js" }, devServer: { clientLogLevel: "warning", hot: true, inline: true, open: true, //在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html (解决histroy mode 404) historyApiFallback: true, host: "localhost", port: "6789", compress: true }, plugins: [ new webpack.HotModuleReplacementPlugin(), new HtmlWebpackPlugin({ filename: "index.html", template: "index.html", inject: true }) ] };2.开撸
首先我们先初始化定义我们需要实现的功能及配置参数。
前端路由 | 参数 | 方法 |
---|---|---|
x | 模式(mode) | push(压入) |
x | 路由列表(routeList) | replace(替换) |
x | x | go(前进/后退) |
const MODE=""; const ROUTELIST=[]; class WebRouter { constructor() { } push(path) { ... } replace(path) { ... } go(num) { ... } } new WebRouter({ mode: MODE, routeList: ROUTELIST });
前面我们说了前端路由有两种实现方式。
1.定义路由列表
2.我们分别为这两种方式创建对应的类,并根据不同的mode参数进行实例化,完成webRouter类的实现。
src/routeList.jsexport const ROUTELIST = [ { path: "/", name: "index", component: "This is index page" }, { path: "/hash", name: "hash", component: "This is hash page" }, { path: "/history", name: "history", component: "This is history page" }, { path: "*", name: "notFound", component: "404 NOT FOUND" } ];src/hash.js
export class HashRouter{ }src/history.js
export class HistoryRouter{ }src/index.js
import { HashRouter } from "./hash"; import { HistoryRouter } from "./history"; import { ROUTELIST } from "./routeList"; //路由模式 const MODE = "hash"; class WebRouter { constructor({ mode = "hash", routeList }) { this.router = mode === "hash" ? new HashRouter(routeList) : new HistoryRouter(routeList); } push(path) { this.router.push(path); } replace(path) { this.router.replace(path); } go(num) { this.router.go(num); } } const webRouter = new WebRouter({ mode: MODE, routeList: ROUTELIST });
前面我们已经实现了webRouter的功能,接下来我们来实现两种方式。
因为两种模式都需要调用一个方法来实现不同路由内容的刷新,so~
index.htmljs/base.js前端路由
const ELEMENT = document.querySelector("#page"); export class BaseRouter { //list = 路由列表 constructor(list) { this.list = list; } render(state) { //匹配当前的路由,匹配不到则使用404配置内容 并渲染~ let ele = this.list.find(ele => ele.path === state); ele = ele ? ele : this.list.find(ele => ele.path === "*"); ELEMENT.innerText = ele.component; } }
ok,下面我们来实现两种模式。
Hash模式
src/hash.jsimport { BaseRouter } from "./base.js"; export class HashRouter extends BaseRouter { constructor(list) { super(list); this.handler(); //监听hash变化事件,hash变化重新渲染 window.addEventListener("hashchange", e => { this.handler(); }); } //渲染 handler() { this.render(this.getState()); } //获取当前hash getState() { const hash = window.location.hash; return hash ? hash.slice(1) : "/"; } //获取完整url getUrl(path) { const href = window.location.href; const i = href.indexOf("#"); const base = i >= 0 ? href.slice(0, i) : href; return `${base}#${path}`; } //改变hash值 实现压入 功能 push(path) { window.location.hash = path; } //使用location.replace实现替换 功能 replace(path) { window.location.replace(this.getUrl(path)); } //这里使用history模式的go方法进行模拟 前进/后退 功能 go(n) { window.history.go(n); } }
History模式
src/history.jsimport { BaseRouter } from "./base.js"; export class HistoryRouter extends BaseRouter { constructor(list) { super(list); this.handler(); //监听历史栈信息变化,变化时重新渲染 window.addEventListener("popstate", e => { this.handler(); }); } //渲染 handler() { this.render(this.getState()); } //获取路由路径 getState() { const path = window.location.pathname; return path ? path : "/"; } //使用pushState方法实现压入功能 //PushState不会触发popstate事件,所以需要手动调用渲染函数 push(path) { history.pushState(null, null, path); this.handler(); } //使用replaceState实现替换功能 //replaceState不会触发popstate事件,所以需要手动调用渲染函数 replace(path) { history.replaceState(null, null, path); this.handler(); } go(n) { window.history.go(n); } }3.小功告成
就这样,一个简单的前端路由就完成拉。
源码地址
如果觉得有帮助到你的话,给个star哈~
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/100920.html
摘要:延伸阅读学习与实践资料索引与前端工程化实践前端每周清单半年盘点之篇前端每周清单半年盘点之与篇前端每周清单半年盘点之篇 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为新闻热点、开发教程、工程实践、深度阅读、开源项目、巅峰人生等栏目。欢迎关注【前端之巅】微信公众号(ID:frontshow),及时获取前端每周清单;本文则是对于半年来发布的前端每周清单...
摘要:前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为新闻热点、开发教程、工程实践、深度阅读、开源项目、巅峰人生等栏目。欢迎关注【前端之巅】微信公众号(ID:frontshow),及时获取前端每周清单;本文则是对于...
摘要:发布是由团队开源的,操作接口库,已成为事实上的浏览器操作标准。本周正式发布,为我们带来了,,支持自定义头部与脚部,支持增强,兼容原生协议等特性变化。新特性介绍日前发布了大版本更新,引入了一系列的新特性与提升,本文即是对这些变化进行深入解读。 showImg(https://segmentfault.com/img/remote/1460000012940044); 前端每周清单专注前端...
摘要:的最后一个大招就是替换一些传统的服务端语言,例如,,等,在业务层上面使用来开发服务端完全不成问题。更多的的使用细节和技巧建议关注美团博客大搜车论坛下一篇我们开启如何结合和搭建一个开发环境和项目目录 往期回顾 前面2期都讲得是浏览器端的东西比较多,包括Webpack,虽然是Node处理的,但是还是浏览器端用的多,对于现在的前端开发来说,不懂一点服务端的东西,简直没办法活,一般的招聘要求都...
阅读 778·2021-09-06 15:02
阅读 2420·2019-08-30 15:43
阅读 2129·2019-08-30 11:26
阅读 2356·2019-08-26 12:12
阅读 3524·2019-08-23 18:24
阅读 3235·2019-08-23 18:16
阅读 674·2019-08-23 17:02
阅读 2226·2019-08-23 15:34