Router In Vue

148 阅读3分钟

Router In Vue

回想起直接参加的面试中,又被问到关于Router部分知识,当时没答好,后面慢慢的都忘了,现在重新捞起来,重新好好学习一轮。

文章涵盖知识点:路由基本钩子函数,Hash和History原理和手写,以及部分面试题

背景

最主要的目的更好的服务SPA(单页面),即当改变地址栏时候,该整个页面不会刷新(利用好axios 进行局部刷新形式)

hash

表示的是URL地址都中 # 符号 不会重新加载页面

缺点:

  • 地址栏携带# 不美观
  • 有体积限制

history

  • 利用了 HTML5 History Interface 中新增的 pushState()replaceState() 方法。(需要特定浏览器支持)
  • 基于浏览器的历史记录栈 对当前已有的back forward go

钩子函数

全局守卫

router.beforeEach( (to,from,next) => { })

to from 即将进入的目标路由对象 当前导航的路由对象

next(): 进行管道中的下一个钩子。

next(false): 中断当前的导航。回到 from 路由对应的地址。

next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址 可传递的参数与 router.push 中选项一致。

next(error): 导航终止,且该错误会被传递给 router.onError() 注册过的回调

特点;

在全局守卫完成之前导航都与一直处在等待中

router.beforeResolve( (to,from,next) => { })

全局解析守卫,在路由跳转前,所有 组件内守卫异步路由组件 被解析之后触发,它同样在 每次导航 时都会触发

router.afterEach( (to,from) => { })

路由守卫

beforeEnterbeforeEach 一样

组件守卫

beforeRouterEnter( (to,from,next) => {})

特点:

在全局守卫beforeEach和路由守卫beforeEnter后 全局 beforeResolve 和全局 afterEach 之前

在beforeCreate生命周期前 因此没有this

beforeRouteUpdate( (to,from)=> {} )

特点:this已经可以使用

beforeRouteLeave( (to,from)=> {} )

特点:this已经可以使用

实现手段

hash(只需要去监听hashchange

原理: 去监听hashchange(所有地址的变化都会触发该事件)事件 当事件发生变化时候 进行一系列操作

<body>
    <div id="content"></div>
    <nav>
        <a href="#/">1</a>
        <a href="#/2">2</a>
        <a href="#/3">3</a>
    </nav>
    <button onclick="push()">push形式到达界面2</button>
</body>
<script>
    class Router {
        constructor(routes = []) {
            /* 存储所有的路由节点信息 */
            this.routes = routes
            /* 存储当前界面 */
            this.currentHash = ''
            this.refresh = this.refresh.bind(this)
            
            /* 监听事件hashchange */
            window.addEventListener('load', this.refresh, false)
            window.addEventListener('hashchange', this.refresh, false)
        }
​
        getUrlPath(url) {
            /* 获取URL hash后的值 */
            return url.indexOf('#') >= 0 ?
                url.slice(url.indexOf('#') + 1) :
                '/'
        }
​
        refresh(event) {
            /* 更新路由 */
            let newHash = ''
            /* 看是否有新路由变化 */
            if(event.newURL) {
                newHash = this.getUrlPath(event.newURL || '')
            }
            /* 没有新路由变化 调用当前路由进行显示(一般在首屏加载时触发) */
            else {
                newHash = this.getUrlPath(window.location.hash)
            }
            this.currentHash = newHash
            this.matchComponeny()
        }
​
        /* 路由发生变化时 刷新界面 */
        matchComponeny() {
            let curRoute = this.routes.find(route => 
                route.path === this.currentHash
            )
            /* 在没查到该路由 会重定向到 / */
            if(!curRoute) {
                curRoute = this.routes.find(route => 
                    route.path === '/'
                )
            }
            const { component } = curRoute
            /* 修改视图 */
            document.querySelector('#content').innerHTML = component
        }
    }
​
    const router = new Router([
        {
            path: '/',
            name: '/',
            component: '第1个界面'
        },
        {
            path: '/2',
            name: '2',
            component: '第2个界面'
        },
        {
            path: '/3',
            name: '3',
            component: '第3个界面'
        },
    ])
​
    /* 使用push形式进行调用 */
    function push() {
        window.location.hash = '/2'
    }
</script>

history

原理:

需要去监听popstate (目的能监听back forward go触发

监听pushState replaceState(使得能直接触发到popstate形式

<body>
    <button id="btn1">1</button>
    <button id="btn2">2</button>
    <button id="btn3">3</button>
</body>
<script>
    const btn1 = document.getElementById('btn1')
    const btn2 = document.getElementById('btn2')
    const btn3 = document.getElementById('btn3')
​
    /* 将history的触发事件赋值到window上 */
    /* 这样在history触发事件 能在window上监听到 并进行一定的修改视图 */
    let _addEvent2Window = function (type) {
        let orig = history[type]
        return function () {  
            let rv = orig.apply(this, arguments)
            let e = new Event(type)  
            e.arguments = arguments
            console.log(e)
            /* 需要主动形式也触发到window上的事件 */
            window.dispatchEvent(e)
            return rv
        }
    }
​
    /* 监听事件赋值到window上 */
    history.pushState = _addEvent2Window('pushState')
    history.replaceState = _addEvent2Window('replaceState')
​
    btn1.addEventListener('click', () => {  
        history.pushState({}, null, '/1')
    })
    btn2.addEventListener('click', () => {  
        history.replaceState({}, null, '/2')
    })
    btn3.addEventListener('click', () => {  
        history.pushState({}, null, '/3')
    })
​
    /* 相对于监听window下pushstate 和 replacestate事件 */
    /* 其中的arguments[2] 能获取到需要到达的目标地址 */
    window.addEventListener('pushState', function (e) {
        console.log('pushState', e, arguments)
    })
    window.addEventListener('replaceState', function (e) {
        console.log('repaceState',e, arguments)
    })
    
    /* 监听popstate 即使用back forward go时候能触发到 */
    window.addEventListener('popstate', function (e) {
        console.log('popState', e, arguments)
    })
</script>

部分面试题

1.为什么配置history模式下,部署在服务器上使用地址访问会出现404?如何解决?(Nginx下

因为使用Hash模式下,哈希后的东西是不会发送出去的

当前的地址假设为: http://127.0.0.1:8081/router.html
发送到服务器的东西是: http://127.0.0.1:8081/router.html
​
当地址发送变化: http://127.0.0.1:8081/router.html#/5
发送到服务器的东西依旧:http://127.0.0.1:8081/router.html
(即是服务器不会根据地址 重新去访问返回的

所以在History下产生的原因就很简单了

当前的地址假设为: http://127.0.0.1:8081/router/index.html 
发送到服务器的东西是: http://127.0.0.1:8081/router/index.html

当地址发送变化: http://127.0.0.1:8081/router/a
发送到后端的东西是:http://127.0.0.1:8081/router/a
(导致服务器在该地址下找不到该资源 就会返回404

处理方案:

既然是服务器找不到该资源,那在找不到该资源下进行修改,使得所有404情况都访问router/index.html

location /router {
    try_files $uri $uri/ /index.html;
    index index.html;
}

同时最好在路由也设置404界面,防止是真的找不到形式(而且需要放置到最后

routes: [{
    path: "/",
    component: index
}, {
    path: "*",
    component: errPage
}]