Node.js+Webpack开发实战
上QQ阅读APP看书,第一时间看更新

4.3 路由

路由是指应用程序如何根据指定的路由路径和指定的HTTP请求方法(GET和POST等)来处理请求。

在Express应用中,每个路由可以有一个或多个处理函数,这些函数会在路由匹配时执行。

路由采用以下结构定义:

app.METHOD(PATH, HANDLER)

· app:应用实例。

· METHOD:小写get和post等的HTTP请求方法。

· PATH:路由路径。

· HANDLER:路由匹配时执行的函数。

4.3.1 路由方法

路由方法是从HTTP方法派生的,以下是GET和POST方法定义路由的示例:

app.get('/', (req, resp) => {
 resp.send('GET请求');
});

app.post('/', (req, resp) => {
 resp.send('POST请求');
});

Express支持所有的HTTP请求方法:

· get

· post

· head

· options

· delete

· put

· patch

如果需要使用一个路由来处理所有的请求方法,则可以调用app.all():

app.all('/', (req, resp) => {
  resp.send('请求首页');
});

4.3.2 路由路径

路由路径结合请求方法,定义了可以发出请求的地址。路由路径可以是字符串、字符串模式或正则表达式。

查询字符串(一般称为GET参数)不是路由路径的一部分。

(1)以下是一些基于字符串的路由路径的示例。

以下路由路径会将请求匹配到根路由/:

app.get('/', (req, resp) => {
 resp.send('主页');
});

以下路由路径会将请求匹配到/about:

app.get('/about', (req, resp) => {
 resp.send('关于页面');
});

以下路由路径可以将请求匹配到/random.txt文件:

app.get('/random.txt', (req, resp) => {
  resp.send('random.txt');
});

字符串模式可以“认为”是正则表达式的子集,支持部分正则表达式语法。

(2)以下是一些基于字符串模式的路由路径示例。

以下路由路径将与/acd和/abcd相匹配。“?”号在正则表达式中代表“至多一个”,示例代码中就是匹配“至多一个b”:

app.get('/ab?cd', (req, resp) => {
 resp.send('ab?cd');
});

以下路由路径将与/abcd、/abbcd、/abbbcd等等相匹配。+在正则表达式中代表“至少一个”,示例代码中就是匹配“至少一个b”:

app.get('/ab+cd', (req, resp) => {
  resp.send('ab+cd');
});

以下路由路径将与/abcd和/ad相匹配。()在正则表达式中代表分组,示例代码中就是“要么有一个bc,要么没有bc”:

app.get('/a(bc)?d', (req, resp) => {
 resp.send('a(bc)?d');
});

(3)以下是一些基于正则表达式的路由路径示例。

以下路由路径将与任何带有user的请求链接相匹配:

app.get(/user/, (req, resp) => {
 resp.send('/user/');
});

以下路由路径将严格与/admin匹配:

app.get(/^\/admin$/, (req, resp) => {
    resp.send('/^\/admin$/');
});

4.3.3 路由参数

路由参数用于捕获URL中各位置的值。捕获的值将填充到req.params对象中,并将路由路径中指定的route参数名称作为req.params对象的键。

app.get('/users/:userId/timelines/:timelineId', (req, resp) => {
 resp.json(req.params);
});

访问:http://localhost:8080/users/1/timelines/1,将得到以下响应:

{
 "userId": "1",
 "timelineId": "1"
}

路由参数的名称必须由[A-Za-z0-9_](也就是大小写字母、数字、下画线)组成。

字符串和字符串模式路由路径中的中划线“-”和点“.”无特殊意义,Express按照字面意思处理这两个字符。因为在正则表达式中这两个字符有特殊意义,特此说明以防止混淆。

app.get('/users/:firstName.:lastName', (req, resp) => {
  resp.json(req.params);
});

访问http://localhost:8080/users/lei.xia,将得到以下响应:

{
  "firstName": "lei",
  "lastName": "xia"
}

在/users/:userId/timelines/:timelineId例子中,预期匹配的是数字ID类型的参数,但是由于没有类型限制,字符串形式的参数也会匹配到。如果不限制参数类型,容易引发类型问题。

在路由参数后使用正则表达式可以限制参数类型,不满足类型的参数无法匹配该路由。

比如上述例子中,我们限制userId和timelineId为数字类型,可以使用以下代码:

app.get('/users/:userId(\\d+)/timelines/:timelineId(\\d+)', (req, resp) => {
    resp.json(req.params);
});

访问http://localhost:8080/users/1/timelines/1,将得到以下响应:

{
  "userId": "1",
  "timelineId": "1"
}

访问http://localhost:8080/users/1a/timelines/1,将得到以下响应:

Cannot GET /users/1a/timelines/1

因为1a与(\d+)不匹配。

4.3.4 路由函数

路由方法和路由路径匹配之后就会执行对应的路由函数,路由函数的签名如下:

function(request, response, next)

· request Express:请求对象。

· response Express:响应对象。

· next:匹配的下一个路由函数(可选参数)。

1.单个路由函数

最简单的路由函数,对请求处理后发出响应,结束本次请求处理。

app.get('/', (req, resp) => {
 resp.send('/');
});
2.多个路由函数

对于同一个路由,可以定义多个路由函数来处理,每个路由函数做一项工作。

不要忘记next()方法的调用,即使定义多个路由函数,只要第一个函数未调用next(),后续的路由函数都不会执行。

app.get('/', (req, resp, next) => {
    console.log(`${req.method} ${req.path}`);
    next();
}, (req, resp) => {
    resp.send('首页');
});

如下示例定义了两个路由函数,第一个函数打印了当前请求方法和请求路径,然后调用next()执行下一个路由函数以响应此次请求。在上面的示例中,访问首页时终端会输出“GET /”,之后会向客户端输出“首页”字样。Express中还可以使用函数数组来定义多个路由函数,上述例子的另外一种写法如下,两种写法效果是一样的。

function logger(req, resp, next) {
    console.log(`${req.method} ${req.path}`);
    next();
}

function home(req, resp) {
    resp.send('首页');
}

// 设置路由
app.get('/', [logger, home]);
3.公共路由路径

如果多个路由有同样的路由路径,只是请求方法不同,若分别为每种方法定义一次路由,则不利于模块化。

app.get('/user/login', (req, resp) => {
 resp.send('登录页面');
});

app.post('/user/login', (req, resp) => {
 resp.send('登录处理');
});

针对这种典型场景,Express提供了app.router()来处理:

4.模块化的路由

使用Express提供的Router对象可以创建模块化的对象,实现路由和入口JS的解耦。使用模块化的路由有以下优点:

· 便于维护。

· 统一的路由前缀。

· 模块化。

user.js用户相关路由:

const express = require('express');
const router = express.Router();

router.get('/login', (req, resp) => {
   resp.send('登录');
});

router.get('/register', (req, resp) => {
   resp.send('注册');
});

// 导出路由对象
module.exports = router;

timeline.js动态相关路由:

const express = require('express');
const router = express.Router();

router.get('/list', (req, resp) => {
    resp.send('动态列表');
});

// 导出路由对象
module.exports = router;

index.js入口文件:

上述例子最终会生成以下路由:

· GET /user/login。

· GET /user/register。

· GET /timeline/list。