前端路由
路由的概念来源于服务端,在服务端在中描述的是URL与处理函数之间的映射关系。
在web前端单页面应用中,路由描述的是URL与UI之间的映射关系,这种映射是单向的,即URL变化引起UI更新(无需刷新页面)
如何实现前端路由
- 如何改变URL却不引起页面刷新?
- 如何检测URL变化?
hash实现
- hash是URL中的hash(#)及后面的那部分,常用作锚点在页面内导航,改变URL的hash部分不会引起页面刷新
- 通过hashchange事件监听URL的变化,改变URL的方式只有几种:通过浏览器前进后退改变URL、通过a标签改变URL、通过window.location改变URL,这几种情况改变URL都会触发hashchange事件
<ul>
<!-- 定义路由 -->
<li><a href="#/home">home</a></li>
<li><a href="#/about">about</a></li>
<!-- 渲染路由对应的 UI -->
<div id="routerView"></div>
</ul>
//页面加载完不会促发hashchange, 这里主动触发一次hashchange
window.addEventListener('DOMContentLoaded',onLoad)
var routerView = null
function onLoad(){
routerView.document.querySelector('#routerView')
onHashChange
}
//监听URL的改变
window.addEventListern('hashchange',onHashChange)
//路由变化时,根据URL更新UI视图
function onHashChange(){
switch(location.hash){
case '#/home':
routerView.innerHTML = "我是home页面";
return;
case '#/about':
routerView.innerHTML = "我是about页面"
return;
default: return;
}
}
history实现
- history提供给了pushState和replaceState两个方法,这两个方法改变URL的path部分不会引起页面刷新
- history提供类似hashchange事件的popstate事件,但popstate事件有些不同:通过浏览器前进改变URL时会触发popstate事件,通过pushState/replaceState或a标签改变URL不会触发popstate事件。好在我们可以拦截pushState/replaceState的调用和 a 标签的点击事件来检测URL变化,所以监听URL变化可以实现,只是没有hashchange那么方便。
function onLoad(){
routerView.document.querySelector('#routerView')
onPopState()
//拦截a标签点击事件默认行为,点击时使用pushState修改URL并更新手动UI,从而实现点击链接更新URL和UI的效果
vae linkList = document.querySelector('a[href]')
linkList.forEach(el => el.addEventListener('click',function(e){
e.preventDefault()
history.pushState(null,'', el.getAttribute('href'))
onPopState()
}))
}
//history提供popstate监听URL的变化(前进后退改变URL时触发)
function onPopState(){
switch(location.pathname){
case '/home':
routerView.innerHTML = 'history模式下:我是home页面'
return
case '/about':
routerView.innerHTML = 'history模式下: 我是about页面'
return
default: return
}
}
Vue版本前端路由实现
基于hash实现
使用方式和vue-router类似(vue-router通过插件机制注入路由,但是这样隐藏了实现细节,为了保持代码只管,这里没有使用vue插件封装)
<div>
<ul>
<li><router-link to="/home">home</router-link></li>
<li><router-link to="/about">about</router-link></li>
</ul>
<router-view></router-view>
</div>
const routes = {
'/home': {
template: '<h2>Home</h2>'
},{
'/about': {
template: '<h2>About</h2>'
}
}
}
const app = new Vue({
el: '.vue.hash',
components: {
'router-view': RouterView,
'router-link': RouterLink
},
beforeCreate () {
this.$routes = routes
}
})
router-view实现
<template>
<component :is="routeView" />
</template>
<script>
import utils from '~/utils.js'
export default {
data () {
return {
routeView: null
}
},
created () {
this.boundHashChange = this.onHashChange.bind(this)
},
beforeMount () {
window.addEventListener('hashchange', this.boundHashChange)
},
mounted () {
this.onHashChange()
},
beforeDestroy() {
window.removeEventListener('hashchange', this.boundHashChange)
},
methods: {
onHashChange () {
const path = utils.extractHashPath(window.location.href)
this.routeView = this.$root.$routes[path] || null
console.log('vue:hashchange:', path)
}
}
}
</script>
<template>
<a @click.prevent="onClick" href=''><slot></slot></a>
</template>
<script>
export default {
props: {
to: String
},
methods: {
onClick () {
window.location.hash = '#' + this.to
}
}
}
</script>