JS入门到精通详解(18)

# 基于SPA的单页面路由

关于单页应用

单页Web应用(single page web application,SPA),就是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。简单来说就是用户只需要加载一次页面就可以不再请求,当点击其他子页面时只会有相应的URL改变而不会重新加载。

  • 大部分用于: 移动端 / PC端的后台管理系统

单页应用的实现

  1. 依赖hash的改变(锚点)
  2. 依赖历史记录(history)
    • 前端路由:在页面中同一个位置,根据不同的hash值显示不同的页面结构

实现改变URL页面不刷新

按照常规的逻辑我们切换URL好像就会跳转网页,但是转念一想锚链接的URL不是也改变了吗? 这里,存在两种满足需求的方式。

  • 利用URL中的hash方式
    了解http协议就会知道,url的组成部分有很多,譬如协议、主机名、资源路径、查询字段等等,其中包含一个称之为片段的部分,以“#”为标识。打开控制台,输入 location.hash,你可以得到当前url的hash部分(如果当前url不存在hash则返回空字符串)。接下来,输入 location.hash = ‘123’,会发现浏览器地址栏的url变了,末尾增加了’#123’字段,并且,页面没有被重新刷新。很显然,这很符合我们的要求。
  • 利用H5的history API
    html5引入了一个history对象,包含了一套访问浏览器历史的api,可以通过window.history访问到它。 HTML5 History API包括2个方法:history.pushState()和history.replaceState(),和1个事件:window.onpopstate。这两个方法都是对浏览器的历史栈进行操作,将传递的url和相关数据压栈,并将浏览器地址栏的url替换成传入的url且不刷新页面,而且他们的参数也相同,第一个参数用于存储该url对应的状态对象,该对象可在onpopstate事件中获取,也可在history对象中获取。第二个参数是标题,目前浏览器并未实现。第三个参数则是设定的url。一般设置为相对路径,如果设置为绝对路径时需要保证同源。。不同的是pushState 将指定的url直接压入历史记录栈顶,而 replaceState 是将当前历史记录栈顶替换成传入的数据。不过低版本对history AIP的兼容性不是很好。

监听URL的变化,执行页面替换逻辑

  1. 对于hash方式,我们通常采用监听hashchange事件,在事件回调中处理相应的页面视图展示等逻辑。

两种实现的比较

总的来说,基于Hash的路由,兼容性更好;基于History API的路由,更加直观和正式。但是,有一点很大的区别是,基于Hash的路由不需要对服务器做改动,基于History API的路由需要对服务器做一些改造。

实现流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-acb1Gt42-1675149671873)(E:\job\teachingPlan - v2019 - v7 - 2019-3-25\teachingPlan\第二阶段v.5.3\img\SPA.png)]

代码实现

<div id="box">
    <div class="top">顶部通栏</div>
    <div class="bottom">
        <div class="slide">
            <a href="#/first">first</a>
            <a href="#/second">second</a>
            <a href="#/third">third</a>
            <a href="#/fourth">fourth</a>
        </div>
        <div class="content router-view"></div>
    </div>
</div>
<style>
*{
    margin: 0;
    padding: 0;
}
html,body,#box{
    width: 100%;
    height:100%;
}
#box{
    display: flex;
    flex-direction:column;
}
#box>.top{
    height: 130px;
    background: skyblue;
}
#box>.bottom{
    flex:1;
    display: flex;
}
#box>.bottom>.slide{
    width: 230px;
    background-color: orange;
    box-sizing: border-box;
    padding: 15px;
}
#box>.bottom>.slide>a{
    font-size: 22px;
    display: block;
    margin: 10px 0;
    text-decoration: none;
    color: #333;
}
#box>.bottom>.content{
    flex: 1;
    box-sizing: border-box;
    padding: 15px;
    background: purple;
    font-size: 100px;
    color: white
}
</style>
//1. 准备不同的页面显示结构
const template1 = `
				<div>
					first
				</div>
			`;
const template2 = `
				<div>
					second
				</div>
			`;
const template3 = `
				<div>
					third
				</div>
			`;
const template4 = `
				<div>
					fourth
				</div>
			`;

//2. 获取路由入口对象
let routerView = document.querySelector('.router-view');
//3. 捕获浏览器地址栏 hash值的改变
window.onhashchange = function(){
    console.log('地址栏hash值改变了');
    console.log(window.location.hash);
    //获取hash
    const {hash} = window.location;
    //判断hash值,进行不同的结构渲染
    switch(hash){
        case '#/first' : routerView.innerHTML = template1;break;
        case '#/second' : routerView.innerHTML = template2;break;
        case '#/third' : routerView.innerHTML = template3;break;
        case '#/fourth' : routerView.innerHTML = template4;break;
    }
}

代码升级(组件化开发)

  1. index.html

    <div id="box">
        <div class="top">顶部通栏</div>
        <div class="bottom">
            <div class="slide">
                <a href="#/first">first</a>
                <a href="#/second">second</a>
                <a href="#/third">third</a>
                <a href="#/fourth">fourth</a>
            </div>
            <div class="content router-view"></div>
        </div>
    </div>
    
    *{
        margin: 0;
        padding: 0;
    }
    html,body,#box{
        width: 100%;
        height:100%;
    }
    #box{
        display: flex;
        flex-direction:column;
    }
    #box>.top{
        height: 130px;
        background: skyblue;
    }
    #box>.bottom{
        flex:1;
        display: flex;
    }
    #box>.bottom>.slide{
        width: 230px;
        background-color: orange;
        box-sizing: border-box;
        padding: 15px;
    }
    #box>.bottom>.slide>a{
        font-size: 22px;
        display: block;
        margin: 10px 0;
        text-decoration: none;
        color: #333;
    }
    #box>.bottom>.content{
        flex: 1;
        box-sizing: border-box;
        padding: 15px;
        background: purple;
        font-size: 100px;
        color: white
    }
    
    <script src="./index.js" type="module"></script>
    
  2. components 组件

    • first.js

      //组件的html结构
      const template = `
      	<div id="first">
      		first
      	</div>
      `;
      //获取到路由出口对象
      const routerView = document.querySelector('.router-view');
      //准备一个渲染函数
      function render(){
      	routerView.innerHTML = template;
      }
      //导出渲染函数
      export default render;
      
    • second.js

      //组件的html结构
      const template = `
      	<div id="second">
      		second
      	</div>
      `;
      //获取到路由出口对象
      const routerView = document.querySelector('.router-view');
      //准备一个渲染函数
      function render(){
      	routerView.innerHTML = template;
      }
      //导出渲染函数
      export default render;
      
    • third.js

      //组件的html结构
      const template = `
      	<div id="third">
      		third
      	</div>
      `;
      //获取到路由出口对象
      const routerView = document.querySelector('.router-view');
      //准备一个渲染函数
      function render(){
      	routerView.innerHTML = template;
      }
      //导出渲染函数
      export default render;
      
    • fourth.js

      //组件的html结构
      const template = `
      	<div id="fourth">
      		fourth
      	</div>
      `;
      //获取到路由出口对象
      const routerView = document.querySelector('.router-view');
      //准备一个渲染函数
      function render(){
      	routerView.innerHTML = template;
      }
      //导出渲染函数
      export default render;
      
  3. router.js

    //导入组件模块
    import firstCom from './components/first.js';
    import secondCom from './components/second.js';
    import thirdCom from './components/third.js';
    import fourthCom from './components/fourth.js';
    // 定义路由表
    const router = [
    	{
    		name : '/first',
    		component: firstCom
    	},
    	{
    		name: '/second',
    		component: secondCom
    	},
    	{
    		name: '/third',
    		component: thirdCom
    	},
    	{
    		name: '/fourth',
    		component: fourthCom
    	}
    ];
    //导出路由
    export default router;
    
  4. index.js

    //1. 导入路由表
    import router from './router.js';
    //2. 注册hashchange 事件
    window.onhashchange = hashChange;
    hashChange();
    //3. 处理程序
    function hashChange(){
    	console.log('根据当前hash值,进行改变');
    	//获取当前的hash值
    	const hash = window.location.hash.slice(1) || '/';
    	// console.log(hash);
    	//从路由表中找到hash对应的那一个信息
    	const info = router.find(item => item.name === hash);
    	console.log(info);
    	//如果没有内容,不需要后续操作
    	if(!info){
    		return;
    	}
    	//渲染页面
    	info.component();
    }
    

g(‘根据当前hash值,进行改变’);
//获取当前的hash值
const hash = window.location.hash.slice(1) || ‘/’;
// console.log(hash);
//从路由表中找到hash对应的那一个信息
const info = router.find(item => item.name === hash);
console.log(info);
//如果没有内容,不需要后续操作
if(!info){
return;
}
//渲染页面
info.component();
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值