首页 > 其他分享 >小tips:怎样实现简单的前端hash与history路由方式?

小tips:怎样实现简单的前端hash与history路由方式?

时间:2022-08-18 23:35:45浏览次数:76  
标签:replaceState const window path tips pushState hash history

前端路由实现方式,主要有两种,分别是history和hash模式。

hash模式

不同路由对应的hash是不一样的,如何能够监听到URL中关于hash部分发生的变化?浏览器已经暴露给我们一个现成的方法hashchange,在hash改变的时候,触发该事件。

实现示例代码:

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title></title>
</head>
<style>
* {
	margin: 0;
	padding: 0;
	box-sizing:border-box;
}
html,body {
	height: 100%;
}
#content {
	height: calc(100% - 50px);
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 30px;
}
#nav {
	width: 100%;
	height: 50px;
	position: fixed;
	bottom: 0;
	left: 0;
	display: flex;
}
#nav a {
	width: 25%;
	display: flex;
	align-items: center;
	justify-content: center;
	border: 1px solid black;
}
#nav a:not(:last-of-type) {
	border-right: none;
}
</style>
<body>
<div id="content">内容区域</div>
<div id="nav">
	<a href="#/">首页</a>
	<a href="#/new-product">新品</a>
	<a href="#/shopping-cart">购物车</a>
	<a href="#/my">我的</a>
</div>
<script type="text/javascript">
class VueRouter {
	constructor(routes = []) {
		this.routes = routes;
		this.currentHash = ''; // 当前hash值
		window.addEventListener('load',this.refresh.bind(this),false);
		window.addEventListener('hashchange',this.refresh.bind(this),false);
	}

	// 获取hash
	getHash(url) {
		return url.indexOf('#') !== -1 ? url.slice(url.indexOf('#') + 1) : '/'
	}

	refresh(event) {
		let newHash = '';
		if(event.newURL) {
			newHash = this.getHash(event.newURL || '');
		} else {
			newHash = this.getHash(window.location.href);
		}
		this.currentHash = newHash;
		this.matchComponentCon();
	}

	// 匹配hash组件内容
	matchComponentCon() {
		let curRoute = this.routes.find(route => route.path === this.currentHash);
		if(!curRoute) {
			curRoute = this.routes.find(route => route.path === '/');
		}
		const { component } = curRoute;
		document.querySelector('#content').innerHTML = component;
	}
}

const router = new VueRouter([
	{path: '/',name: 'home',component: '<div>首页内容</div>'},
	{path: '/new-product',name: 'new-product',component: '<div>新品内容</div>'},
	{path: '/shopping-cart',name: 'shopping-cart',component: '<div>购物车内容</div>'},
	{path: '/my',name: 'my',component: '<div>我的内容</div>'}
])
</script>
</body>
</html>

效果如图所示:
image

当点击底部不同的菜单,中间区域显示对应菜单的内容。

history模式

HTML5提供的一个history全局对象,这个对象包含了关于我们访问网页(历史会话)的一些信息,history路由的实现要归功于它。

history它还暴露了一些方法,如:

  • window.history.go: 可以跳转到浏览器会话历史中的指定的某一个记录页
  • window.history.forward: 指向浏览器会话历史中的下一页,跟浏览器的前进按钮相同
  • window.history.back: 返回浏览器会话历史中的上一页,跟浏览器的回退按钮功能相同
  • window.history.pushState: 可以将给定的数据压入到浏览器会话历史栈中
  • window.history.replaceState: 将当前的会话页面的url替换成指定的数据

而history路由的实现,主要就是依靠于pushState与replaceState实现的。这两个方法的特点:

  • 改变当前页面的URL,不会刷新页面;
  • 调用pushState方法会把当前的URL压入到浏览器的会话历史栈中,会让history.length加1,而replaceState是替换当前的这条会话历史,不会增加history.length;

我们是否可以通过pushStatereplaceState能够监听URL变化这个动作,就可以实现不同路由页面的渲染处理,我们需要了解下事件处理程序popState,官网参考地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/popstate_event

每当激活同一文档中不同的历史记录条目时,popstate 事件就会在对应的window对象上触发。如果当前处于激活状态的历史记录条目是由history.pushState()方法创建的或者是由 history.replaceState() 方法修改的,则 popstate 事件的 state 属性包含了这个历史记录条目的 state 对象的一个拷贝。

备注: 调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。popstate 事件只会在浏览器某些行为下触发,比如点击后退按钮(或者在 JavaScript 中调用 history.back() 方法)。即,在同一文档的两个历史记录条目之间导航会触发该事件。

从官网描述看,总结如下:

  • history.pushStatehistory.replaceState方法不会触发popstate事件
  • 浏览器的某些行为会导致popstate事件,比如go、back、forward
  • popstate事件对象中的state属性,可以理解是我们在通过history.pushStatehistory.replaceState方法时,传入的指定的数据

结论就是popstate无法监听history.pushStatehistory.replaceState方法,既然不支持,那么我们重新写下这个history.pushStatehistory.replaceState方法。在这个方法中,也能够暴露出自定义的全局事件,然后再监听自定义的事件
就行了。

let _wr = function(type) {
	let orig = history[type];
	return function() {
		orig.apply(this,arguments);
		let e = new Event(type);
		e.arguments = arguments;
		window.dispatchEvent(e);
	}
}

history.pushState = _wr('pushState');
history.replaceState = _wr('replaceState');

执行上面的方法,相当于自定义了同名的pushStatereplaceState自定义事件的触发绑定到了window上面,即是可以通过pushSate或者replaceState来执行自定义的事件,同时还执行了浏览器的history.pushState或者history.replaceState方法。

简单实现:

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title></title>
</head>
<style>
* {
	margin: 0;
	padding: 0;
	box-sizing:border-box;
}
html,body {
	height: 100%;
}
#content {
	height: calc(100% - 50px);
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 30px;
}
</style>
<body>
<div id="content">内容区域</div>
<button id="button1">首页</button>
<button id="button2">新品</button>
<button id="button3">购物车</button>
<button id="button4">我的</button>

<script type="text/javascript">
const button1 = document.querySelector('#button1');
const button2 = document.querySelector('#button2');
const button3 = document.querySelector('#button3');
const button4 = document.querySelector('#button4');

let _wr = function(type) {
	let orig = history[type];
	return function() {
		orig.apply(this,arguments);
		let e = new Event(type);
		e.arguments = arguments;
		window.dispatchEvent(e);
	}
}

history.pushState = _wr('pushState');
history.replaceState = _wr('replaceState');

button1.addEventListener('click',() => {
	const path = './home';
	const title = '首页';
	history.pushState({ path,title },title, path);
})
button2.addEventListener('click',() => {
	const path = './new-product';
	const title = '新品';
	history.pushState({ path,title },title, path);
})
button3.addEventListener('click',() => {
	const path = './shopping-cart';
	const title = '购物车';
	history.pushState({ path,title },title, path);
})
button4.addEventListener('click',() => {
	const path = './my';
	const title = '我的';
	history.pushState({ path,title },title, path);
})


class VueRouter {
	constructor(routes = []) {
		this.routes = routes;
		this.currentUrl = ''; 
		this.matchComponentCon();
		window.addEventListener('pushState', e => {
			console.log(e)
			this.currentUrl = e.arguments[2] && e.arguments[2].slice(e.arguments[2].indexOf('.') + 1);
			window.document.title = e.arguments[1];
			this.matchComponentCon();
		},false)
		
		// 监听浏览器的back、forward、go事件
		window.addEventListener('popstate', e => {
			const { path = '/', title } = e.state || {};
 			console.log(e)
			this.currentUrl = path.slice(path.indexOf('.') + 1);
			window.document.title = title;
			this.matchComponentCon();
		},false)
	}

	// 匹配路由页面内容
	matchComponentCon() {
		let curRoute = this.routes.find(route => route.path === this.currentUrl);
		if(!curRoute) {
			curRoute = this.routes.find(route => route.path === '/');
		}
		const { component } = curRoute;
		document.querySelector('#content').innerHTML = component;
	}
}

const router = new VueRouter([
	{path: '/',name: 'home',component: '<div>首页内容</div>'},
	{path: '/new-product',name: 'new-product',component: '<div>新品内容</div>'},
	{path: '/shopping-cart',name: 'shopping-cart',component: '<div>购物车内容</div>'},
	{path: '/my',name: 'my',component: '<div>我的内容</div>'}
])

</script>
</body>
</html>

history模式需要后端配合解决刷新页面404的问题。nginx配置如下:

location / {
  try_files $uri $uri/ /index.html;
}

标签:replaceState,const,window,path,tips,pushState,hash,history
From: https://www.cnblogs.com/moqiutao/p/16596992.html

相关文章

  • hash の 题(内含兔子与兔子,Hash 键值 (hash))
     Hash键值(hash)【思路】按照正常模拟,很容易写出代码,如图: for(inti=1;i<=q;i++){ intopt; scanf("%d",&opt); if(opt==1){ intx,y,ans=0; scanf("%d%d"......
  • map-HashMap
    HashMap图片~~~其他常见的map结构常见的map结构常用的Map结构有:hashMap(最常用)、hashTable、LinkedHashMap、TreeMap(对存入的键值进行排序)LinkedHashMap和HashMap......
  • c++ 实现hashmap
    由于hashmap不是c++stl中标准实现,这样在跨平台使用时就可能会出现问题,于是想到自己实现一个hashmaphash算法使用开链法解决hash冲突,主要实现了添加,删除,查找几个方法头文......
  • redis hash
    在redis的value中以键值对存储数据  hsethashnamexage18addresshefei插入元素hgethashname输出元素"x"hgethashage"18"hgethashaddress"......
  • Redis---hash哈希散列
    1.前言Redishash(哈希散列)是由字符类型的field(字段)和value组成的哈希映射表结构(也称散列表),它非常类似于表格结构。在hash类型中,field与value一一对应,且不允许重......
  • Redis常用指令之string、list、set、zset、hash
    Redis之五大类型常用指令redis的一些小知识redis服务器端口默认是6379在编译完成后的bin目录下启动服务端:redis-server客户端连接操作:redis-cli-hlocalhost-p......
  • verilog随机序列发生器及很久不写verilog回忆起来的一些tips
    今天写了一个随机序列发生器,实际上是伪随机数,使用了LFSR(linefeedbackshiftregister)LFSR将移位寄存器中的值取出,做适当的运算,再返回到序列中作为新的输入,就可以构......
  • 【面试】【1】HashMap的扩容机制
    1、HashMap是一种数据存储的容器,当创建一个集合对象的时候,实际上就是在内存里面一次性的申请了一块内存空间,存储容量的大小是在创建集合的时候给指定的,HashMap的默认大小是......
  • gorm tips
    约定的列明typeUserstruct{IDuint//列名是`id`Namestring//列名是`name`Birthdaytime.Time//列名是`birthday`CreatedA......
  • 海量数据去重的Hash与BloomFilter
    今天我们谈论一下散列表,我之前的两个博文写的都是关于平衡二叉树的平衡二叉树增删改查时间复杂度为log2n平衡的目的是增删改以后,保证下次搜索能稳定排除一半的数据;总结......