美文网首页
前端路由解析以及实现 (学习)

前端路由解析以及实现 (学习)

作者: WEB前端含光 | 来源:发表于2020-08-06 16:02 被阅读0次

    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 = null
    
    function 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 = null
    
    function 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>
    

    如果你现在也想学习前端开发技术,在学习前端的过程当中有遇见任何关于学习方法,学习路线,学习效率等方面的问题,你都可以申请加入我的Q群:前114中6649后671,里面有许多前端学习资料 大厂面试真题免费获取,希望能够对你们有所帮助。

    相关文章

      网友评论

          本文标题:前端路由解析以及实现 (学习)

          本文链接:https://www.haomeiwen.com/subject/jwttdktx.html