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版前端路由实现

下面说到基本上分为两种实现形式,别离通过 hashhistory实现。

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>

小结

以上大部分内容和原文差不多,次要是记录集体学习的一个过程,全程基本上本人手打,加深印象。共勉~