zoukankan      html  css  js  c++  java
  • 单页面路由原理及实现

    单页面路由原理及实现

    单页面路由即在前端单页面实现的一种路由,由于React,Vue等框架的火热,我们可以很容易构建一个用户体验良好的单页面应用,但是如果我们要在浏览器改变路由的时候,在不请求服务器的情况下渲染不同的内容,就要类似于后端的路由系统,在前端也实现一套完整的路由系统

    下面让我们来实现一个简单的路由系统。该路由系统将基于React进行书写。在写之前,我们先仔细想下,我们应该从哪方面入手。这是最终实现的效果simple-react-router-demo

    功能思考

    不论是前端还是后端路由,我们都可以通过一种路由匹配加匹配后回调的方式来实现。如果没有理解也没有关系,后面会理解。 我们用一个对象Router来表示我们的Router对象。先来想下我们要做哪些工作

    1. 配置路由模式historyhash
    2. 添加和删除路由
    3. 监听路由变化并调用对应路由回调
    4. 暴露路由跳转函数

    实现路由核心部分

    首先我们来实现我们的router-core.js

    const Router = {
    	routes: [], // 用来存放注册过的路由
    	mode: null, // 用来标识路由模式
    }
    复制代码

    我们用两个属性来存放我们需要存储的路由和路由模式

    下面在刚才的基础上添加一个config函数,让我们的路由能够配置

    const Router = {
    	// ... routes mode
    	config: (options) => {
    		Router.mode = options && options.mode && options.mode === 'history' && !!history.pushState ? 'history' : 'hash';
    		return Router;
    	}
    }
    复制代码

    config函数中我们通过传入的配置,来配置我们的路由模式,当且仅当options.mode === 'history'historyapi存在的时候我们设置Router模式为history。返回Router是为了实现链式调用,除了工具函数,后面其他函数也会保持这种写法。

    配置完Router模式后,我们要能够添加和删除路由

    const Router = {
    	// ... routes mode config
    	add: (pathname, handler) => {
    		Router.routes.push({ pathname: clearEndSlash(pathname), handler });
    		return Router;
    	},
    	remove: (pathname) => {
    		Router.routes.forEach((route, index) => {
    			if (route.pathname === clearEndSlash(pathname)) {
    				Router.routes.splice(index, 1);
    			}
    		});
    		return Router;
    	}
    }
    复制代码

    在添加和删除路由函数中,我们传入了名为pathname的变量,为了跟后面普通的path区分开。直白点来说,就是pathname对应/person/:id的写法,path对应/person/2的写法。

    这里有个clearEndSlash函数,是为了防止有/home/的写法。我们将尾部的/去掉。该函数实现如下

    const clearEndSlash = (path = '') => path.toString().replace(//$/, '') || '/';
    复制代码

    为了方便比较,在完成添加和删除后我们来实现一个match函数,来确定pathname是否和path相匹配。

    const Router = {
    	// ... routes mode config add remove
    	match: (pathname, path) => {
    		const reg = pathToRegexp(pathname);
    		return reg.test(path);
    	}
    }
    复制代码

    这里使用pathToRegexp函数将pathname解析成正则表达式,然后用该正则表达式来判断时候和path匹配。pathToRegexp的实现如下

    const pathToRegexp = (pathname, keys = []) => {
      const regStr = '^' + pathname.replace(//:([^/]+)/g, (_, name) => {
        keys.push({ name });
        return '/([^/]+)';
      });
      return new RegExp(regStr);
    }
    复制代码

    函数返回解析后的正则表达式,其中keys参数用来记录参数name。举个例子

    // 调用pathToRegexp函数
    const keys = [];
    const reg = pathToRegexp('/person/:id/:name', keys);
    console.log(reg, keys);
    
    // reg: /^/person/([^/]+)/([^/]+)/
    // keys: [ { name: 'id' }, { name: 'name' } ]
    复制代码

    好像有点扯远了,回到我们的Router实现上来,根据我们一开始列的功能,我们还要实现路由改变监听事件,由于我们有两种路由模式historyhash,因此要进行判断。

    const Router = {
    	// ... routes mode config add remove match
    	current: () => {
    		if (Router.mode === 'history') {
    			return location.pathname;
    		}
    		
    		return location.hash;
    	},
    	listen: () => {
    		let currentPath = Router.current();
    		
    		const fn = () => {
    			const nextPath = Router.current();
    			if (nextPath !== currentPath) {
    				currentPath = nextPath;
    				
    				const routes = Router.routes.filter(route => Router.match(route.pathname, currentPath));
    				routes.forEach(route => route.handler(currentPath));
    			}
    		}
    		
    		clearInterval(Router.interval);
    		Router.interval = setInterval(fn, 50);
    		return Router;
    	}
    }
    复制代码

    路由改变事件监听,在使用History API的时候可以使用popstate事件进行监听,Hash改变可以使用hashchnage事件进行监听,但是由于在不同浏览器上有些许不同和兼容性问题,为了方便我们使用setInterval来进行监听,每隔50ms我们就来判断一次。

    最后我们需要实现一个跳转函数。

    const Router = {
    	// ... routes mode config add remove match current listen
    	navigate: path => {
    		if (Router.mode === 'history') {
    			history.pushState(null, null, path);
    		} else {
    			window.location.href = window.location.href.replace(/#(.*)$/, '') + path;
    		}
    	}
    	return Router;
    }
    复制代码

    但这里我们基本已经完成了我们路由的核心部分。

    下一节,我们将在该核心部分的基础上,实现几个路由组件,以达到react-router的部分效果。

    这是原文地址,该部分的完整代码可以在我的Github的simple-react-router上看到,如果你喜欢,能顺手star下就更好了♪(・ω・)ノ,可能有部分理解错误或书写错误的地方,欢迎指正。

  • 相关阅读:
    《Maven实战》文字版[PDF]
    spring管理的类如何调用非spring管理的类
    从session中获取当前用户的工具类
    WebService,ESB笔记
    Activiti
    ElasticSearch最全分词器比较及使用方法
    [ElasticSearch]Java API 之 滚动搜索(Scroll API)
    从html富文本中提取纯文本
    Jetty启动报Error scanning entry META-INF/versions/9/org/apache/logging/log4j/util/ProcessIdUtil.class
    elasticsearch: 创建mapping
  • 原文地址:https://www.cnblogs.com/zhangycun/p/9547277.html
Copyright © 2011-2022 走看看