关于router:从零开始用elementui躺坑vue-router

Vue Router 是 Vue.js 官网的路由管理器。它和 Vue.js 的外围深度集成,让构建单页面利用变得大海捞针。学习Vue,Vue Router是必须要把握的技能之一。官网教程是最好的的浏览文献,请仔细阅读 利用场景首先,咱们聊一个vue-router的典型利用场景 产品经理: 某天,产品经验拿着掘金APP说,这是APP很漂亮,咱们也要做一个Web APP,款式呢,照抄掘金APP的款式就行。 要求: 底部导航栏: 底部一排固定不动的按钮顶部导航栏: 顶部**也有一排固定不动的按钮顶部也有一个带有返回按钮的导航栏固定不动,点击底部导航栏之后,两头局部的页面应该跳转到对应性能页面,点击顶部的返回按钮之后,应该返回到之前的页面。加载页面时尽可能的缩小刷新,甚至是不刷新。优化用户体验加载速度肯定要疾速,且尽可能的缩小流量的耗费URL要好看丑陋,并且URL能够动静承受参数。如:http://music.163.com/#/my/m/m...需要剖析菜鸟: 菜鸟拿到需要,前三个需要比较简单,应用css的display属性进行切换就能够搞定。第四个和第五个需要怎么搞呢?加载页面的时候,必定要做页面申请的,页面资源也是很大的流量的。至于第6个性能嘛,好难搞呀。抓耳挠腮中...... 老鸟: 菜鸟呀,看你这苦思冥想的,遇到什么难题了? 菜鸟: 天杀的产品经理,让我做我的项目,还提了一堆附加要求,我都想拿大刀砍死他了。与产品经理唇齿相依 老鸟: 哎呀,别着急呀。一看你就是陷入传统开发的思维定势了。都2020年了,还不晓得单页面开发,你太out喽? 菜鸟:何为单页面开发呀,给我讲讲呗?这么神奇的吗?能解决我的难题? 老鸟:内事不决为百度,当初学习也为时不晚。哈哈。 菜鸟:努力学习中...... 何为单页面只有一个WEB主页面的利用,公共资源(js、css等)仅需加载一次,所有的内容都蕴含在主页面,对每一个功能模块组件化。单页利用跳转,就是切换相干组件,仅刷新部分资源。罕用于PC端官网、购物等网站。 何为多页面每一次页面跳转的时候,后盾服务器都会给返回一个新的html文档,这种类型的网站也就是多页网站,也叫做多页利用。传统页面的写法大多为多页面 Vue-Router装置npmnpm install vue-router// main.jsimport Vue from 'vue'import VueRouter from 'vue-router'Vue.use(VueRouter)根本路由配置export const constantRouterMap = [ ..., { path: '/', component: Layout, // 重定向 redirect: '/dashboard', // 嵌套多级路由 children: [ { path: 'dashboard', component: () => import('@/views/Dashboard'), name: 'Dashboard', // 路由元信息 meta: { ... } } ] }, ...]路由拆分 ...

August 26, 2022 · 1 min · jiezi

关于router:React-Router学习笔记

路由是干嘛的? 路由三要素Routers,Route Matchers,Navigation(or Route Changes) Routers  <BrowserRouter> &<HashRouter>    区别:保留URL的形式和与web服务器通信的形式 Route MatchersSwitch & Route <Switch>渲染时,它会搜寻通过其孩子<Route>元素找到一个匹配以后URL的门路。当它找到一个匹配的,它会渲染这个<Route>并疏忽其余的<Route>。<Route path="/">通常放在最初面。 这里不得不提一下React Router路由配置和路由的匹配原理。路由匹配原理蕴含三个方面:嵌套关系,门路语法,优先级 嵌套关系:嵌套路由被形容成一种树形构造。React Router 会深度优先遍历整个路由配置来寻找一个与给定的 URL 相匹配的路由。 门路语法:路由门路是匹配一个(或一部分)URL 的 一个字符串模式。大部分的路由门路都能够间接依照字面量了解,除了以下几个非凡的符号: :paramName – 匹配一段位于 /、? 或 # 之后的 URL。 命中的局部将被作为一个参数() – 在它外部的内容被认为是可选的* – 匹配任意字符(非贪心的)直到命中下一个字符或者整个 URL 的开端,并创立一个 splat 参数<Route path="/hello/:name"> // 匹配 /hello/michael 和 /hello/ryan<Route path="/hello(/:name)"> // 匹配 /hello, /hello/michael 和 /hello/ryan<Route path="/files/*.*"> // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg 优先级:路由算法会依据定义的程序自顶向下匹配路由。因而,当你领有两个兄弟路由节点配置时,你必须确认前一个路由不会匹配后一个路由中的门路。例如,千万不要这么做: <Route path="/comments" ... /><Redirect from="/comments" ... />Navigation(or Route Changes)link & Redirect <Link to="/">Home</Link> // 或者能够用这个代替 <a href="/">Home</a><Redirect to="/login" /> 没有Switch包裹的状况下:http://localhost:3000/about会匹配两个组件Home和About,原理:他会对每个path都进行匹配 <Route path ="/"> <Home/> </Route> //改为 <Route exact path ="/"> <Home/> </Route>有Switch包裹的状况下,会依照先后顺序进行匹配,匹配实现后不会再匹配其余路由,这时候能够将这句返回Switch的孩子Route组件的最初一个,而且这个"/"能够匹配除了后面路由的任何一个 <Route path ="/"> <Home/> </Route>这个时候发现有一个问题,就是app中的跳转列表是不会隐没的,个别状况下咱们可能须要间接跳转到一个新页面 ...

September 15, 2021 · 1 min · jiezi

关于router:SAP-UI5-Routing-路由介绍

官网链接:https://sapui5.hana.ondemand.... 一个例子: "routing": { "config": { "routerClass": "sap.m.routing.Router", "viewType": "XML", "viewPath": "kyma.sample.app.view", "controlId": "app", "controlAggregation": "pages", "async": true }, "routes": [ { "pattern": "", "name": "orders", "target": ["orders"] } ], "targets": { "orders": { "viewName": "Orders", "viewId": "orders", "viewLevel": 1, "title": "{i18n>title}" } } 在 manifest.json 文件的 sap.ui5 区域,增加了三个子区域: (1) config(2) routes(3) target configThis section contains the global router configuration and default values that apply for all routes and targets.蕴含了全局路由器信息和适应于所有路由门路和指标的默认值。 ...

June 16, 2021 · 1 min · jiezi

React-SPA-应用-hash-路由如何使用锚点

当我们在做 SPA 应用的时候,为了兼容老的浏览器(如IE9)我们不得不放弃 HTML5 browser history api 而只能采用 hash 路由的这种形式来实现前端路由,但是因为 hash 被路由占据了,导致本来不是问题的锚点功能却成了一个不大不小的问题。 经过我自己的搜索目前有两种方式能够解决这个问题,为了能在 react 生态下面简单优雅的使用,我专门封装了一个锚点组件 react-anchor-without-hash,它使用了类似原生 a 标签的写法,并且可以支持滚动的距离和指定滚动的元素,尽可能的满足业务的需求。 不过在介绍这个组件之前,还是得先说一下两种基本的解决方案。 两种解决方案scrollIntoViewscrollIntoView 方法可以让当前的元素滚动到浏览器窗口的可视区域内。 它的使用方法如下: var element = document.getElementById("box");element.scrollIntoView();这个 api 兼容 IE8 及以上的浏览器,所以可以放心使用。 注:IE10 之前的 IE 浏览器部分支持,具体请查看Can I Use。scrollTop另一个方法是使用 scrollTop 这个 api,这个方法的兼容性也是比较好的,这个方法相比于 scrollIntoView 来说需要你自己定义要滚动的元素和要滚动的高度,虽然看起来麻烦一些,但是好处是自由度比较高,试想一下下面的场景: 你有一个 A 元素在 Content 里面,Content 设置了滚动,你想让 A 元素滚动到可视区域内。 如果用 scrollIntoView 会变成下面这样,A 元素显示到整个浏览器视口的最上面,这样就会和 Header 重合,被遮挡住一部分。 所以这时候需要使用 scrollTop 去设置 content 滚动距离,比如说是 60px,最后的效果就变成了我们想要的结果。 使用方式如下: const cont = document.querySelector('#container');const a = document.querySelector('#a');cont.scrollTop = a.offsetTop + 60;react-anchor-without-hash以上两种方式如果想方便的在项目里面使用多少都需要封装一下,而且使用起来和原生的 a 标签形式也相差甚远。 ...

August 19, 2019 · 1 min · jiezi

vue项目全家桶vuevueroutervuexaxios

axios: 相当于ajax,之前是用vue-resourse,不过现在这个模块不维护了,基本使用axiosvue-router: 是vue的路由vuex: 是vue的状态管理,方便组件间通信 npm install vue-router vuex axios --save-dev

August 18, 2019 · 1 min · jiezi

Angular-路由

概述路由所要解决的核心问题就是通过建立URL和页面的对应关系,使得不同的页面可以用不同的URL来表示。Angular路由的核心工作流程图 首先,当用户在浏览器上输入URL后,Angular将获取该URL并将其解析生成一个UrlTree实例其次,在路由配置中寻找并激活与UrlTree实例匹配的配置项再次,为配置项中指定的组件创建实例最后,将该组件渲染于路由组件的模板中<router-outlet>指令所在的位置基本用法Angular路由最为基本的用法是将一个URL所对应的组件在页面中显示出来。要做到这一点,有三个必不可少的步骤,分别是创建根路由模块、定义路由配置、添加<router-outlet>指令标签。 创建根路由模块根路由模块包含了路由所需要的各项服务,是路由工作流程得以正常执行的基础。 下面的代码以路由配置rootRouterConfig为参数,通过调用RouterModule.forRoot()方法来创建根路由模块,并将其导入到应用的根模块AppModule中。 app-routing.module.ts const rootRouterConfig: Routes = [ { path: 'add', component: AddComponent, }, { path: 'list', component: ListComponent, }, { path: '', redirectTo: 'add', pathMatch: 'full', }, { path: '**', redirectTo: 'add', pathMatch: 'full', }];@NgModule({ imports: [RouterModule.forRoot(rootRouterConfig)], exports: [RouterModule]})export class AppRoutingModule { }path 不能以斜杠 / 开头** 通配符路由,不满足以上路径时,选择此路由路由策略HashLocationStrategyhttp://localhost:4200/#/addHashLocationStrategy是Angular路由最为常见的策略,其原理是利用了浏览器在处理hash部分的特性 浏览器向服务器发送请求时不会带上hash部分的内容,更换URL的hash部分不会向服务器重新发送请求,这使得在进行跳转的时候不会引发页面的刷新和应用的重新加载使用该策略,只需要在注入路由服务时使用useHash属性进行显示指定即可 app-routing.module.ts @NgModule({ imports: [RouterModule.forRoot(rootRouterConfig, { useHash: true })], exports: [RouterModule]})HashLocationStrategy路由跳转Web应用中的页面跳转,指的是应用响应某个事件,从一个页面跳转到另一个页面的行为。对于Angular构建的单页面而言,页面跳转实质上就是从一个配置项跳转到另一个配置项的行为。 指令跳转指令跳转通过使用RouterLink指令来完成,该指令接收一个链接参数数组,Angular将根据该数组生成UrlTree实例进行跳转 <div [routerLink]="['/add']" routerLinkActive="add" >add</div><div [routerLink]="['/list']" routerLinkActive="list" >list</div>第一个参数名可以使用 /、./ 或 ../ 前缀 ...

July 26, 2019 · 1 min · jiezi

如何解决npm-run-build后页面空白

欢迎关注前端小讴的github,阅读更多原创技术文章问题一:assetsPublicPath配置错误解决办法:打开config/index.js文件 build:{ // assetsPublicPath: '/' assetsPublicPath: './'} 问题二:路由history模式配置有误解决办法:关闭路由historym模式 export default new Router({ // mode: 'history', // require service support scrollBehavior: () => ({ y: 0 }), routes: constantRouterMap})

July 10, 2019 · 1 min · jiezi

koa-rapid-router超越koa-router性能的100多倍

对比如果使用nodejs来搭建Service服务,那么我们首选express或者koa,而fastify告诉我们一个数据:FrameworkVersionRouter?Requests/sechapi18.1.0✓29,998Express4.16.4✓38,510Restify8.0.0✓39,331Koa2.7.0✗50,933Fastify2.0.0✓76,835- http.Server10.15.2✗71,768从数据中可以看出,Koa的性能远大于express。当然,它的测试基于简单的单路由测试。不过由此我们可以看到fastify的性能远大于Koa。相信使用过fastify的小伙伴都会对它的性能速度感到惊讶。其实原理很简单,就是请求的URL快速匹配Callback。如何做到,理论上也很简单,就是找寻它的最短路径来匹配。所以一般能够快速匹配的,都是通过空间换时间的方式来达到效果。这里,我还想告诉大家一点,fastify并不是最快的。主角今天的主角就是koa-rapid-router。为什么我们会以KOA打头呢?因为这篇文章目的其实是与koa-router的比较,而不是fastify。而此路由架构,也是为了在使用KOA的时候能够接近fastify的性能(经过测试,没有超过fastify,KOA本身的性能也有问题)。接下来,我们会抛出一系列的测试数据来告诉大家Koa-router的性能是何其糟糕。我们分别使用这样的原则来测试向每个架构注入10000个静态路由,测试最末尾的那个。使用相同的测试命令 autocannon -c 100 -d 40 -p 10 <url>对比静态路由和动态路由性能上的差距测试代码全部在这里静态路由对比我们写入如下的代码for (let i = 0; i < 10000; i++) { router.get(’/uuid/’ + (i + 1), async (ctx) => ctx.body = ‘ok’); vrouter.get(’/uuid/’ + (i + 1), (res) => res.end(‘ok’)); route_2.get(’/interface/api/uuid/’ + (i + 1), async (ctx) => ctx.body = ‘ok’); fastify.get(’/interface/api/uuid/’ + (i + 1), (request, reply) => reply.send(‘ok’));}接着进行测试 npm run test,得到数据:Preview:ResultscommandarchitectureLatencyReq/SecBytes/Sectest:koakoa + koa-router245.07 ms394.2556 kBtest:fastfastify1.96 ms493247 MBtest:rapidkoa + koa-rapid-router2.17 ms44828.86.37 MBtest:httphttp + koa-rapid-router1.64 ms58911.25.95 MB从数据上得出结论,koa-router在有10000个路由的时候,它的性能超级低下,只能达到平均的394.25,意思就是每秒只能处理394.25个请求,多来就不行。而koa + koa-rapid-router则处理到了44828.8个。同样是使用KOA模型,差距很明显。我做了分析,主要是koa-router内部循环比较多导致的。在10000个请求循环过程中,效率非常低下。而我们如何做到达到44828.8的性能,主要我们在内存中维护了一份静态路由列表,能让程序以最快的速度找到我们需要的callback。对比fastify,可以看出,KOA本身性能的问题很大。大家一定会问,对比静态路由Koa-router肯定没有优势,那么我们来对比动态路由。动态路由对比我们写入如下代码router.get(’/zzz/{a:number}’, async (ctx) => ctx.body = ‘ok’);vrouter.get(’/zzz/{a:number}’, (res) => res.end(‘ok’));route_2.get(’/interface/api/zzz/:a(\d+)’, async (ctx) => ctx.body = ‘ok’);fastify.get(’/interface/api/zzz/:a’, (request, reply) => reply.send(‘ok’));我们将这段代码加入到10000个静态路由代码的后面,修正测试的路径,我们得到如下数据:ResultscommandarchitectureLatencyReq/SecBytes/Sectest:koakoa + koa-router220.29 ms441.7562.7 kBtest:fastfastify1.9 ms50988.657.24 MBtest:rapidkoa + koa-rapid-router2.32 ms41961.65.96 MBtest:httphttp + koa-rapid-router1.82 ms53160.85.37 MB动态路由的对比从一定程度上可以看出koa-router的糟糕之处,不论是静态路由还是动态路由,它都基本稳定在400左右的qps。而koa + koa-rapid-router稍有下降,fastify一如既往的稳定。但是从http + koa-rapid-router模型上看,rapid完全超越fastify。koa + koa-rapid-router与koa + koa-router对比,性能大概是100倍的样子。如果我们可以这样认定,如果我们需要高并发,但是还是使用koa的生态的话,koa + koa-rapid-router是最佳选择。如果我们完全追求性能,不考虑生态的话,那么fastify首选。有人会问,那么为什么http + koa-rapid-router不使用,它可是比fastify更快的路由?那是因为,http + koa-rapid-router需要单独建立生态,暂时无法做到大规模使用,也许到最后,我们可以用上新的基于koa-rapid-router的企业级服务架构。这也是我正在思考的。结尾我们所造的轮子的性能是不可能超越http模块的性能,我们只能无限接近它。这就像光速的道理一样,只能接近,无法等于。高性能架构主要还是在于理念模型,跟数学息息相关。项目开源在 https://github.com/cevio/koa-rapid-router 有兴趣的小伙伴关注下,谢谢。 ...

March 16, 2019 · 1 min · jiezi

为了学习理解依赖注入和路由,自己撸了一个PHP框架

如何提高自己编写代码的能力呢?作为web开发者,我们通常都是基于面向对象OOP来开发的,所以面向对象的设计能力或者说设计模式的运用能力尤为重要,当然还有开发语言本身特性和基础的灵活运用。我们可以去阅读一些优秀的开源项目,理解里面的代码设计,去学习和造轮子来提高自己。我比较关注web framework中的路由、HTTP、依赖注入容器这几部分,路由和http处理是web框架必不可少的,整个框架的服务对象依赖解析也是很重要的,有了依赖注入容器可以实现类很好的解耦。Dependency Injection Container先来说下什么是依赖注入,依赖注入是一种允许我们从硬编码的依赖中解耦出来,从而在运行时或者编译时能够修改的软件设计模式(来自维基百科 Wikipedia)。依赖注入通过构造注入,函数调用或者属性的设置来提供组件的依赖关系。下面的代码中有一个 Database 的类,它需要一个适配器来与数据库交互。我们在构造函数里实例化了适配器,从而产生了耦合。这会使测试变得很困难,而且 Database 类和适配器耦合的很紧密。<?phpnamespace Database;class Database{ protected $adapter; public function __construct() { $this->adapter = new MySqlAdapter; }}class MysqlAdapter {}这段代码可以用依赖注入重构,从而解耦<?phpnamespace Database;class Database{ protected $adapter; public function __construct(MySqlAdapter $adapter) { $this->adapter = $adapter; }}class MysqlAdapter {}现在我们通过外界给予 Database 类的依赖,而不是让它自己产生依赖的对象。我们甚至能用可以接受依赖对象参数的成员函数来设置,或者如果 $adapter 属性本身是 public的,我们可以直接给它赋值。根据依赖注入的概念,我们的框架实现了这些特性。Dependency injection Container基于PSR-11规范实现,包括3种注入实现方式:构造方法注入(Constructor Injection)、setter方法或属性注入(Setter Injection)、匿名回调函数注入,代码如下:1.构造方法注入(Constructor Injection)<?php declare(strict_types=1);namespace Examples;use Eagle\DI\Container;class Foo{ /** * @var \Examples\Bar / public $bar; /* * Foo constructor. * @param \Examples\Bar $bar / public function __construct(Bar $bar) { $this->bar = $bar; }}/class Bar {}/class Bar { public $baz; public function __construct(Baz $baz) { $this->baz = $baz; }}class Baz {}$container = new Container;$container->set(Foo::class)->addArguments(Bar::class);$container->set(Bar::class)->addArguments(Baz::class);$foo = $container->get(Foo::class);var_dump($foo, $foo->bar);var_dump($foo instanceof Foo); // truevar_dump($foo->bar instanceof Bar); // truevar_dump($foo->bar->baz instanceof Baz); // true2.方法注入<?phpdeclare(strict_types=1);namespace Examples;require ‘vendor/autoload.php’;use Eagle\DI\Container;class Controller{ public $model; public function __construct(Model $model) { $this->model = $model; }}class Model{ public $pdo; public function setPdo(\PDO $pdo) { $this->pdo = $pdo; }}$container = new Container;$container->set(Controller::class)->addArguments(Model::class);$container->set(Model::class)->addInvokeMethod(‘setPdo’, [\PDO::class]);$container->set(\PDO::class) ->addArguments([‘mysql:dbname=test;host=localhost’, ‘root’, ‘111111’]);$controller = $container->get(Controller::class);var_dump($controller instanceof Controller); // truevar_dump($controller->model instanceof Model); // truevar_dump($controller->model->pdo instanceof \PDO); // true3.匿名回调函数注入<?phpdeclare(strict_types=1);namespace Examples;require ‘vendor/autoload.php’;use Eagle\DI\Container;class Controller{ public $model; public function __construct(Model $model) { $this->model = $model; }}class Model{ public $pdo; public function setPdo(\PDO $pdo) { $this->pdo = $pdo; }}$container = new Container;$container->set(Controller::class, function () { $pdo = new \PDO(‘mysql:dbname=test;host=localhost’, ‘root’, ‘111111’); $model = new Model; $model->setPdo($pdo); return new Controller($model);});$controller = $container->get(Controller::class);var_dump($controller instanceof Controller); // truevar_dump($controller->model instanceof Model); // truevar_dump($controller->model->pdo instanceof \PDO); // true自动布线 (auto wiring)<?phpdeclare(strict_types=1);namespace AutoWiring;require ‘vendor/autoload.php’;use Eagle\DI\ContainerBuilder;class Foo{ /* * @var \AutoWiring\Bar / public $bar; /* * @var \AutoWiring\Baz / public $baz; /* * Construct. * * @param \AutoWiring\Bar $bar * @param \AutoWiring\Baz $baz / public function __construct(Bar $bar, Baz $baz) { $this->bar = $bar; $this->baz = $baz; }}class Bar{ /* * @var \AutoWiring\Bam / public $bam; /* * Construct. * * @param \AutoWiring\Bam $bam */ public function __construct(Bam $bam) { $this->bam = $bam; }}class Baz{ // ..}class Bam{ // ..}$container = new ContainerBuilder;$container = $container->build();$foo = $container->get(Foo::class);var_dump($foo instanceof Foo); // truevar_dump($foo->bar instanceof Bar); // truevar_dump($foo->baz instanceof Baz); // truevar_dump($foo->bar->bam instanceof Bam); // trueRoute再介绍下路由使用的例子,route可以使用symfony的http foundation组件来处理HTTP请求(http messages)。<?phprequire ‘vendor/autoload.php’;use Eagle\Route\Router;use Symfony\Component\HttpFoundation\Request;$router = new Router();$router->get(’/articles’, function () { return ‘This is articles list’;});$router->get(’/articles/{id:\d+}’, function ($id) { return ‘Article id: ’ . $id;});$router->get(’/articles/{id:\d+}[/{title}]’, function ($id, $title) { return ‘Article id: ’ . $id . ‘, title: ’ . $title;});/匹配处理路由组/$router->group(’/articles’, function () use ($router) { $router->get(’/list’, function() { return ‘This is articles list’; }); $router->get(’/detail’, function ($id, $title) { return ‘Article detail id: ’ . $id . ‘, title: ’ . $title; });});$request = new Request();$routeHandler = $router->getRouteHandler();$response = $routeHandler->handle($request);echo $response;其它的ORM、cache、filesystem、session、validation等组件可以使用composer来由用户自由扩展。项目地址 https://github.com/parvinShi/… ...

March 11, 2019 · 2 min · jiezi

让我们来重新设计一下 koa-router

前言koa-router 是目前用的比较多的 Koa 的路由中间件之一,前段时间由于作者没有精力继续维护而将其公开售卖。我们有些项目也用到了这个库,但是目前很多我们想要的特性都没有,比如生成接口文档。本身这个库代码实现还比较简单,因此综合考虑打算重写一个。项目地址:https://github.com/d-band/koa…特性:支持几乎所有的 koa-router 特性支持 params 校验params 支持从 path, header, query, cookie 中获取支持 body parser支持 request body 校验支持参数类型自动转换支持自动生成 OpenAPI简单例子:index.jsimport Koa from ‘koa’;import Mapper from ‘koa-mapper’;import * as service from ‘./service’;const Mapper = new Mapper();mapper.get(’/users/:id/projects’, { params: { id: { type: ’number’ }, status: { type: ‘array<string>’, in: ‘query’ }, token: { type: ‘string’, in: ‘header’ } }}, service.getProjects);mapper.post(’/users/:id/projects’, { params: { id: { type: ’number’ } }, body: ‘Project’}, service.addProject);mapper.schema(‘Project’, { id: { type: ’number’, required: true }, name: { type: ‘string’, required: true }, status: { type: ‘array<Status>’, required: true }});mapper.schema(‘Status’, { id: { type: ‘integer’ }, name: { type: ‘string’ }}, { required: [‘id’, ’name’]});app.use(mapper.routes());app.use(mapper.allowedMethods());app.listen(3000);// open http://localhost:3000/openapi.jsonservice.jsexport async function getProjects(ctx) { const { id, status, token } = ctx.params; await checkToken(id, token); ctx.body = await Project.findAll({ where: { userId: id, status: { $in: status } } });}export async function addProject(ctx) { const { body } = ctx.request; ctx.body = await Project.create({ …body, userId: id });}路由定义:mapper.get(path, [options], …middlewares);mapper.post(path, [options], …middlewares);mapper.put(path, [options], …middlewares);mapper.del(path, [options], …middlewares);…options 为可选参数,包含:name: 路由名称params: 请求参数定义body: 请求 Body 定义其他 OpenAPI 中 Operation Object 的参数options.params 为请求参数定义,如:params = { id: { type: ’number’ }, name: { type: ‘string’, in: ‘query’ }, user: { type: ‘User’, in: ‘query’ }}type: 参数类型,包含基本类型(number、string、integer、date、time、datetime),数组类型(array<string>),自定义类型(如 User),自定义数组类型(array<User>),多个类型(number|string)in: 参数来源,包含 path,header,query,cookie其他 OpenAPI 中 Parameter Object 的参数自定义类型mapper.define(schemaName, properties, options);// ormapper.schema(schemaName, properties, options);支持类型组合,如:mapper.schema(‘Status’, { id: { type: ‘integer’ }, name: { type: ‘string’ }}, { required: [‘id’]});mapper.schema(‘Project’, { id: { type: ’number’, required: true }, name: { type: ‘string’, required: true }, status: { type: ‘array<Status>’, required: true }});支持继承,如:mapper.schema(‘Model’, { id: { type: ’number’ }, createdAt: { type: ‘datetime’ }, updatedAt: { type: ‘datetime’ }});mapper.schema(‘User: Model’, { name: { type: ‘string’ }});Body Parsermapper.post(’/users’, { body: ‘User’}, (ctx) => { const { id, name } = ctx.request.body;});支持文件上传,如:mapper.post(’/uploadImage’, { bodyparser: { multipart: true }, body: { user: { type: ’number’ }, image: { type: ‘file’ } }}, (ctx) => { const { user, image } = ctx.request.body;});结尾目前 koa-mapper 刚发布,测试覆盖率达到 100%,有哪些有兴趣的小伙伴欢迎一起维护。 ...

March 8, 2019 · 2 min · jiezi

history对象详解及单页面路由实现

history对象保存着用户的上网记录,从浏览器窗口打开的那一刻算起。出于安全的考虑,开发人员无法得知用户浏览过的URL。不过,借由用户访问过的页面列表,同样可以在不知道实际URL的情况下实现后退与前进一、history对象的方法go(Stirng|number)使用go方法可以在用户的历史记录中任意跳转,可以向后也可以向前。这个方法接受一个参数,表示向后或向前跳转的页面数的一个整数值。负数表示向后跳转(类似浏览器的后退按钮),正数表示向前跳转(类似浏览器的前进按钮)。来看下例子//后退一页history.go(-1)//前进一页history.go(1)//前进两页history.go(2)也可以给go()方法船体一个字符串参数,此时浏览器会跳转到历史记录中包含改字符串的第一个位置,可能后退也可能前进,具体要看哪一个位置最近。如果历史记录中不包含该字符串,则什么都不做。例如://跳转到最近的wrox.com页面history.go(“wrox.com”)//跳转到最近的douban.cn页面history.go(“douban.cn”)back()和forward这两个方法可以来代替go(),模仿浏览器的后退和前进功能back()相当于 go(-1) 后退一个页面forward相当于go(1) 前进一个页面注:接下来几个方法是html5新增的方法二、html5中history新增的方法pushState(state,title,url)该方法的作用是 在历史记录中新增一条记录,改变浏览器地址栏的url,但是,不刷新页面。pushState对象接受三个参数,state:一个与添加的记录相关联的状态对象,主要用于popstate事件。该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填null。title:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。举个例子,假设当前网址是hello.com/1.html,使用puchState()方法在浏览记录中添加一个新纪录var stateObj={foo:‘bar’}history.pushState(starteObj,’’,‘2.html’)添加新纪录后,浏览器的地址栏立刻显示`hello.com/2.html,但不会跳转到2.html,也不会检查2.html是否存在,它只是成为浏览历史中的最新记录。总之,pushState()方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应,使用该方法后,就可以使用history.state属性读出状态对象var stateObj={foo:‘bar’}history.pushState(starteObj,’’,‘2.html’)history.state //=> {foo:“bar”}注意:如果pushState的URL参数设置了一个新的hash值,并不会触发hashchange事件。replaceState(state,title,url)replaceState方法的作用是替换当前的历史记录,其他的都与pushState()方法一模一样。假定当前网页是example.com/example.html。history.pushState({page: 1}, ’title 1’, ‘?page=1’)// URL 显示为 http://example.com/example.html?page=1history.pushState({page: 2}, ’title 2’, ‘?page=2’);// URL 显示为 http://example.com/example.html?page=2history.replaceState({page: 3}, ’title 3’, ‘?page=3’);// URL 显示为 http://example.com/example.html?page=3history.back()// URL 显示为 http://example.com/example.html?page=1history.back()// URL 显示为 http://example.com/example.htmlhistory.go(2)// URL 显示为 http://example.com/example.html?page=3三、popstate事件popstate事件是window对象上的事件,配合pushState()和replaceState()方法使用。当同一个文档(可以理解为同一个网页,不能跳转,跳转了就不是同一个网页了)的浏览历史出现变化时,就会触发popstate事件。上面我们说过,调用pushState()或者replaceState()方法都会改变当前的历史记录,仅仅调用pushState()方法或replaceState()方法 ,并不会触发该事件,另外一个条件是用户必须点击浏览器的倒退按钮或者前进按钮,或者使用js调用history.back()或者history.forward()等方法。所以,记住popstate事件触发的条件1. 处在同一个文档(同一个html页面)2. 文档的浏览历史(即history对象)发生改变只要符合这两个条件,popstate事件就会触发具体例子//index.html<head> <script> window.onpopstate=function(){ alert(’location ‘+document.location+’,state ‘+JSON.stringify(event.state)) } </script></head><body> <!–第二步 –> <button onclick=“window.history.back()">后退</button> <button onclick=“window.history.forward()">前进</button> <!–第一步 –> <button onclick=“window.history.pushState(null,’’,‘1.html’)">pushState</button> </body>先点击pushState按钮,在点击后退按钮,就会触发popstate事件再来一个例子//index.html<head> <script> window.onpopstate=function(){ alert(’location ‘+document.location+’,state ‘+JSON.stringify(event.state)) } </script></head><body> <a href="#one”>#one</a> </body>直接点击a标签,也可以触发popstate事件四、浏览器兼容性图片来自mdn传送门五、单页面路由原理前端路由的本质是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新。目前单页面使用的路由就只有两种实现方式hash模式history模式hash模式www.test.com/##/就是Hash URL,当##后面的哈希值发生变化时,不会向服务器请求数据,可以通过hashchange事件来监听到URL的变化,从而进行跳转页面网上偷来的一张图:history模式history模式相比hash模式更美观,需要用到Html5新增的几个api实现,原理如下:继续偷图:五、实例,使用history api实现简单的单页面路由在介绍实例前先介绍下location对象,location对象提供了与当前窗口中加载的文档有关的信息。它包含以下属性:属性名例子说明hostwww.hello.com:8080返回服务器名称和端口号(如果有的话)hostnamewww.hello.com返回服务器名称,不带端口号hrefhttp://www.hello.com返回当前加载页面的完整urlpathname/user/ming返回url中的目录hash#content返回url中的hash,如果没有返回空字符串search?q=javascript返回Url的查询字符串,这个字符串以问号开头我们在下方的示例中需要用到pathname属性拿到访问的路径一个简单的history模式单页面路由实现如下://1. 路由规则const routes={ ‘/user’:user, //user是引入的视图 import user from ‘./view/user’ ‘/about’:about}//2. 路由控制类class Router { start() { // 点击浏览器后退/前进按钮时会触发window.onpopstate事件, 我们在这时切换到相应页面 // https://developer.mozilla.org/en-US/docs/Web/Events/popstate window.addEventListener(‘popstate’, () => { this.load(location.pathname) }) // 打开页面时加载当前页面 在单页面入口文件中要调用start方法 this.load(location.pathname) } // 前往path, 变更地址栏URL, 并加载相应页面 go(path) { // 变更地址栏URL history.pushState({}, ‘’, path) // 加载页面 this.load(path) } // 加载path路径的页面 load(path) { // 首页 if (path === ‘/’) path = ‘/foo’ // 创建页面实例 const view = new routespath // 调用页面方法, 把页面加载到document.body中 view.mount(document.body) }}Router类的作用是控制页面根据当前Url切换start()作用1: 监听onpopstate事件,在浏览器前进或后退时加载相应的页面作用2: 打开页面时加载当前页面,需要在单页面的入口文件引入,并执行go(path)跳转到path对应的页面load(path)加载path路径的页面参考链接高程三 p215(history对象) p207(location对象)JavaScript标准参考教程-阮一峰interviewMap ...

February 20, 2019 · 1 min · jiezi

前端路由跳转基本原理

目前前端三杰 Angular、React、Vue 都推介单页面应用 SPA 开发模式,在路由切换时替换 DOM Tree 中最小修改的部分 DOM,来减少原先因为多页应用的页面跳转带来的巨量性能损耗。它们都有自己的典型路由解决方案,@angular/router、react-router、vue-router。一般来说,这些路由插件总是提供两种不同方式的路由方式: Hash 和 History,有时也会提供非浏览器环境下的路由方式 Abstract,在 vue-router 中是使用了外观模式将几种不同的路由方式提供了一个一致的高层接口,让我们可以更解耦的在不同路由方式中切换。值得一提的是,Hash 和 History 除了外观上的不同之外,还一个区别是:Hash 方式的状态保存需要另行传递,而 HTML5 History 原生提供了自定义状态传递的能力,我们可以直接利用其来传递信息。下面我们具体看看这两种方式都有哪些特点,并提供简单的实现,比如基本的功能,更复杂的功能比如懒加载、动态路径匹配、嵌套路由、路由别名等等,可以关注一下后面的 vue-router 源码解读方面的博客。1. Hash1.1 相关 ApiHash 方法是在路由中带有一个 #,主要原理是通过监听 # 后的 URL 路径标识符的更改而触发的浏览器 hashchange 事件,然后通过获取 location.hash 得到当前的路径标识符,再进行一些路由跳转的操作,参见 MDNlocation.href:返回完整的 URLlocation.hash:返回 URL 的锚部分location.pathname:返回 URL 路径名hashchange 事件:当 location.hash 发生改变时,将触发这个事件比如访问一个路径 http://sherlocked93.club/base/#/page1,那么上面几个值分别为:# http://sherlocked93.club/base/#/page1{ “href”: “http://sherlocked93.club/base/#/page1", “pathname”: “/base/”, “hash”: “#/page1”}注意:因为 Hash 方法是利用了相当于页面锚点的功能,所以与原来的通过锚点定位来进行页面滚动定位的方式冲突,导致定位到错误的路由路径,所以需要采用别的办法,之前在写 progress-catalog 这个插件碰到了这个情况。1.2 实例这里简单做一个实现,原理是把目标路由和对应的回调记录下来,点击跳转触发 hashchange 的时候获取当前路径并执行对应回调,效果:class RouterClass { constructor() { this.routes = {} // 记录路径标识符对应的cb this.currentUrl = ’’ // 记录hash只为方便执行cb window.addEventListener(’load’, () => this.render()) window.addEventListener(‘hashchange’, () => this.render()) } /* 初始化 / static init() { window.Router = new RouterClass() } / 注册路由和回调 / route(path, cb) { this.routes[path] = cb || function() {} } / 记录当前hash,执行cb / render() { this.currentUrl = location.hash.slice(1) || ‘/’ this.routesthis.currentUrl }}具体实现参照 CodePen如果希望使用脚本来控制 Hash 路由的后退,可以将经历的路由记录下来,路由后退跳转的实现是对 location.hash 进行赋值。但是这样会引发重新引发 hashchange 事件,第二次进入 render 。所以我们需要增加一个标志位,来标明进入 render 方法是因为回退进入的还是用户跳转class RouterClass { constructor() { this.isBack = false this.routes = {} // 记录路径标识符对应的cb this.currentUrl = ’’ // 记录hash只为方便执行cb this.historyStack = [] // hash栈 window.addEventListener(’load’, () => this.render()) window.addEventListener(‘hashchange’, () => this.render()) } / 初始化 / static init() { window.Router = new RouterClass() } / 记录path对应cb / route(path, cb) { this.routes[path] = cb || function() {} } / 入栈当前hash,执行cb / render() { if (this.isBack) { // 如果是由backoff进入,则置false之后return this.isBack = false // 其他操作在backoff方法中已经做了 return } this.currentUrl = location.hash.slice(1) || ‘/’ this.historyStack.push(this.currentUrl) this.routesthis.currentUrl } / 路由后退 / back() { this.isBack = true this.historyStack.pop() // 移除当前hash,回退到上一个 const { length } = this.historyStack if (!length) return let prev = this.historyStack[length - 1] // 拿到要回退到的目标hash location.hash = #${ prev } this.currentUrl = prev this.routesprev // 执行对应cb }}代码实现参考 CodePen2. HTML5 History Api2.1 相关 ApiHTML5 提供了一些路由操作的 Api,关于使用可以参看 <Manipulating the browser history> 这篇 MDN 上的文章,这里就列举一下常用 Api 和他们的作用,具体参数什么的就不介绍了,MDN 上都有history.go(n):路由跳转,比如n为 2 是往前移动2个页面,n为 -2 是向后移动2个页面,n为0是刷新页面history.back():路由后退,相当于 history.go(-1)history.forward():路由前进,相当于 history.go(1)history.pushState():添加一条路由历史记录,如果设置跨域网址则报错history.replaceState():替换当前页在路由历史记录的信息popstate 事件:当活动的历史记录发生变化,就会触发 popstate 事件,在点击浏览器的前进后退按钮或者调用上面前三个方法的时候也会触发,参见 MDN2.2 实例将之前的例子改造一下,在需要路由跳转的地方使用 history.pushState 来入栈并记录 cb,前进后退的时候监听 popstate 事件拿到之前传给 pushState 的参数并执行对应 cb,因为借用了浏览器自己的 Api,因此代码看起来整洁不少class RouterClass { constructor(path) { this.routes = {} // 记录路径标识符对应的cb history.replaceState({ path }, null, path) // 进入状态 this.routes[path] && this.routespath window.addEventListener(‘popstate’, e => { const path = e.state && e.state.path this.routes[path] && this.routespath }) } / 初始化 / static init() { window.Router = new RouterClass(location.pathname) } / 注册路由和回调 / route(path, cb) { this.routes[path] = cb || function() {} } / 跳转路由,并触发路由对应回调 */ go(path) { history.pushState({ path }, null, path) this.routes[path] && this.routespath }}Hash 模式是使用 URL 的 Hash 来模拟一个完整的 URL,因此当 URL 改变的时候页面并不会重载。History 模式则会直接改变 URL,所以在路由跳转的时候会丢失一些地址信息,在刷新或直接访问路由地址的时候会匹配不到静态资源。因此需要在服务器上配置一些信息,让服务器增加一个覆盖所有情况的候选资源,比如跳转 index.html 什么的,一般来说是你的 app 依赖的页面,事实上 vue-router 等库也是这么推介的,还提供了常见的服务器配置。代码实现参考 CodePen网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出~参考:history | MDNhashchange | MDNManipulating the browser history | MDN前端路由的基本原理 - 大史不说话History 对象 – JavaScript 标准参考教程 ...

January 31, 2019 · 2 min · jiezi

React Router4.0

React Router v4是对React Router的一次彻底重构,采用动态路由,遵循React中一切皆组件的思想,每一个Route(路由)都是一个普通的React组件。BrowserRouter创建的URL形式如下:http://react.com/some/pathHashRouter创建的URL形式如下:http://react.com/#/some/path使用BrowserRouter时,一般还需要对服务器进行配置,让服务器能正确处理所有可能的URL.例如,当浏览器发送 http://react.com/some/path 和 http://react.com/some/path2两…,服务器能够返回正确的HTML页面(也就是单页面应用中唯一的html页面).使用HashRouter则不存在这个问题,因为hash部分的内容会被服务器自动忽略,真正有效的是hash前面的部分,而对于单页面应用来说,这部分内容是固定的。路由的配置1.path(1)当使用BrowserRouter时,path用来描述这个Route匹配的URL的pathname(2)当使用HashRouter时,path用来描述这个Route匹配的URL的hash.2.match(1)params: Route的path可以包含参数,例如:<Route path=’/foo/:id’> 包含一个参数id。params就是用于从匹配的URL中解析出path中的参数,例如:当URL=“http://react.com/foo/1时,params={id:1}。(2)isExact: 是一个布尔值,当URL完全匹配时,值为true;当URL部分匹配时,值为false.例如:当path="/foo”,URL=“http://react.com/foo"时,是完全匹配;当URL=“http://react.com/foo/1时,是部分匹配。(3)path: Route的path属性,构建嵌套路由时会使用到。(4)url: URL的匹配部分。3.Route渲染组件的方式(1)componentcomponent的值是一个组件,当URL和Route匹配时,component属性定义的组件就会被渲染。<Route path=’/foo’ component={FOO}>当URL=“http://react.com/foo"时,Foo组件会被渲染。(2)renderrender的值是一个函数,这个函数返回一个React元素,这个函数返回一个React元素。这种方式可以很方便的为待渲染的组件传递额外的属性。例如:<Route path=’/foo’ render={(props)=>(<Foo {…props} data={extraProps} />)}>Foo组件接收了一个额外的data属性。(3)children children的值也是一个函数,函数返回要渲染的React元素。与之前两种方式不同的是,无论是否匹配成功,children返回的组件都将会被渲染。但是当匹配不成功时,match属性为null。例如:<Route path=’/foo’ children={(props)=>(<div className={props.match?‘active’:’’}> <Foo /></div>)} />如果Route匹配当前URL,待渲染元素的根节点div的class将被设置成active。4.Switch和exact当URL和多个Route匹配时,这些Route都会执行渲染操作。如果只想让第一个匹配的Router渲染,那么可以把这些Route包到一个Switch组件中。如果想让URL和Route完全匹配时,Route才渲染,那么可以使用Route的exact属性。Switch和exact常常联合使用,用于应用首页的导航。例如:<Router><Switch> <Route exact path=’/’ component={Home} /> <Route path=’/posts’ component={Posts} /> <Route path=’/:user’ component={User} /></Switch></Router>如果不使用Switch,当URL的pathname为”/posts"时,<Route path=’/posts’/>和<Route path=’/:user’ />都会被匹配。如果不使用exact,”/” “/posts” “/user1"等几乎所有URL都会匹配第一个Route,又因为Switch的存在,后面的两个Route永远也不会被匹配。使用exact,保证只有当URL的pathname为”/“时,第一个Route才会被匹配。5.嵌套路由嵌套路由是指在Route渲染的组件内部定义新的Route.例如:const Posts = ({match}) => {return( <div> {/这里match.url等于/posts/} <Route path={${match.url}/:id} component={PostDetail} /> <Route exact path={match.url} component={PostList} /> </div>)}当URL的pathname为”/posts/react"时,PostDetail组件会被渲染;当URL的pathname为"/posts"时,PostList组件会被渲染。Route的嵌套使用让应用可以更加灵活的使用路由。6.链接Link是React Router提供的链接组件,一个Link组件定义了当点击该Link时,页面应该如何路由:例如:const Navigation = () => (<header> <nav> <ul> <li><Link to=’/’>Home</Link></li> <li><Link to=’/posts’>Posts</Link></li> </ul> </nav></header>)Link使用to属性声明要导航到URL地址。to可以是string或object类型,当to为object类型时,可以包含pathname、search、hash、state、四个属性,例如:<Link to={{pathname:’/posts’,search:’?sort=name’,hash:’#the-hash",state:{formHome:true}}}/>除了使用Link外,我们还可以使用history对象手动实现导航,history中最常用的方法是push(path,[state])和replace(path,[state]),push会向浏览器历史记录中新增一条记录,replace会用新纪录替换当前纪录,例如:history.push(’/posts’)history.replace(’/posts’)import React from “react”;import { BrowserRouter as Router, Route, Link } from “react-router-dom”;function BasicExample() { return (<Router> <div> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/about">About</Link> </li> <li> <Link to="/topics">Topics</Link> </li> </ul> <hr /> <Route exact path="/" component={Home} /> <Route path="/about" component={About} /> <Route path="/topics" component={Topics} /> </div></Router>);}function Home() { return (<div> <h2>Home</h2></div>);}function About() { return (<div> <h2>About</h2></div>);}function Topics({ match }) { return (<div> <h2>Topics</h2> <ul> <li> <Link to={${match.url}/rendering}>Rendering with React</Link> </li> <li> <Link to={${match.url}/components}>Components</Link> </li> <li> <Link to={${match.url}/props-v-state}>Props v. State</Link> </li> </ul> <Route path={${match.path}/:topicId} component={Topic} /> <Route exact path={match.path} render={() => <h3>Please select a topic.</h3>} /></div>);}function Topic({ match }) { return (<div> <h3>{match.params.topicId}</h3></div>);}export default BasicExample; ...

January 30, 2019 · 1 min · jiezi

原生 js 实现一个前端路由 router

效果图:项目地址:https://github.com/biaochenxuying/route效果体验地址:1. 滑动效果: https://biaochenxuying.github.io/route/index.html2. 淡入淡出效果: https://biaochenxuying.github.io/route/index2.html1. 需求因为我司的 H 5 的项目是用原生 js 写的,要用到路由,但是现在好用的路由都是和某些框架绑定在一起的,比如 vue-router ,framework7 的路由;但是又没必要为了一个路由功能而加入一套框架,现在自己写一个轻量级的路由。2. 实现原理现在前端的路由实现一般有两种,一种是 Hash 路由,另外一种是 History 路由。2.1 History 路由History 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录。属性History.length 是一个只读属性,返回当前 session 中的 history 个数,包含当前页面在内。举个例子,对于新开一个 tab 加载的页面当前属性返回值 1 。History.state 返回一个表示历史堆栈顶部的状态的值。这是一种可以不必等待 popstate 事件而查看状态而的方式。方法History.back()前往上一页, 用户可点击浏览器左上角的返回按钮模拟此方法. 等价于 history.go(-1).Note: 当浏览器会话历史记录处于第一页时调用此方法没有效果,而且也不会报错。History.forward()在浏览器历史记录里前往下一页,用户可点击浏览器左上角的前进按钮模拟此方法. 等价于 history.go(1).Note: 当浏览器历史栈处于最顶端时( 当前页面处于最后一页时 )调用此方法没有效果也不报错。History.go(n)通过当前页面的相对位置从浏览器历史记录( 会话记录 )加载页面。比如:参数为 -1的时候为上一页,参数为 1 的时候为下一页. 当整数参数超出界限时 ( 译者注:原文为 When integerDelta is out of bounds ),例如: 如果当前页为第一页,前面已经没有页面了,我传参的值为 -1,那么这个方法没有任何效果也不会报错。调用没有参数的 go() 方法或者不是整数的参数时也没有效果。( 这点与支持字符串作为 url 参数的 IE 有点不同)。history.pushState() 和 history.replaceState()这两个 API 都接收三个参数,分别是a. 状态对象(state object) — 一个JavaScript对象,与用 pushState() 方法创建的新历史记录条目关联。无论何时用户导航到新创建的状态,popstate 事件都会被触发,并且事件对象的state 属性都包含历史记录条目的状态对象的拷贝。b. 标题(title) — FireFox 浏览器目前会忽略该参数,虽然以后可能会用上。考虑到未来可能会对该方法进行修改,传一个空字符串会比较安全。或者,你也可以传入一个简短的标题,标明将要进入的状态。c. 地址(URL) — 新的历史记录条目的地址。浏览器不会在调用 pushState() 方法后加载该地址,但之后,可能会试图加载,例如用户重启浏览器。新的 URL 不一定是绝对路径;如果是相对路径,它将以当前 URL 为基准;传入的 URL 与当前 URL 应该是同源的,否则,pushState() 会抛出异常。该参数是可选的;不指定的话则为文档当前 URL。相同之处: 是两个 API 都会操作浏览器的历史记录,而不会引起页面的刷新。不同之处在于: pushState 会增加一条新的历史记录,而 replaceState 则会替换当前的历史记录。例子:本来的路由 http://biaochenxuying.cn/执行:window.history.pushState(null, null, “http://biaochenxuying.cn/home");路由变成了: http://biaochenxuying.cn/hot详情介绍请看:MDN2.2 Hash 路由我们经常在 url 中看到 #,这个 # 有两种情况,一个是我们所谓的锚点,比如典型的回到顶部按钮原理、Github 上各个标题之间的跳转等,但是路由里的 # 不叫锚点,我们称之为 hash。现在的前端主流框架的路由实现方式都会采用 Hash 路由,本项目采用的也是。当 hash 值发生改变的时候,我们可以通过 hashchange 事件监听到,从而在回调函数里面触发某些方法。3. 代码实现3.1 简单版 - 单页面路由先看个简单版的 原生 js 模拟 Vue 路由切换。原理监听 hashchange ,hash 改变的时候,根据当前的 hash 匹配相应的 html 内容,然后用 innerHTML 把 html 内容放进 router-view 里面。这个代码是网上的:<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no”> <meta name=“author” content=”"> <title>原生模拟 Vue 路由切换</title> <style type=“text/css”> .router_box, #router-view { max-width: 1000px; margin: 50px auto; padding: 0 20px; } .router_box>a { padding: 0 10px; color: #42b983; } </style></head><body> <div class=“router_box”> <a href="/home" class=“router”>主页</a> <a href="/news" class=“router”>新闻</a> <a href="/team" class=“router”>团队</a> <a href="/about" class=“router”>关于</a> </div> <div id=“router-view”></div> <script type=“text/javascript”> function Vue(parameters) { let vue = {}; vue.routes = parameters.routes || []; vue.init = function() { document.querySelectorAll(".router").forEach((item, index) => { item.addEventListener(“click”, function(e) { let event = e || window.event; event.preventDefault(); window.location.hash = this.getAttribute(“href”); }, false); }); window.addEventListener(“hashchange”, () => { vue.routerChange(); }); vue.routerChange(); }; vue.routerChange = () => { let nowHash = window.location.hash; let index = vue.routes.findIndex((item, index) => { return nowHash == (’#’ + item.path); }); if (index >= 0) { document.querySelector("#router-view").innerHTML = vue.routes[index].component; } else { let defaultIndex = vue.routes.findIndex((item, index) => { return item.path == ‘’; }); if (defaultIndex >= 0) { window.location.hash = vue.routes[defaultIndex].redirect; } } }; vue.init(); } new Vue({ routes: [{ path: ‘/home’, component: “<h1>主页</h1><a href=‘https://github.com/biaochenxuying’>https://github.com/biaochenxuying</a>” }, { path: ‘/news’, component: “<h1>新闻</h1><a href=‘http://biaochenxuying.cn/main.html’>http://biaochenxuying.cn/main.html</a>” }, { path: ‘/team’, component: ‘<h1>团队</h1><h4>全栈修炼</h4>’ }, { path: ‘/about’, component: ‘<h1>关于</h1><h4>关注公众号:BiaoChenXuYing</h4><p>分享 WEB 全栈开发等相关的技术文章,热点资源,全栈程序员的成长之路。</p>’ }, { path: ‘’, redirect: ‘/home’ }] }); </script></body></html>3.2 复杂版 - 内联页面版,带缓存功能首先前端用 js 实现路由的缓存功能是很难的,但像 vue-router 那种还好,因为有 vue 框架和虚拟 dom 的技术,可以保存当前页面的数据。要做缓存功能,首先要知道浏览器的 前进、刷新、回退 这三个操作。但是浏览器中主要有这几个限制:没有提供监听前进后退的事件不允许开发者读取浏览记录用户可以手动输入地址,或使用浏览器提供的前进后退来改变 url所以要自定义路由,解决方案是自己维护一份路由历史的记录,存在一个数组里面,从而区分 前进、刷新、回退。url 存在于浏览记录中即为后退,后退时,把当前路由后面的浏览记录删除。url 不存在于浏览记录中即为前进,前进时,往数组里面 push 当前的路由。url 在浏览记录的末端即为刷新,刷新时,不对路由数组做任何操作。另外,应用的路由路径中可能允许相同的路由出现多次(例如 A -> B -> A),所以给每个路由添加一个 key 值来区分相同路由的不同实例。这个浏览记录需要存储在 sessionStorage 中,这样用户刷新后浏览记录也可以恢复。3.2.1 route.js3.2.1.1 跳转方法 linkTo像 vue-router 那样,提供了一个 router-link 组件来导航,而我这个框架也提供了一个 linkTo 的方法。 // 生成不同的 key function genKey() { var t = ‘xxxxxxxx’ return t.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0 var v = c === ‘x’ ? r : (r & 0x3 | 0x8) return v.toString(16) }) } // 初始化跳转方法 window.linkTo = function(path) { if (path.indexOf("?") !== -1) { window.location.hash = path + ‘&key=’ + genKey() } else { window.location.hash = path + ‘?key=’ + genKey() } }用法://1. 直接用 a 标签<a href=’#/list’ >列表1</a>//2. 标签加 js 调用方法<div onclick=‘linkTo("#/home")’>首页</div>// 3. js 调用触发linkTo("#/list")3.2.1.2 构造函数 Router定义好要用到的变量function Router() { this.routes = {}; //保存注册的所有路由 this.beforeFun = null; //切换前 this.afterFun = null; // 切换后 this.routerViewId = “#routerView”; // 路由挂载点 this.redirectRoute = null; // 路由重定向的 hash this.stackPages = true; // 多级页面缓存 this.routerMap = []; // 路由遍历 this.historyFlag = ’’ // 路由状态,前进,回退,刷新 this.history = []; // 路由历史 this.animationName = “slide” // 页面切换时的动画 }3.2.1.3 实现路由功能包括:初始化、注册路由、历史记录、切换页面、切换页面的动画、切换之前的钩子、切换之后的钩子、滚动位置的处理,缓存。Router.prototype = { init: function(config) { var self = this; this.routerMap = config ? config.routes : this.routerMap this.routerViewId = config ? config.routerViewId : this.routerViewId this.stackPages = config ? config.stackPages : this.stackPages var name = document.querySelector(’#routerView’).getAttribute(‘data-animationName’) if (name) { this.animationName = name } this.animationName = config ? config.animationName : this.animationName if (!this.routerMap.length) { var selector = this.routerViewId + " .page" var pages = document.querySelectorAll(selector) for (var i = 0; i < pages.length; i++) { var page = pages[i]; var hash = page.getAttribute(‘data-hash’) var name = hash.substr(1) var item = { path: hash, name: name, callback: util.closure(name) } this.routerMap.push(item) } } this.map() // 初始化跳转方法 window.linkTo = function(path) { console.log(‘path :’, path) if (path.indexOf("?") !== -1) { window.location.hash = path + ‘&key=’ + util.genKey() } else { window.location.hash = path + ‘?key=’ + util.genKey() } } //页面首次加载 匹配路由 window.addEventListener(’load’, function(event) { // console.log(’load’, event); self.historyChange(event) }, false) //路由切换 window.addEventListener(‘hashchange’, function(event) { // console.log(‘hashchange’, event); self.historyChange(event) }, false) }, // 路由历史纪录变化 historyChange: function(event) { var currentHash = util.getParamsUrl(); var nameStr = “router-” + (this.routerViewId) + “-history” this.history = window.sessionStorage[nameStr] ? JSON.parse(window.sessionStorage[nameStr]) : [] var back = false, refresh = false, forward = false, index = 0, len = this.history.length; for (var i = 0; i < len; i++) { var h = this.history[i]; if (h.hash === currentHash.path && h.key === currentHash.query.key) { index = i if (i === len - 1) { refresh = true } else { back = true } break; } else { forward = true } } if (back) { this.historyFlag = ‘back’ this.history.length = index + 1 } else if (refresh) { this.historyFlag = ‘refresh’ } else { this.historyFlag = ‘forward’ var item = { key: currentHash.query.key, hash: currentHash.path, query: currentHash.query } this.history.push(item) } console.log(‘historyFlag :’, this.historyFlag) // console.log(‘history :’, this.history) if (!this.stackPages) { this.historyFlag = ‘forward’ } window.sessionStorage[nameStr] = JSON.stringify(this.history) this.urlChange() }, // 切换页面 changeView: function(currentHash) { var pages = document.getElementsByClassName(‘page’) var previousPage = document.getElementsByClassName(‘current’)[0] var currentPage = null var currHash = null for (var i = 0; i < pages.length; i++) { var page = pages[i]; var hash = page.getAttribute(‘data-hash’) page.setAttribute(‘class’, “page”) if (hash === currentHash.path) { currHash = hash currentPage = page } } var enterName = ’enter-’ + this.animationName var leaveName = ’leave-’ + this.animationName if (this.historyFlag === ‘back’) { util.addClass(currentPage, ‘current’) if (previousPage) { util.addClass(previousPage, leaveName) } setTimeout(function() { if (previousPage) { util.removeClass(previousPage, leaveName) } }, 250); } else if (this.historyFlag === ‘forward’ || this.historyFlag === ‘refresh’) { if (previousPage) { util.addClass(previousPage, “current”) } util.addClass(currentPage, enterName) setTimeout(function() { if (previousPage) { util.removeClass(previousPage, “current”) } util.removeClass(currentPage, enterName) util.addClass(currentPage, ‘current’) }, 350); // 前进和刷新都执行回调 与 初始滚动位置为 0 currentPage.scrollTop = 0 this.routes[currHash].callback ? this.routes[currHash].callback(currentHash) : null } this.afterFun ? this.afterFun(currentHash) : null }, //路由处理 urlChange: function() { var currentHash = util.getParamsUrl(); if (this.routes[currentHash.path]) { var self = this; if (this.beforeFun) { this.beforeFun({ to: { path: currentHash.path, query: currentHash.query }, next: function() { self.changeView(currentHash) } }) } else { this.changeView(currentHash) } } else { //不存在的地址,重定向到默认页面 location.hash = this.redirectRoute } }, //路由注册 map: function() { for (var i = 0; i < this.routerMap.length; i++) { var route = this.routerMap[i] if (route.name === “redirect”) { this.redirectRoute = route.path } else { this.redirectRoute = this.routerMap[0].path } var newPath = route.path var path = newPath.replace(/\s*/g, “”); //过滤空格 this.routes[path] = { callback: route.callback, //回调 } } }, //切换之前的钩子 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(‘路由切换后回调函数不正确’) } } }3.2.1.4 注册到 Router 到 window 全局 window.Router = Router; window.router = new Router();完整代码:https://github.com/biaochenxu…3.2.2 使用方法3.2.2.1 js 定义法callback 是切换页面后,执行的回调<script type=“text/javascript”> var config = { routerViewId: ‘routerView’, // 路由切换的挂载点 id stackPages: true, // 多级页面缓存 animationName: “slide”, // 切换页面时的动画 routes: [{ path: “/home”, name: “home”, callback: function(route) { console.log(‘home:’, route) var str = “<div><a class=‘back’ onclick=‘window.history.go(-1)’>返回</a></div> <h2>首页</h2> <input type=‘text’> <div><a href=‘javascript:void(0);’ onclick=‘linkTo("#/list")’>列表</a></div><div class=‘height’>内容占位</div>” document.querySelector("#home").innerHTML = str } }, { path: “/list”, name: “list”, callback: function(route) { console.log(’list:’, route) var str = “<div><a class=‘back’ onclick=‘window.history.go(-1)’>返回</a></div> <h2>列表</h2> <input type=‘text’> <div><a href=‘javascript:void(0);’ onclick=‘linkTo("#/detail")’>详情</a></div>” document.querySelector("#list").innerHTML = str } }, { path: “/detail”, name: “detail”, callback: function(route) { console.log(‘detail:’, route) var str = “<div><a class=‘back’ onclick=‘window.history.go(-1)’>返回</a></div> <h2>详情</h2> <input type=‘text’> <div><a href=‘javascript:void(0);’ onclick=‘linkTo("#/detail2")’>详情 2</a></div><div class=‘height’>内容占位</div>” document.querySelector("#detail").innerHTML = str } }, { path: “/detail2”, name: “detail2”, callback: function(route) { console.log(‘detail2:’, route) var str = “<div><a class=‘back’ onclick=‘window.history.go(-1)’>返回</a></div> <h2>详情 2</h2> <input type=‘text’> <div><a href=‘javascript:void(0);’ onclick=‘linkTo("#/home")’>首页</a></div>” document.querySelector("#detail2").innerHTML = str } }] } //初始化路由 router.init(config) router.beforeEach(function(transition) { console.log(‘切换之 前 dosomething’, transition) setTimeout(function() { //模拟切换之前延迟,比如说做个异步登录信息验证 transition.next() }, 100) }) router.afterEach(function(transition) { console.log(“切换之 后 dosomething”, transition) }) </script>3.2.2.2 html 加 <script> 定义法id=“routerView” :路由切换时,页面的视图窗口data-animationName=“slide”:切换时的动画,目前有 slide 和 fade。class=“page”: 切换的页面data-hash="/home":home 是切换路由时执行的回调方法window.home : 回调方法,名字要与 data-hash 的名字相同<div id=“routerView” data-animationName=“slide”> <div class=“page” data-hash="/home"> <div class=“page-content”> <div id=“home”></div> <script type=“text/javascript”> window.home = function(route) { console.log(‘home:’, route) // var str = “<div><a class=‘back’ onclick=‘window.history.go(-1)’>返回</a></div> <h2>首页</h2> <input type=‘text’> <div><a href=’#/list’ >列表1</div></div><div class=‘height’>内容占位</div>” var str = “<div><a class=‘back’ onclick=‘window.history.go(-1)’>返回</a></div> <h2>首页</h2> <input type=‘text’> <div><div href=‘javascript:void(0);’ onclick=‘linkTo("#/list")’>列表</div></div><div class=‘height’>内容占位</div>” document.querySelector("#home").innerHTML = str } </script> </div> </div> <div class=“page” data-hash="/list"> <div class=“page-content”> <div id=“list”></div> <div style=“height: 700px;border: solid 1px red;background-color: #eee;margin-top: 20px;">内容占位</div> <script type=“text/javascript”> window.list = function(route) { console.log(’list:’, route) var str = “<div><a class=‘back’ onclick=‘window.history.go(-1)’>返回</a></div> <h2>列表</h2> <input type=‘text’> <div><a href=‘javascript:void(0);’ onclick=‘linkTo("#/detail")’>详情</a></div>” document.querySelector("#list”).innerHTML = str } </script> </div> </div> <div class=“page” data-hash="/detail"> <div class=“page-content”> <div id=“detail”></div> <script type=“text/javascript”> window.detail = function(route) { console.log(‘detail:’, route) var str = “<div><a class=‘back’ onclick=‘window.history.go(-1)’>返回</a></div> <h2>详情</h2> <input type=‘text’> <div><a href=‘javascript:void(0);’ onclick=‘linkTo("#/detail2")’>详情 2</a></div><div class=‘height’>内容占位</div>” document.querySelector("#detail").innerHTML = str } </script> </div> </div> <div class=“page” data-hash="/detail2"> <div class=“page-content”> <div id=“detail2”></div> <div style=“height: 700px;border: solid 1px red;background-color: pink;margin-top: 20px;">内容占位</div> <script type=“text/javascript”> window.detail2 = function(route) { console.log(‘detail2:’, route) var str = “<div><a class=‘back’ onclick=‘window.history.go(-1)’>返回</a></div> <h2>详情 2</h2> <input type=‘text’> <div><a href=‘javascript:void(0);’ onclick=‘linkTo("#/home")’>首页</a></div>” document.querySelector("#detail2”).innerHTML = str } </script> </div> </div> </div> <script type=“text/javascript” src="./js/route.js"></script> <script type=“text/javascript”> router.init() router.beforeEach(function(transition) { console.log(‘切换之 前 dosomething’, transition) setTimeout(function() { //模拟切换之前延迟,比如说做个异步登录信息验证 transition.next() }, 100) }) router.afterEach(function(transition) { console.log(“切换之 后 dosomething”, transition) }) </script>参考项目:https://github.com/kliuj/spa-…5. 最后项目地址:https://github.com/biaochenxuying/route博客常更地址1 :https://github.com/biaochenxuying/blog博客常更地址2 :http://biaochenxuying.cn/main.html足足一个多月没有更新文章了,因为项目太紧,加班加班啊,趁着在家有空,赶紧写下这篇干货,免得忘记了,希望对大家有所帮助。如果您觉得这篇文章不错或者对你有所帮助,请点个赞,谢谢。微信公众号:BiaoChenXuYing分享 前端、后端开发等相关的技术文章,热点资源,随想随感,全栈程序员的成长之路。关注公众号并回复 福利 便免费送你视频资源,绝对干货。福利详情请点击: 免费资源分享–Python、Java、Linux、Go、node、vue、react、javaScript ...

January 29, 2019 · 8 min · jiezi