序列化器
序列化器是一個物件,負責將從您的路由處理器返回的「模型」或「集合」轉換為符合您前端應用程式期望格式的 JSON 酬載。
例如,這個路由處理器會返回一個 Movie
模型
this.get("movies/:id", (schema, request) => {
return schema.movies.find(request.params.id)
})
序列化器會將該 JavaScript 物件轉換為此 JSON 酬載
// GET /movies/1
{
"data": {
"id": "1",
"type": "movies",
"attributes": {
"title": "Interstellar"
}
}
}
序列化器是 Mirage 架構中最後一個與資料層互動的主要部分,因為產生格式良好的 JSON 回應通常需要遍歷模型的關聯圖。
讓我們看看它們是如何運作的。
選擇要使用的序列化器
使用 Mirage 序列化器的第一步是選擇從哪個內建序列化器開始,這反過來取決於您的後端使用哪種 JSON 格式將資料提供給您的 JavaScript 應用程式。
上面的 JSON 酬載是一個遵循 JSON:API 規範的 API 範例。它具有非常特定的結構,可區分屬性和關聯,支援具名和多型關聯、連結、查詢參數包含等等。它還解決了其他定義不太嚴格的格式中存在的許多問題。
如果您的現有後端 API 使用 JSON:API,Mirage 會隨附 JSONAPISerializer
,它會為您完成繁重的工作。如果您要啟動新的應用程式,請考慮使用 JSON:API,因為它可以解決您在建置 API 時會遇到的許多問題,並可協助您的團隊避免無謂的爭論。
當然,有很多 JavaScript 應用程式不使用 JSON:API 作為其 API 序列化格式。
Mirage 隨附其他兩個具名的序列化器,它們符合流行的後端格式。ActiveModelSerializer
旨在模擬類似於使用 active_model_serializer
gem 建置的 Rails API 的 API,而 RestSerializer
是許多其他常見 API 的良好起點。
根據您自己的後端 API 格式,您需要選擇最接近的序列化器作為起點,並自訂它以符合您的生產格式。我們將在本指南的後面部分詳細討論。
定義序列化器
一旦您選擇了合適的序列化器作為起點,請將其定義為您預設的應用程式範圍序列化器,如下所示
import { createServer, RestSerializer } from "miragejs"
createServer({
serializers: {
application: RestSerializer,
},
})
應用程式序列化器是系統中每個模型和集合使用的預設序列化器。
如果您需要為特定模型類型自訂序列化器,您可以定義模型專用的序列化器,這些序列化器的優先順序高於您的應用程式序列化器。
import { createServer, RestSerializer } from "miragejs"
createServer({
serializers: {
application: RestSerializer,
movie: RestSerializer.extend({
include: ["castMembers"],
}),
},
})
您將在本指南的稍後部分看到您可能需要的模型專用自訂範例。
通常,您可能會有除了模型專用自訂之外,還想進行應用程式範圍的自訂。因此,最佳實務是使用從您的應用程式序列化器擴充的模型專用序列化器。您可以使用以下方式達成此目的
import { createServer, RestSerializer } from "miragejs"
let ApplicationSerializer = RestSerializer.extend({
root: false,
})
createServer({
serializers: {
application: ApplicationSerializer,
movie: ApplicationSerializer.extend({
include: ["castMembers"],
}),
},
})
現在,當您建立模型專用序列化器時,將會維護應用程式範圍的自訂。
自訂序列化器
在自訂應用程式的序列化器時,您主要會調整 Mirage 的預設值。
例如,如果您的應用程式希望屬性名稱為 PascalCase
// GET /movies/1
{
Id: '1',
ReleaseDate: 'Interstellar'
}
您可以覆寫序列化器的 keyForAttribute
方法
import { RestSerializer } from "miragejs"
import { camelCase, upperFirst } from "lodash"
let ApplicationSerializer = RestSerializer.extend({
keyForAttribute(attr) {
return upperFirst(camelCase(attr))
},
})
請參閱每個序列化器的 API 文件,以了解所有可用的自訂 Hook。
關聯
關聯性是序列化器的另一個重要面向,因為後端處理關聯性的方式有很多種。
例如,開箱即用的 JSONAPISerializer
只有在透過查詢參數包含 (includes) 要求時,才會包含關聯性資訊。
/* GET /movies/1?include=cast-members */
{
"data": {
"id": "1",
"type": "movies",
"attributes": {
"title": "Interstellar"
},
"relationships": {
"cast-members": {
"data": [
{ "type": "people", "id": "1" },
{ "type": "people", "id": "2" },
{ "type": "people", "id": "3" }
]
}
}
},
"included": [
{ "id": "1", "type": "people", "attributes": { "name": "Susan" } },
{ "id": "2", "type": "people", "attributes": { "name": "Bob" } },
{ "id": "3", "type": "people", "attributes": { "name": "Jane" } }
]
}
否則,只會包含主要資源。
/* GET /movies/1 */
{
"data": {
"id": "1",
"type": "movies",
"attributes": {
"title": "Interstellar"
}
}
}
但是,有些 API 會包含資源的所有關聯性 ID,無論請求中是否使用查詢參數包含 (includes)。
JSONAPISerializer
上有一個選項可以啟用此功能。
JSONAPISerializer.extend({
alwaysIncludeLinkageData: true,
})
現在,對 /movies/1
發出 GET 請求會回應以下酬載:
/* GET /movies/1 */
{
"data": {
"id": "1",
"type": "movies",
"attributes": {
"title": "Interstellar"
},
"relationships": {
"cast-members": {
"data": [
{ "type": "people", "id": "1" },
{ "type": "people", "id": "2" },
{ "type": "people", "id": "3" }
]
}
}
}
}
現在,您的 JavaScript 應用程式可以使用這些 ID 來隨後提取相關的演員。
其他 API 契約會透過回應連結來提取相關資料。JSONAPISerializer
也提供了這方面的 Hook。
JSONAPISerializer.extend({
links(movie) {
return {
"cast-members": {
related: `/api/movies/${movie.id}/cast-members`,
},
}
},
})
現在,對 /movies/1
發出 GET 請求會回應以下酬載:
/* GET /movies/1 */
{
"data": {
"id": "1",
"type": "movies",
"attributes": {
"title": "Interstellar"
},
"relationships": {
"cast-members": {
"links": {
"related": "/api/movies/1/cast-members"
}
}
}
}
}
其他序列化器也有控制如何載入相關資料的機制。請務必查看 API 文件以了解所有詳細資訊。
使用序列化的 JSON
雖然大多數路由處理程式應傳回 Model 或 Collection 實例,並將序列化邏輯交給序列化器處理,但有時直接在路由處理程式中執行一些最終序列化邏輯可能會很方便。
您可以使用路由處理程式中的 this.serialize
輔助方法來執行此操作 - 請務必使用 function
而不是箭頭函式,以便您可以存取正確的 this
。
createServer({
routes() {
this.get("/movies", function (schema, request) {
let movies = schema.movies.all()
let json = this.serialize(movies)
json.meta.size = movies.length
return json
})
},
})
serialize
輔助程式會使用典型的查找邏輯,首先檢查特定模型的序列化器,然後回退到預設的 Application 序列化器。
如果您有特殊情況,也可以透過傳入序列化器的名稱作為第二個參數來使用特定的序列化器。
import { createServer, RestSerializer } from "miragejs"
let ApplicationSerializer = RestSerializer.extend()
createServer({
serializers: {
application: ApplicationSerializer,
movieWithRelationships: ApplicationSerializer.extend({
include: ["castMembers", "reviews"],
}),
},
routes() {
this.get("/movies/:id", function (schema, request) {
let movie = schema.movies.find(request.params.id)
let json = this.serialize(movie, "movie-with-relationships")
return json
})
},
})
請注意,字串名稱是您用來定義序列化器的鍵 ( movieWithRelationships
) 的烤肉串式版本 (kebab-case version) (movie-with-relationships
)。
一般而言,您應該盡量使用提供的 Hook 來實作適用於大多數模型的 ApplicationSerializer。
當涉及到 API 時,一致性是關鍵。您的生產 API 越一致,在 Mirage 中實作就越容易。使用您的 Mirage 伺服器在您的前端和後端團隊之間溝通 API 契約中的不一致之處。傳統的 API 契約將有助於您減少程式碼的編寫,不僅在 Mirage 中,也在您 JavaScript 應用程式的其他部分!
請務必查看 API 文件,以了解所有可用的 Hook 來客製化您的序列化器層。
現在我們已經介紹了 Mirage 的所有主要概念,我們準備好看看如何使用 Mirage 來有效地測試我們的 JavaScript 應用程式。