1. 什么是前端路由
路由的概念来源于服务端,在服务端中路由形容的是URL与处理函数之间的映射关系。
在web前端单页面利用中,路由形容的是 URL 和 UI 之间的映射关系,这种映射关系是单向的,即 URL 变动引起 UI 更新。
2. 如何实现前端路由
要实现前端路由,须要解决两个外围:
- 如何扭转URL 却不引起页面刷新
- 如何检测URL 变动了
上面别离应用hash 和 history两种实现形式答复下面的两个外围问题。
2.1通过hash实现
- hash是URL中#以及前面的那局部,罕用作锚点在页面内进行导航,扭转URL中的hash局部不会引起页面刷新。
- 通过 hashchange监听URL的扭转。通过浏览器后退后退,通过a标签扭转,通过window.location扭转都会触发hashchange事件。
2.2通过history实现
- history 提供了pushState 和 popState办法,这两个办法扭转URL的path局部不会引起页面刷新。
- history 提供 popState事件,能够监听浏览器的后退后退事件。通过pushState/replaceState或者a标签扭转URL不会触发popState事件,好在咱们能够拦挡pushState、replaceState、a标签的点击事件来检测URL变动,所以监听URL变动能够实现,只是没有hashChange那么不便。
3. 原生JS版前端路由实现
下面说到基本上分为两种实现形式,别离通过 hash 和 history实现。
3.1 基于hash实现
<body> <ul> <!-- 定义路由 --> <li><a href="#/home">home</a></li> <li><a href="#/about">about</a></li> <!-- 渲染路由对应的 UI --> <div id="routeView"></div> </ul></body>
// 页面加载,被动触发一次window.addEventListener('DOMContentLoaded', onLoad)window.addEventListener('hashchange', onHashChange)var routerView = nullfunction onload() { routerView = document.querySelector('#routerView')}function onHashChange() { switch(location.hash) { case '#/home': routerView.innerHTML = 'HOME' return case '#/about': routerView.innterHTML = 'About' return default: return }}
3.2基于history实现
<body> <ul> <li><a href='/home'>home</a></li> <li><a href='/about'>about</a></li> <div id="routeView"></div> </ul></body>
window.addEventListener('DOMContentLoaded', onLoad)window.addEvenetListener('popState', onPopState)var routerView = nullfunction onLoad() { routerView = document.querySelector('#routerView') onPopState() // 拦挡a标签 var linkList = document.querySelectorAll('a[href]') linkList.forEach(el => { el.addEventListener('click', function(e){ e.preventDefault() history.pushState(null, '', el.getAttribute('href') onPopState() }) })}function onPopState() { switch(location.pathname) { case '/home': routerView.innterHTML = 'HOME' return case '/about': routerView.innerHTML = 'about' return default: return }}
4. React版前端路由实现
4.1基于hash实现
<BrowserRouter> <ul> <li> <Link to="/home">home</Link> </li> <li> <Link to="/about">about</Link> </li> </ul> <Route path="/home" render={() => <h2>Home</h2>} /> <Route path="/about" render={() => <h2>About</h2>} /> </BrowserRouter>
BrowerRouter实现:
export deafult class BrowerRouter extends React.Component { state = { currentPath: util.extractHashPath(window.location.href) } onHashChange = (e) => { const currentPath = util.extrachHashPath(e.newURL) this.setState({currentPath}) } componentDidMount() { window.addEventListener('hashChange', this.onHashChange) } componentWillUnmount() { window.removeEventListner('hashChange',this.onHashChange) } render() { return ( <RouteContext.Provider value={{currentPath: this.state.currentPath}}> {this.props.children} </RouteContext.Provider> ) }}
Route实现
export default ({path, render}) => { <RouteContext.Consumer> {{(currentPath) => currentPath === path && render()}} </RouteContext.Consumer>}
Link实现
export default ({ to, ...props }) => <a {...props} href={"#" + to} />;
4.2基于history实现
export default class HistoryRouter extends React.Component { state = { currentPath: utils.extractUrlPath(window.location.href) } onPopState = (e) => { const currentPath = utils.extractUrlPath(window.location.href) this.setState({currentPath}) } componentDidMont() { window.addEventListener('popState', this.onPopState) } componentWillUnmount() { window.removeEventListener('popstate', this.onPopState) } render() { return ( <RouteContext.Provider value={{currentPath: this.state.currentPath, onPopState: this.onPopState}}> {this.props.children} </RouteContext.Provider> ); }}
Route的实现
export default ({path, render}) => ( <RouteContext.Consumer> {({currentPath}) => {currentPath === path && render()}} </RouteContext.Consumer>)
Link实现
export default ({to, ...props}) => { <RouteContext.Consumer> {({onPopState}) => ( <a href="" {...props} onClick={ e=> { e.preventDefault() window.history.pushState(null, "", to) onPopState() } }> )} </RouteContext.Consumer>}
5. Vue版本前端路由实现
5.1基于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>' }}const app = new Vue({ el: '', 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>
router-link实现:
<template> <a @click.prevent="onClick" href=''><slot></slot></a></template><script>export default { props: { to: String }, methods: { onClick () { window.location.hash = '#' + this.to } }}</script>
5.2基history实现
<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.history', components: { 'router-view': RouterView, 'router-link': RouterLink }, created () { this.$routes = routes this.boundPopState = this.onPopState.bind(this) }, beforeMount () { window.addEventListener('popstate', this.boundPopState) }, beforeDestroy () { window.removeEventListener('popstate', this.boundPopState) }, methods: { onPopState (...args) { this.$emit('popstate', ...args) } }})
router-view 实现:
<template> <component :is="routeView" /></template><script>import utils from '~/utils.js'export default { data () { return { routeView: null } }, created () { this.boundPopState = this.onPopState.bind(this) }, beforeMount () { this.$root.$on('popstate', this.boundPopState) }, beforeDestroy() { this.$root.$off('popstate', this.boundPopState) }, methods: { onPopState (e) { const path = utils.extractUrlPath(window.location.href) this.routeView = this.$root.$routes[path] || null console.log('[Vue] popstate:', path) } }}</script>
router-link实现:
<template> <a @click.prevent="onClick" href=''><slot></slot></a></template><script>export default { props: { to: String }, methods: { onClick () { history.pushState(null, '', this.to) this.$root.$emit('popstate') } }}</script>
小结
以上大部分内容和原文差不多,次要是记录集体学习的一个过程,全程基本上本人手打,加深印象。共勉~