Crossroads.js 是基于路由/分发 Route/Dispatch 的 JS 专业路由库 - 文章教程

Crossroads.js 是基于路由/分发 Route/Dispatch 的 JS 专业路由库

发布于 2020-03-09 字数 20169 浏览 1381 评论 0

Crossroads.js 是一个受 Rails、Pyramid、Django、CakePHP 等基于路由/分发(Route/Dispatch)方式处理路由的后端 MVC 框架启发的一套js专业路由库。它能够直接解析传入的字符串并根据相应的规则来过滤和验证路由,然后再执行下一步的操作。

Crossroads.js 是基于路由/分发 Route/Dispatch 的 JS 专业路由库

如果正确使用,它可以通过解耦对象和抽象导航路径来减少代码复杂度。

介绍

Crossroads.js 是一个独立的库功能十分强大和灵活,而且职责单一、目标定位清晰。如果在项目中应用适当不尽可以减少代码的复杂度,同时也可以通过在页面中使用 hash 路由方式来抽象页面路径和服务器请求。当然在开发 SPA 页面过程中,如果我们不使用 backbone、Angular 等类似的 MVC 库来开发 SPA 的话,它是一个相当不错的选择。当然还有一个 router.js 也是一个相当不错的独立路由插件。

Crossroads.js 由 Miller Medeiros 开发,它的使用不依赖于第三方库,内部仅仅只依赖于作者另一个 JS 库 signals.js:将 ActionScript 一个开源库 as3-signals 移植到了 JS。

由于 Crossroads.js 只定义了路由配置和规范,当页面监有路由请求信号发出时 Crossroads.js 会监听到信号,同时浏览器地址栏中的地址栏也会指向目标地址,但还需要一个处理页面 hashchange 事件的库。这里可以使用作者开发的库 hasher.js 或者使用 history.js 来处理 hashchange 事件。

使用方法

下面我将和大家一起来了解 Crossroads.js 的用法:

首先引入相应的js库:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>路由插件(crossroads.js)</title>
  <script src="js/jquery-1.11.2.min.js"></script>
  <script src="js/signals.min.js"></script>
  <script src="js/hasher.min.js"></script>
  <script src="js/crossroads.min.js"></script>
</head>
<body>
  <h1>路由插件(crossroads.js)</h1>
  <script>
    $(function() {
      // 这里输入内容
    });
  </script>
</body>
</html>

在 Crossroads.js 中定义一个路由可以使用 crossroads.addRoute(pattern, [handler], [priority]):Route 方法,可以接受三个参数:

  • parttern: 对应路由规则,可以为字符串或者正则表达式
  • handler: 可选参数,当监听到路由时的处理方法
  • priority: 可选参数,定义路由在路由队列中的优先级

从函数的定义中可以看出其返回一个 Route 对象,后面会讲解 Route 对象相关属性和方法。在 Crossroads.js 中我们可以通过以下两种方式定义一个路由:

定义路由时同时定义操作

crossroads.addRoute('projects/{projectsType}', function(projectsType) {
   console.log(projectsType);
});

只定义一个路由,返回一个’Route’对象

var projectsRoute = crossroads.addRoute('projects/{projectsType}');
// 这里通过'Route.matched'信号来处理操作
projectsRoute.matched.add(function(projectsType) {
  console.log(projectsType);
});
// 通过'crossroads.parse()'方法来解析一个路由
crossroads.parse('/projects/sell'); // 输出: sell

如果需要带查询参数,在crossroads.js中可以通过对每个分组名前添加”?“(例如:{?foo}、:?bar:)来实现。

crossroads.addRoute('projects/{projectsType}/{?filter}',
  function(projectsType, filter) {
  console.log("项目类型:" + projectsType + ';\n' + 
  "排序:" + filter.sort + ';\n' + 
  "项目ID:" + filter.category + ';\n' + 
  "分类:" + filter.attribute + ';');
});
crossroads.parse('projects/latest?attribute=online&category=927151&sort=2');

解析后所有匹配的值会被转换到一个对象中,默认情况下crossroads.js不会关心crossroads.parse()中传入的数据类型,传入的数字也会当成字符串处理。如果设置crossroads.shouldTypecast = ture, 那么会对处理后的对象值做出相应的类型判断。

crossroads.addRoute('projects/{projectsType}', function(projectsType) {
  console.log(projectsType);
  console.log(typeof projectsType);
});

crossroads.parse('/projects/sell'); // 输出: sell和string
crossroads.parse('/projects/12323'); // 输出: 12323和string

// 将对象类型检查开启
crossroads.shouldTypecast = true;

crossroads.parse('/projects/sell'); // 输出: sell和string
crossroads.parse('/projects/12323'); // 输出: 12323和number

在定义路由表达式时也可以通过用 :: 包含一个字符或正则来实现可选路由(optional segments):

crossroads.addRoute('foo/:bar:/:far:', function(bar, far) {
  console.log(bar + " " + far);
});

crossroads.parse('foo'); // undefined undefined
crossroads.parse('foo/abc'); // abc undefined
crossroads.parse('foo/abc/xyz'); // abc xyz

如果需要更多的可选路由(在其官网中称为:’rest’ segments),而且长度不确定,那么可以通过在分组尾部以’*’结尾来实现。

crossroads.addRoute('foo/:bar*:', function(bar) {
  console.log(bar);
});

crossroads.parse('foo'); // undefined
crossroads.parse('foo/abc'); // abc
crossroads.parse('foo/abc/xyz'); // abc/xyz
crossroads.parse('foo/abc/xyz/123'); // abc/xyz/123

在路由解析过程中,如果同一个地址能够被多个规则解析,那么可以通过指定crossroads.addRoute()第三个参数来提升优先级

/* 默认情况下按照声明先后顺序解析 */
crossroads.addRoute("bar/{lorem}", function(lorem) {
console.log(lorem);
});
crossroads.addRoute("bar/{lorem}/:date:", function(lorem, date) {
console.log(lorem + ' : ' + date);
});

crossroads.parse("bar/1"); // 1
crossroads.parse("bar/abc"); // abc
crossroads.parse("bar/abc/2015-3-11"); // abc : 2015-3-11

/* 下面通过设置优先级来改变默认解析顺序 */

// 默认情况下优先级为'0'
crossroads.addRoute("bar/{lorem}", function(lorem) {
console.log(lorem);
}, 0);
// 指定第二个路由优先级为'1'
crossroads.addRoute("bar/{lorem}/:date:", function(lorem, date) {
console.log(lorem + ' : ' + date);
}, 1);

crossroads.parse("bar/1"); // 1 : undefined
crossroads.parse("bar/abc"); // abc : undefined
crossroads.parse("bar/abc/2015-3-11"); // abc : 2015-3-11

Crossroads.js 既然能够创建路由,当然也有移除路由对应的方法:crossroads.removeRoute(route) 以及 crossroads.removeAllRoutes():

// 路由一
var a = crossroads.addRoute("foo/{bar}", function(bar) {
  console.log("第一个路由:" + bar);
});

// 路由二
crossroads.addRoute("bar/{lorem}", function(lorem) {
  console.log("第二个路由:" + lorem);
});

// 路由三
crossroads.addRoute("lorem/{ipsum}", function(ipsum) {
  console.log("第三个路由:" + ipsum);
});

crossroads.parse('foo/route1'); // 第一个路由:route1
crossroads.parse('bar/route2'); // 第二个路由:route2
crossroads.parse('lorem/route3'); // 第三个路由:route3
console.log(crossroads.getNumRoutes()); // 3

// 移除路由一
crossroads.removeRoute(a);

// 第一个路由已从路由队列中移除了,不再接受解析
crossroads.parse('foo/route4');
crossroads.parse('bar/route5'); // 第二个路由:route5
crossroads.parse('lorem/route6'); // 第三个路由:route6
console.log(crossroads.getNumRoutes()); // 2

// 移除所有路由
crossroads.removeAllRoutes();

// 下面三个解析都没有任何作用,因为当前没有可解析的路由
crossroads.parse('foo/route7');
crossroads.parse('bar/route8');
crossroads.parse('lorem/route9');
console.log(crossroads.getNumRoutes()); // 0

注意在上面示例中,由于 crossroads.removeRoute() 需要一个 Route 对象作为参数,所以最好以第二种方式来定义一个路由规则,同时也方便路由统一配置和管理。crossroads.getNumRoutes() 方法用于获取当前路由队列中路由数量。

使用示例

Read the documentation for a detailed explanation of available methods and features.

Basic usage

crossroads.addRoute('/news/{id}', function(id){
  console.log(id);
});
crossroads.parse('/news/123'); //will match '/news/{id}' route passing 123 as param

Optional parameters

Optional segments are very useful since they can drastically reduce the amount of routes required to describe the whole application.

//{id} is required, :date: is optional
crossroads.addRoute('/news/{id}/:date:', function(id, date){
  console.log(id +' - '+ date);
});
crossroads.parse('/news/123'); //match route and pass "123" as param
crossroads.parse('/news/45/2011-09-31'); //match route passing "45" and "2011-09-31" as param

Rest segments (v0.8.0+)

Rest segments are useful in cases where you might want to match an arbitrary number of segments.

// {section*} will match multiple segments
crossroads.addRoute('/news/{section*}/{id}', function(section, id){
  console.log(section +' - '+ id);
});
//match route and pass "lorem" and "123"
crossroads.parse('/news/lorem/123');
//match route passing "lorem/ipsum/dolor" and "45"
crossroads.parse('/news/lorem/ipsum/dolor/45');

Decode Query String (v0.9.0+)

If a capturing group starts with ? it will be decoded into an object.

crossroads.addRoute('foo.php{?query}', function(query){
  console.log(query.lorem +' - '+ query.dolor);
});
// match route passing {lorem:"ipsum", dolor:"amet"}
crossroads.parse('foo.php?lorem=ipsum&dolor=amet');

RegExp route

//capturing groups are passed as parameters to listeners
crossroads.addRoute(/^news\/([0-9]+)$/, function(id){
  console.log(id);
});
crossroads.parse('/news/123'); //will match route passing "123" as param
crossroads.parse('/news/qwerty'); //won't match route

Store route reference and attach mutiple listeners

var articleRoute = crossroads.addRoute('/article/{category}/{name}');
articleRoute.matched.add(function(category, name){
  console.log(category);
});
articleRoute.matched.add(function(category, name){
  console.log(name);
});
 //will match articleRoute passing "lol_catz" and "keyboard_cat" as param
crossroads.parse('/article/lol_catz/keyboard_cat');

Use RegExp to validate params

var specialNews = crossroads.addRoute('/news/{id}');
specialNews.matched.add(function(id){
  console.log(id);
});
specialNews.rules = {
  id : /^[0-9]+$/ //match only numeric ids
};
crossroads.parse('/news/asd'); //won't match since ID isn't numeric
crossroads.parse('/news/5'); //will match `specialNews` and pass "5" as param to all listeners
crossroads.parse('/news/5asd'); //won't match since ID isn't numeric

Use functions to validate params

This feature is very useful and gives a lot of flexibility on the validation.

var specialNews = crossroads.addRoute('/news/{category}/{id}');
specialNews.matched.add(function(id){
  console.log(id);
});
specialNews.rules = {
  //function should return a boolean value
  id : function(val, request, values){
    return val !== 'foo' && values.category !== 'bar';
  }
};
crossroads.parse('/news/world/asd'); //will match
crossroads.parse('/news/bar/5'); //won't match

Use arrays to validate params

var specialNews = crossroads.addRoute('/news/{id}');
specialNews.matched.add(function(id){
  console.log(id);
});
specialNews.rules = {
  //segments are treated as strings unless `crossroads.shouldTypecast = true` (default = false)
  id : ['asd', '5', '123', '23456', 'qwerty']
};
crossroads.parse('/news/asd'); //will match
crossroads.parse('/news/5'); //will match
crossroads.parse('/news/loremipsum'); //won't match

Validate whole request

var specialNews = crossroads.addRoute('/news/{id}');
specialNews.matched.add(function(id){
  console.log(id);
});
specialNews.rules = {
  //request_ will accept any kind of validation rule (function, array, RegExp)
  request_ : ['/news/asd', '/news/5']
};
crossroads.parse('/news/asd'); //will match
crossroads.parse('/news/5'); //will match
crossroads.parse('/news/loremipsum'); //won't match

Normalize parameters & add default parameters

This feature is very powerful and can simplify a lot the logic of you application, it makes it easy to create multiple alias to the same route (see #21) and to set default parameters to the matched signal.

function onSectionMatch(sectionId, subId){
    console.log(sectionId +' - '+ subId);
}

var newsRoute = crossroads.addRoute('/news/{date}/{id}', onSectionMatch);
newsRoute.rules = {
  normalize_ : function(request, vals){
      //return parameters that should be passed to matched signal listeners
      return ['editorial', vals.id];
  }
};
crossroads.parse('/news/2011-08-32/123'); //will log "editorial - 123"

Default arguments (v0.8.0+)

If you are using crossroads to route nodejs requests you can use the crossroads.parse() second argument to specify the default arguments passed to all handlers. Very useful in case you need to access the request and response objects.

var http = require('http'),
    url = require('url'),
    crossroads = require('crossroads');

http.createServer(function (req, res) {
  //pass request and response as first arguments to all signals
  crossroads.parse( url.parse(req.url).pathname, [req, res] );
}).listen(1337, "127.0.0.1");

Typecast parameters

crossroads.addRoute('/foo/{val}', function(val){
    console.log(typeof val);
});

crossroads.shouldTypecast = false; //default = false
crossroads.parse('/foo/false'); //log "string"
crossroads.parse('/foo/true'); //log "string"
crossroads.parse('/foo/123'); //log "string"
crossroads.parse('/foo/abc'); //log "string"

crossroads.shouldTypecast = true; //default = false
crossroads.parse('/foo/false'); //log "boolean"
crossroads.parse('/foo/true'); //log "boolean"
crossroads.parse('/foo/123'); //log "number"
crossroads.parse('/foo/abc'); //log "string"

Check if route match a string

var myRoute = crossroads.addRoute('/foo/{id}');
console.log( myRoute.match('/foo/bar') ); //true

Create a string that matches the route (v0.9.0+)

var myRoute = crossroads.addRoute('foo/{id}/:slug:');
myRoute.interpolate({id: 123, slug: 'something'}); // "foo/123/something"
myRoute.interpolate({id: 456}); // "foo/456"

Dispose route

var myRoute = crossroads.addRoute('/foo/{id}');
myRoute.dispose(); //remove route from crossroads and also remove all listeners from route.matched

Create a new Router instance

You can create multiple Routers if needed. The new instance will work exactly like the crossroads object and it is totally independent.

   var sectionRouter = crossroads.create();
   sectionRouter.addRoute('/foo');
   console.log( sectionRouter.getNumRoutes() ); // 1

Piping multiple routers (v0.11.0+)

It’s often a better practice to use multiple routers than greedy routes (more granular control and split responsibility), so after v0.11.0 you can also pipe/chain multiple routers.

var sectionRouter = crossroads.create();
var navRouter = crossroads.create();

sectionRouter.pipe(navRouter);
// will also call `parse()` on `navRouter`
sectionRouter.parse('foo');

// there is also an `unpipe()` method
// sectionRouter.unpipe(navRouter);

Validate RegExp route capturing groups

You can also validate each capturing group (the same way you do with the normal segments):

var awesomeRoute = crossroads.addRoute(/^(\w+)\/(\w+)$/, function(a, b){
  console.log(a +' - '+ b);
});
awesomeRoute.rules = {
  //keys match index of capturing groups
  '0' : ['asd', 'qwe', 'zxc'];
  '1' : function(val, request, values){
    if(values[0] == 'asd' && val == '123'){
      return false
    } else {
      return true;
    }
  }
};
crossroads.parse('asd/123'); //wont match
crossroads.parse('asd/456'); //match

Execute an action every time crossroads matches any route

Useful for tracking, debugging and any other action that may need to happen every time the app changes the current route.

//log all routes
crossroads.routed.add(console.log, console);

Execute an action every time crossroads can’t match any route

Useful for tracking, debugging or handling errors.

//log all requests that were bypassed
crossroads.bypassed.add(console.log, console);

Execute an action when changing from current route to next one (v0.8.0+)

Useful for cases where you might need to trigger an action in between changes and that should only happen in a few cases.

var route1 = crossroads.addRoute('/foo/{bar}');
// will be triggered when current route is `route1` and crossroads is about to
// match another route
route1.switched.add(console.log, console);

Set Route priority

Routes with higher priority will be tested before. Routes are tested by order of creation if priority is omitted. The default priority is 0.

crossroads.addRoute('/lorem');
crossroads.addRoute('/{foo}', null, 5);
crossroads.addRoute('/{bar}');
crossroads.parse('/lorem'); //will match second route since it has higher priority

Chaining

Some people loves chaining… if that’s your thing, crossroads API can be chained till a certain extent, use it with care, code may become harder to read:

crossroads
    .addRoute('{section}', loadSection)
    .rules = {
        section : ['home', 'login', 'contact']
    };

Using with History.js

crossroads.js can be used with Hasher.js, but also with History.js. You’ll need signals.js, crossroads.js, history.js, a history.js adapter (on their site). Example is in Coffeescript.

  # ### Routes

  # Route for some view that takes params
  crossroads
    .addRoute('{?query}', (query) ->
      console.log(query)
    )
  # Route for an admin section
  crossroads
    .addRoute('/admin', ->
      console.log("admin section")
    )

  # Process the landing route
  crossroads.parse(document.location.pathname + document.location.search)

  # log all requests that were bypassed / not matched
  crossroads.bypassed.add(console.log, console);


  # ### History management

  History = window.History
  if History.enabled
    State = History.getState()
    
    # set initial state to first page that was loaded
    History.pushState
      urlPath: window.location.pathname
    , $("title").text(), State.urlPath
  else
    return false

  loadAjaxContent = (target, urlBase, selector) ->
    $(target).load urlBase + " " + selector

  updateContent = (State) ->
    selector = "#" + State.data.urlPath.substring(1)
    if $(selector).length #content is already in #hidden_content
      $("#content").children().appendTo "#hidden_content"
      $(selector).appendTo "#content"
    else
      $("#content").children().clone().appendTo "#hidden_content"
      loadAjaxContent "#content", State.url, selector

  
  # Content update and back/forward button handler
  History.Adapter.bind window, "statechange", ->
    crossroads.parse(document.location.pathname + document.location.search)

  
  # navigation link handler
  $("body").on "click", "a", (e) ->
    urlPath = $(this).attr("href")
    if _(urlPath).startsWith('#')   # probably some js function we don't want to mess with
      return true
    e.preventDefault()
    title = $(this).text()
    History.pushState
      urlPath: urlPath
    , title, urlPath

相关链接

Github 地址:https://github.com/millermedeiros/crossroads.js

如果你对这篇文章有疑问,欢迎到本站 社区 发帖提问或使用手Q扫描下方二维码加群参与讨论,获取更多帮助。

扫码加入群聊

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

目前还没有任何评论,快来抢沙发吧!

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

2891 文章
评论
84935 人气
更多

推荐作者

勿忘心安

文章 0 评论

ekko

文章 0 评论

江挽川

文章 0 评论

献世佛

文章 0 评论

Meets

文章 0 评论