# 前后端路由

路由可以看成是URL到函数的映射,访问URL时,路由会以URL的数据为参数,匹配相应的返回值给这个URL。

在后端路由中,进行页面切换的时候,浏览器发送不同的url请求,服务器接收到浏览器的请求时,通过解析不同的url去拼接需要的html或者模板,然后将结果返回给浏览器进行渲染。后端路由的优点是安全行更高,利于SEO;缺点是增加了服务端的负担,并且需要reload 页面。

需要注意的是,后端路由的url地址和请求的 method 也应该是一一对应的。假设后端只配置了了一个 post 请求的路由,那么当发出一个 get 请求的时候是访问不到的。

    // http://xxx.com/user/123  get请求
    router.get('/user/:id', getUser) // 需要配置get请求
    router.post('/user/:id', addUser)

前端路由使客户端浏览器不依赖于服务端,根据不同的URL渲染不同的视图页面。使用前端路由减轻了服务端的压力,同时,页面的切换可以不需要刷新整个页面,没有闪烁刷新,提升了体验。

PS:前端路由广泛应用于 SPA 项目中,例如由 Vue 构建的 SPA 应用,这种应用从服务端拿到的 html 只包含一个 <div id="#app"></div> 这样的入口以及一系列的 js 文件。页面通过 js 渲染出来,这就是常说的前端渲染。前端渲染将页面渲染的任务交给了浏览器,通过客户端的算力来解决页面构建。缓解了服务端的压力且页面切换体验更好,但是对 SEO 不友好。

# 前端路由实现方式

实现前端路由,需要解决的问题是:

  1. 在页面不刷新的前提下实现url变化
  2. 监听url变化,以便执行页面替换逻辑

实现前端路由有两种方式,一种是 hash 方式。这种方式利用浏览器不会对 # 后面的路径对服务端发起路由请求。也就是说,http://localhost/#/userhttp://localhost/ 这两个地址请求的都是 http://localhost 这个页面,前端再根据后面 hash 值的变化决定渲染的内容。另一种是 History 方式,这种方式使用 HTML5 新提供的 History API 。需要 IE10 以上的浏览器才支持。通过 pushStatereplaceState 改变url数据,history.go()history.back()history.forward() 等方法触发 popstate 事件进而决定页面渲染内容。

# hash

    // main.js
    class Routers {
        constructor() {
            this.routes = {} // 存放hash及对应的callback
            this.currentUrl = ''
            this.refresh = this.refresh.bind(this)
            window.addEventListener('load', this.refresh, false)
            window.addEventListener('hashchange', this.refresh, false)
        }

        route(path, callback) {
            this.routes[path] = callback || function() {}
        }

        refresh() {
            this.currentUrl = location.hash.slice(1) || '/'
            this.routes[this.currentUrl]()
        }
    }

    window.Router = new Routers()
    const content = document.querySelector('body')

    function changeColor(color) {
        content.style.backgroundColor = color
    }

    Router.route('/', () => {
        changeColor('yellow')
    })

    Router.route('/blue', () => {
        changeColor('blue')
    })

    Router.route('/green', () => {
        changeColor('green')
    })
    <!-- index.html -->
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>hash router</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <ul>
            <li>
                <a href="#/">turn yellow</a>
                <a href="#/blue">turn blue</a>
                <a href="#/green">turn green</a>
            </li>
        </ul>
    </body>
    <script src="main.js"></script>
    </html>

# history

    class Routers {
        constructor() {
            this.routes = {}
            this.bindPopSate()
        }

        // 初始化路由
        init(path) {
            history.replaceState({path: path}, null, path)
            this.routes[path] && this.routes[path]()
        }

        route(path, callback) {
            this.routes[path] = callback || function() {}
        }

        // 未触发 popstate 事件的路由变化
        go(path, e) {
            // 解决本地测试跨域问题
            const fullPath = `file:///C:/Users/Administrator/Desktop/history/index.html?${e.target.getAttribute('href')}`
            history.pushState({path: path}, null, fullPath)
            this.routes[path] && this.routes[path]()
        }

        bindPopState() {
            window.addEventListener('popstate', e => {
                console.log(e.state)
                const path = e.state && e.state.path
                this.routes[path] && this.routes[path]()
            })
        }
    }

    window.Router = new Routers()
    Router.init(location.pathname)
    const content = document.querySelector('body')
    const ul = document.querySelector('ul')

    function changeColor(color) {
        content.style.backgroundColor = color
    }

    Router.route('/', () => {
        changeColor('yellow')
    })

    Router.route('/blue', () => {
        changeColor('blue')
    })

    Router.route('/green', () => {
        changeColor('green')
    })

    ul.addEventListener('click', e => {
        if (e.target.tagName === 'A') {
            e.preventDefault()
            Router.go(e.target.getAttribute('href'),e)
        }
    })
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>hash router</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <ul>
            <li>
                <a href="/">turn yellow</a>
                <a href="/blue">turn blue</a>
                <a href="/green">turn green</a>
            </li>
        </ul>
    </body>
    <script src="main.js"></script>
    </html>

参考:

history api (opens new window)

浅谈前后端路由与前后端渲染 (opens new window)

面试官: 你了解前端路由吗? (opens new window)

Last Updated: 2/24/2020, 9:11:55 PM