前端路由實現 history|hash

前言

在技術的世界,沒有奇蹟,只有精妙的,使人咂舌的技術運用。 ---- 南方小菜語
看到一句話,前端的革命性事件:ajax實現主動請求局部刷新,路由控制權的掌控;前者很好理解,後者越以爲很讓人驚喜,以往本身開發項目的固態思惟:javascript

  1. 後端服務器定義兩類接口,頁面跳轉、數據返回;
  2. 前端獲取數據並渲染頁面,關注界面及用戶體驗;
  3. 再加上一些數據庫加密避免http無狀態而利用session等等blablabla的小點。 但前端路由橫空出世,頓時天雷地火,把我後端的那點小而傻的沾沾自喜轟炸的渣都不剩,一個新事物的流行,必然在於它解決的一些問題,前端路由指出了傳統的三大問題:
  • 頁面跳轉白屏,刷新緩慢
  • 在某些場合中,用ajax請求,可讓頁面無刷新,頁面變了但Url沒有變化,用戶就不能複製到想要的地址
  • 加大服務器的壓力,代碼冗合。 因而一個全棧項目成爲了這樣的實現:
  1. 後端服務器定義數據返回API;
  2. 前端構建SPA應用,調用接口並基於MVVM進行雙向數據綁定渲染
  3. 前端路由實現無刷新內容更新

一如前端路由深似海,今後再無進度條css

固然,前端路由也存在缺陷:使用瀏覽器的前進,後退鍵時會從新發送請求,來獲取數據,沒有合理地利用緩存。但總的來講,如今前端路由已是實現路由的主要方式了。

思路實現

首先先不聊怎麼實現,先思考html

需求是什麼:前端無刷新更新掛載節點的內容
變化點是什麼: location

即點擊連接後url發生變化,每一個變化對應一個掛載點的內容(很天然的,咱們須要一個路由表,即路徑與掛載點內容的k-v,可用經過json實現)前端

找到合理的鉤子函數: (現而今主要是hashchange/popstate)

技術實現

傳統後端路由每次跳轉都刷新頁面,另發起一個新的請求,會給用戶帶來的白屏、耗時等較差體驗。所以前端路由採用的是當即加載的方式,再也不向服務器請求,而是加載路由對應的組件;而這種思路的實現主要採用兩種方案:hashchange 以及 historyvue

  • hash
    1. 基於hashchange事件,通過window.location.hash 獲取地址上的hash值
    2. 經過構造Router類,構造傳參配置routes對象設置hash與組件內容的對應
  • history
    1. 藉助vue的mvvm,經過vue中的data的current來設置要渲染的router-view,從而達到動態的spa

history

htmljava

<div>
        <a href="javascript:;" data-href="/">home</a>
        <a href="javascript:;" data-href="/book">book</a>
        <a href="javascript:;" data-href="/movie">movie</a>
        <div id="content"></div>
    </div>
複製代碼

jsajax

//路由類
class Router {
    constructor(opts) {
        //路由表
        this.routes = {},
            this.init();
        this.bindEvent();
        opts.forEach(item => {
            this.route(item.path, () => {
                document.getElementById('content').innerHTML = item.component;
            })
        })
    }
    init() {
        //頁面初始化時渲染路由
        window.addEventListener('load', this.updateView.bind(this));
        // 當活動歷史記錄條目更改時, 將觸發popstate事件。
        // 若是被激活的歷史記錄條目是經過對history.pushState() 的調用建立的,
        // 或者受到對history.replaceState() 的調用的影響,
        //  popstate事件的state屬性包含歷史條目的狀態對象的副本。
        window.addEventListener('popstate', this.updateView.bind(this));
    }
    // 路由渲染
    updateView() {
        const currentUrl = window.location.pathname || '/';
        this.routes[currentUrl] && this.routes[currentUrl]();
    }
    // 配置路由
    route(path, fn) {
        this.routes[path] = fn;
    }
    push(url){
        window.history.pushState({},null,url);
        this.updateView()
    }
    // 爲超連接綁定事件
    bindEvent() {
        const _this = this;
        const links = document.getElementsByTagName('a');
        [].forEach.call(links, link => {
            link.addEventListener('click', function () {
                const url = this.getAttribute('data-href');
                console.log(url);
                
                _this.push(url);
            })

        })
    }
}
//實例化路由
const router = new Router([{
            path: '/',
            component: 'home'
        }, {
            path: '/movie',
            component: 'movie'
        }, {
            path: '/book',
            component: 'book'
        }])

複製代碼

hash

hash也存在下面幾個特性:數據庫

URL中hash值只是客戶端的一種狀態,也就是說當向服務器端發出請求時,hash部分不會被髮送。 hash值的改變,都會在瀏覽器的訪問歷史中增長一個記錄。所以咱們能經過瀏覽器的回退、前進按鈕控制hash的切換。 咱們能夠使用hashchange事件來監聽hash的變化。json

出發hsah變化的方式也有兩種,一種是經過a標籤,並設置href屬性,當用戶點擊這個標籤後,URL就會發生改變,也就會觸發hashchange事件了後端

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <a href="#/" data-href="/">home</a>
    <a href="#/book" data-href="/">book</a>
    <a href="#/movie" data-href="/">movie</a>
    <div id="content">

    </div>
    <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
    <script>
        // window.onload = function (params) {
        //     window.location.href += '#/' 
        // }

        const Home = {
            template: '<div>home</div>'
        }
        const Book = {
            template: '<div>book</div>'
        }
        const Movie = {
            template: '<div>movie</div>'
        }
        class Router {
            constructor(opts) {
                // this.path = opts.path;
                // this.component = opts.component;
                // this.routes = opts.routes;
                this.routes = {

                }
                // console.log(opts);
                
                opts.forEach(item => {
                    this.route(item.path,()=>{
                        document.getElementById('content').innerHTML = item.component;
                    })
                })
                console.log(this.routes);
                
                this.init()
            }
            bindEvent() { }
            init() {
                window.addEventListener('load',this.updateView.bind(this))
                window.addEventListener('hashchange', this.updateView.bind(this))

            }
            updateView(e) {
                // console.log(e,'updated');
                // console.log(e.newURL.indexOf(e.oldURL));

                // console.log(e.newURL.substring(e.newURL.indexOf(e.oldURL)));
                const hashTag = window.location.hash.slice(1) || '/'
                console.log(window.location.hash.slice(1));
                this.routes[hashTag] && this.routes[hashTag]()

            }
            route(path,cb){
                this.routes[path] = cb;
            }
        }
        new Router([
            {
                path: '/',
                component: 'home',
            },
            {
                path: '/book',
                component: 'book'
            },
            {
                path: '/movie',
                component: 'movie'
            }
        ])
    </script>
</body>

</html>
複製代碼
相關文章
相關標籤/搜索