Front-end routing implementation of #hash

First go to the github project address:  spa-routers
running renderings
Running effect diagram

Background introduction

Many front-end frameworks are used for spaapplications. For example, backbone,angular,vuethey all have their own routing systems that manage each page switch in the front-end. If you want to understand the implementation of routing, the best way is to implement one manually.
There are two ways to implement front-end routing. One is launched by html5 historyapi. We are talking about another hashroute, which is a common  # number. This method has better compatibility.

demand analysis

We are simply implementing a routing wheel here. The basic functions include the following:

  1. switch page

  2. load js asynchronously

  3. Asynchronous parameter passing

Implementation steps

  1. Switching pages: The biggest role of routing is to switch pages. In the past, the routing in the background directly changed the url method of the page to refresh the page. However, the front-end routing cannot refresh the page through the # sign. It can only monitor the hash change through the window's listening event hashchange, and then capture the specific hash value for operation.

    //路由切换
    window.addEventListener('hashchange',function(){
        //do something 
        this.hashChange()
    })
  2. Register routing: We need to register routing rules to the page, so that the page will have different effects when switching.

    //注册函数
     map:function(path,callback){
       path = path.replace(/\s*/g,"");//过滤空格
       //在有回调,且回调是一个正确的函数的情况下进行存储 以 /name 为key的对象 {callback:xx}
       if(callback && Object.prototype.toString.call(callback) === '[object Function]' ){
           this.routers[path] ={
                callback:callback,//回调
                fn:null //存储异步文件状态,用来记录异步的js文件是否下载,下文有提及
            } 
        }else{
        //打印出错的堆栈信息
            console.trace('注册'+path+'地址需要提供正确的的注册回调')
        }
     }
     
     //调用方式
     map('/detail',function(transition){
      ...
      })
  3. Asynchronous loading of js: Generally, for performance optimization of single-page applications, the files of each page will be split and loaded on demand, so the function of asynchronously loading js files should be added to the routing. For asynchronous loading, we use the simplest native method, create script tags, and dynamically import js.

    var _body= document.getElementsByTagName('body')[0],
        scriptEle= document.createElement('script'); 
    scriptEle.type= 'text/javascript'; 
    scriptEle.src= xxx.js; 
    scriptEle.async = true;
    scriptEle.onload= function(callback){ 
        //为了避免重复引入js,我们需要在这里记录一下已经加载过的文件,对应的 fn需要赋值处理
        callback()
    } 
    _body.appendChild(scriptEle);     
  4. Parameter passing: After we dynamically introduce the js of a separate module, we may need to pass some separate parameters to this module. Here is a reference to the processing method of jsonp. We wrap the js of a separate module into a function, provide a global callback method, and call the callback function when the loading is complete.

    SPA_RESOLVE_INIT = function(transition) { 
        document.getElementById("content").innerHTML = '<p style="color:#F8C545;">当前异步渲染列表页'+ JSON.stringify(transition) +'</p>'
        console.log("首页回调" + JSON.stringify(transition))
    }

Extension: We have completed the basic functions above, we will align and expand, and add 2 methods to process it before the page switch beforeEachand when the switch is completed . afterEachThe idea is that after registering these two methods, call it before beforeEachswitching, after switching, you need to wait for the download js to complete, onloadand call it inside afterEach

        //切换之前一些处理
        beforeEach:function(callback){
            if(Object.prototype.toString.call(callback) === '[object Function]'){
                this.beforeFun = callback;
            }else{
                console.trace('路由切换前钩子函数不正确')
            }
        },
        //切换成功之后
        afterEach:function(callback){
            if(Object.prototype.toString.call(callback) === '[object Function]'){
                this.afterFun = callback;
            }else{
                console.trace('路由切换后回调函数不正确')
            }
        },

Through the analysis of the above ideas and integration, we have completed a simple front-end routing, which can be added to the page for actual SPA development, but it is still very simple.

full code

/*
*author:https://github.com/kliuj
**使用方法
*        1:注册路由 : spaRouters.map('/name',function(transition){
                        //异步加载js 
                        spaRouters.asyncFun('name.js',transition)
                        //或者同步执行回调
                        spaRouters.syncFun(function(transition){},transition)
                    })
        2:初始化      spaRouters.init()
        3:跳转  href = '#/name'            
*/
(function() {
    var util = {
        //获取路由的路径和详细参数
        getParamsUrl:function(){
            var hashDeatail = location.hash.split("?"),
                hashName = hashDeatail[0].split("#")[1],//路由地址
                params = hashDeatail[1] ? hashDeatail[1].split("&") : [],//参数内容
                query = {};
            for(var i = 0;i<params.length ; i++){
                var item = params[i].split("=");
                query[item[0]] = item[1]
            }        
            return     {
                path:hashName,
                query:query
            }
        }
    }
    function spaRouters(){
        this.routers = {};//保存注册的所有路由
        this.beforeFun = null;//切换前
        this.afterFun = null;
    }
    spaRouters.prototype={
        init:function(){
            var self = this;
            //页面加载匹配路由
            window.addEventListener('load',function(){
                self.urlChange()
            })
            //路由切换
            window.addEventListener('hashchange',function(){
                self.urlChange()
            })
            //异步引入js通过回调传递参数
            window.SPA_RESOLVE_INIT = null;
        },
        refresh:function(currentHash){
            var self = this;
            if(self.beforeFun){    
                self.beforeFun({
                    to:{
                        path:currentHash.path,
                        query:currentHash.query
                    },
                    next:function(){
                        self.routers[currentHash.path].callback.call(self,currentHash)
                    }
                })
            }else{
                self.routers[currentHash.path].callback.call(self,currentHash)
            }
        },
        //路由处理
        urlChange:function(){
            var currentHash = util.getParamsUrl();
            if(this.routers[currentHash.path]){
                this.refresh(currentHash)
            }else{
                //不存在的地址重定向到首页
                location.hash = '/index'
            }
        },
        //单层路由注册
        map:function(path,callback){
            path = path.replace(/\s*/g,"");//过滤空格
            if(callback && Object.prototype.toString.call(callback) === '[object Function]' ){
                this.routers[path] ={
                    callback:callback,//回调
                    fn:null //存储异步文件状态
                } 
            }else{
                console.trace('注册'+path+'地址需要提供正确的的注册回调')
            }
        },
        //切换之前一些处理
        beforeEach:function(callback){
            if(Object.prototype.toString.call(callback) === '[object Function]'){
                this.beforeFun = callback;
            }else{
                console.trace('路由切换前钩子函数不正确')
            }
        },
        //切换成功之后
        afterEach:function(callback){
            if(Object.prototype.toString.call(callback) === '[object Function]'){
                this.afterFun = callback;
            }else{
                console.trace('路由切换后回调函数不正确')
            }
        },
        //路由异步懒加载js文件
        asyncFun:function(file,transition){
           var self = this;
           if(self.routers[transition.path].fn){
                   self.afterFun && self.afterFun(transition)     
                   self.routers[transition.path].fn(transition)
           }else{
                  console.log("开始异步下载js文件"+file)
               var _body= document.getElementsByTagName('body')[0]; 
               var scriptEle= document.createElement('script'); 
               scriptEle.type= 'text/javascript'; 
               scriptEle.src= file; 
               scriptEle.async = true;
               SPA_RESOLVE_INIT = null;
               scriptEle.onload= function(){ 
                   console.log('下载'+file+'完成')
                   self.afterFun && self.afterFun(transition)     
                   self.routers[transition.path].fn = SPA_RESOLVE_INIT;
                   self.routers[transition.path].fn(transition)
               } 
               _body.appendChild(scriptEle);         
           }        
        },
        //同步操作
        syncFun:function(callback,transition){
            this.afterFun && this.afterFun(transition)
            callback && callback(transition)
        }

    }
    //注册到window全局
    window.spaRouters = new spaRouters();
})()

Simple single page with full demo
spa-routers on github

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324966151&siteId=291194637