路由处理器(Route handlers)
前一章节已经快速了解过路由处理器的工作模式,这一节着重了解路由处理器中一些参数的意义。先从路由的两个定义方法开始。
路由处理器的定义(Define route handlers)
最简单的方法是直接在路由的第二个参数返回一个对象作为响应的数据,为了便于记忆,可以称之为对象式:
1 | this.get("/movies", { movies: ["Interstellar", "Inception", "Dunkirk"] }) |
接下来是平时比较常用的函数式路由:
1 | this.get("/movies", (schema, request) => { |
函数式路由可以灵活方便的访问 Mirage 的数据层和请求对象。大多数时候都使用函数式路由。
你可以使用任何 HTTP 动词来定义路由,它们的定义方法都是一样的,第一个参数表示 API URL,第二个参数通常是一个用来返回响应数据的函数。
1 | // this.[HTTP verbs].(API_URL, () => {}) |
定义API 响应延迟(Timing)
路由处理器的定义方法提供了第三个可选的对象形参数用于定义 API 的响应延迟。
响应延迟一般情况下用来模拟慢速网络对 API 的请求。
1 | this.get( |
在开发环境时(during development),timing
的默认值是 400ms
,而在写测试时(during testing),timing
的默认值是 0
,所以在测试时,总是能很快的返回结果。
可以为所有路由定义一个全局的 timing
,单独定义的路由 timing
将会覆盖全局的定义。
1 | createServer({ |
如果需要的话,也可以单元测试中定义延迟。只要在需要定义延迟的断言里指定即可:
1 | test("this route works with a delay", function () { |
延迟只在定义的断言中存活,每次断言运行后,Mirage 服务都会重置 timing
,所以不需要担心影响到测试的其他部分。
访问数据层(Accessing the data layer)
还记得函数式定义路由处理器时的第二个参数吗?有时候我们需要给作为第二个参数的方法传入一些参数用来完成一些事情,比如访问 Mirage 数据层或者接收请求参数。
假设我们定义了一个数据模型 movie
,现在需要访问数据模型 movie
,传入第一个参数 schema
:
1 | createServer({ |
第二个参数 request
包装了一些和请求相关的信息,比如请求地址中的参数:
1 | this.get("/movies/:id", (schema, request) => { |
比如应用提交 POST
和 PATCH
请求时提交的请求体(request body)数据:
1 | this.post("/movies", (schema, request) => { |
normalizedRequestAttrs
helper 为请求数据(request data)提供了一些语法糖来减少工作量。
动态路径和查询参数(Dynamic paths and query params)
注入路由处理器的请求对象(the request object),包含任何的动态路由参数(dynamic route segments)和查询参数(query params)。
动态路由参数是通过冒号语法(colon syntax :segment
)在路由路径中定义的,例如/movies/:id
。
查询参数则是一串跟在请求路径中,问号?
后的一串键值对字符串。
在路由处理器中,使用 request.params.[segment]
来访问动态路由参数;使用 request.queryParams.[param]
来访问查询参数。
1 | // GET /authors/2?order=desc&limit=10 |
返回的状态码和响应头(Status codes and headers)
默认情况下,Mirage 将基于响应的 HTTP 动词设置状态码:
- GET 200
- PATCH/PUT 204
- POST 201
- DEL 204
如果 PATCH/PUT/POST 有返回响应数据(response body),将会把状态码改为 200。
此外,响应头里的 Content-Type
将设置为 application/json
。
也可以根据业务需求,在响应中返回自定义的状态码和响应头:
1 | import { createServer, Model, Response } from "miragejs" |
模拟外部 API (External origins)
Mirage 还可以模拟外部 API 的请求。比如在应用中使用了第三方的接口:
1 | this.get('http://api.twitter.com/v1', () => {...}) |
与使用内部 API 不同的是,请求地址需要填写完整的 API 地址。
如果在应用中多次使用了外部 API ,也可以设置一个全局的 URL 前缀urlPrefix
来缩短路由定义中地址的长度:
1 | createServer({ |
帮助函数(Helpers)
Mirage 提供了一些帮助函数用于函数式路由,在定义一些复杂的路由时尤其有用。
注意,帮助函数必须使用完整的
function
而不是箭头函数,因为帮助函数需要获取正确的上下文this
,箭头函数内部得不到正确的this
。
序列化(this.serialize())
这个方法可以在返回响应数据之前,序列化从数据模型(Model)或者数据集合(Collection)得到的数据,最后会得到一个格式化过的 JSON 对象。
1 | // Note: Be sure to use function() here, rather than () => {} |
this.serialize()
方法使用默认的命名序列化器来序列化数据,你也可以在第二个参数中传入自定义的命名序列化器:
1 | this.get("/movies", function (schema) { |
规范化请求数据(this.normalizedRequestAttrs())
这个方法可以在往 Mirage 数据库修改或者创建数据前,规范请求提交的数据。这个方法本质上去除了从 API 返回数据的格式(可以理解为 API 返回数据的反向操作)。还是看例子比较好理解。
假设应用 POST 了这些数据:
1 | // POST /users |
路由处理器接收到之后,在入库之前,使用 this.normalizedReuestAttrs()
来格式化 POST 的数据:
1 | this.post("/users", function (schema, request) { |
请注意,此处的属性名(attribute keys)使用的是驼峰命名法,并且提取了 team
外键。这是因为 user
模型定义了 team
数据关系,如果请求中包含了另一个关系,但是 user
模型没有定义与其的关系,则不会提取这个关系的外键。
这个帮助函数使用了系列化器中定义的 normalize
方法,在这个例子中,假设使用了 JSONAPISerializer
序列化器,它已经定义了 normalize
方法。如果使用了自定义的命名序列化器,则要自行实现 normalize
方法返回 JSON:API
文档定义的数据格式。
this.normalizedRequestAttrs()
方法依赖模型名称modeName
才能正常工作,并尝试从请求 URL 中检查是否包含模型名称。
如果依照 RESTful 规范来设计 API 地址,如 PATCH /users/1
,这个帮助函数可以正常工作;如果使用了自定义的规范,如 PATCH /users/edit/1
,则需要将模型名称作为第一个参数传入:
1 | this.patch("/users/edit/:id", function (schema, request) { |
函数式路由处理器是很灵活的,但是为每个请求编写代码也很繁琐。
评论