Ember JSONAPIAdapter
目前 Emberjs 框架中使用 JSONAPIAdapter 为默认的 adapter,遵循 JSONAPI 的通信标准。目前本公司也默认使用的是此 adapter,所以一下 api 均是在此基础上。
另如无特殊说明,文内的文件结构均是在 Pods 目录结构下的。
Adapter
在 Ember Data 中,adapter 决定了如何向后端传递数据,提供了一些可以设置的接口,如 格式化请求的 URL,设置请求的 header 等。
在 Emberjs 项目中,你可以设置顶层的 application/adapter.js
也可以在每个对应的 model
(pods 文件目录)的文件中创建针对单个 model
的 adapter:modelName/adapter.js
。其中针对单个的 adapter.js
的优先权大于 application/adapter.js
。
URL Conventions
在 Ember Data 中默认使用的 DS.JSONAPIAdapter
中,如果要请求数据,可以在 route.js
中:
// route.js
model() {return this.get('store').findAll('post');
}
上面的请求默认发向的 url 为/posts
,也就是 JSONAPIAdapter 会默认为请求路径转换为复数。
提供了几个默认的请求:
Action | HTTP Verb | URL |
---|---|---|
Find 1 | GET | /posts/123 |
Find All 2 | GET | /posts |
Update 3 | PATCH | /posts/123 |
Create 4 | POST | /posts |
Delete 5 | DELETE | /posts/123 |
请求过程中的复数转换
上文也提到了,在使用 JSONAPIAdapter 过程中,会进行复数的转换,包括对 modelName
也是,会进行转换,比如说 我们请求:
model(){return this.get('store').findAll('campus');
}
在 JSONAPIAdapter
中会发送请求到 /campus
中,而寻找的 modelName
则是 campu
这显然不对,所以我们需要对特殊字词进行处理。
在 Ember Data 中使用的是 Ember Inflector 控制的复数转换。同样的,我们也需要对它进行设置(pods 目录下):
// app/app.js
import './modules/custom-inflector-rules';
// app/modules/custom-inflector-rules.js
import Inflector from 'ember-inflector';
const inflector = Inflector.inflector;
// Tell the inflector that the plural of "campus" is "campuses"
inflector.irregular('campus', 'campuses');
// Modules must have an export, so we just export an empty object here
export default {};
然后可以看到 请求发送的地址是 /campuses
,寻找的 modelName
也是 campus
,现在变成正常的了,数据也是可以正常显示的了。
properties
JSONAPIAdapter
提供了以下 porperties:
- coalesceFindRequests
- coalesceFindRequests
- defaultSerializer
- headers
- host
- namespace
coalesceFindRequests
这有篇文章讲的这个属性的使用。下面是具体的使用:
我们先来看不设置此属性的时候:
// 后端返回的数据
'data': {
'type': 'post',
'id': 'idPost1',
'attributes': {
'title': 'post1',
'content': 'post content'
},
'relationships': {
'comments': {
'data': [
{
'id': 1,
'type': 'comment'
},
{
'id': 2,
'type': 'comment'
}
]
}
}
}
这是 post 数据,当我们请求 post
数据的时候:
// route.js
model() {return this.get('store').findRecord('post', 'idPost1');
}
这时候可以看到 Ember Data 向 mirage 发送了两条请求(需要设置 {async: true}
:
GET '/comments/1'
GET '/comments/2'
在将 coalesceFindRequests
属性设置为 true
的时候:
// comment/adapter.js
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({coalesceFindRequests: true});
可以看到现在只发送一条请求:
GET '/comments?filter[id]=1,2
defaultSerializer
defaultSerializer
这个属性设置使用的 serializer
:
// post/adapter.js
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({defaultSerializer: 'person'});
将使用 person/serializer.js
中的设定对 post
进行设定。
需要注意 的是此属性起作用的时候只有在此 model 的 serializer.js
以及 application/serializer.js
不存在的时候起作用(Pods 目录)。
header
HTTP 消息头 允许客户端和服务器通过 request和 response传递附加信息 6。某一些 API 会需要一些请求头,比如现在项目中使用到的 token,就是在每次进行请求的时候都携带这些请求头数据发送给后端服务。一般不在init()
中设置 header,而是将其设为计算属性:
// post/adapter.js
headers: computed(function () {
return {
'dataType': 'json',
'contentType': 'application/json',
'Content-Type': 'application/json',
'Authorization': `bearer selfToken`
};
})
请求头就被改变了。
这样就会在每次请求的时候携带本地的 token。
host
自定义主机,默认为本地作用域。
namespace
顾名思义, 定义命名空间的.
// adapter.js
namespace: '/api/'
main method
pathForType(type)
格式化请求的路径:
// router
this.get('store').findAll('bjCompany');
如果不在adapter.js
中进行设置, 发送的请求是:
GET /bj-companies
也就是默认的转换为中划线以及进行复数化, 如果不想进行中划线的转换:
// bj-company/adapter.js
import DS from 'ember-data';
import {camelize} from '@ember/string';
import {pluralize} from 'ember-inflector';
export default DS.JSONAPIAdapter.extend({pathForType(type) {let newType = pluralize(camelize(type));
return newType; // newType: bjCompanies
}
});
这样就达到了我们的目的.
buildURL
对 URL 进行格式化, 主要是进行复数化, 可以通过复写 pathForType()
方法来达到重写 URL 的目的.
Record 相关
JSONAPIAdapter
提供的关于 record 的一些 hook, 可以让你复写这些 hook 的逻辑来达到自己的目的, 但是一般完全符合 JSONAPI 的数据规范后, 这些基本不用重写. 更多关于 Record 的部分请查询 相关 API 以及其他文档.
这里列举出来 JSONAPIAdapter
中涉及 record 的一些 hook:
- [createRecord() ]()
-
fetchRecord
- findAll()
- findBelongsTo()
- findHasMany()
- findMany()
- findRecord()
- [query()]()
- [queryRecord()]()
- updateRecord()
- deleteRecord()
generateIdForRecord()
用于生成在客户端生成的 Record 的 id. 返回的值将分配给 record 的primaryKey
. 一般很少使用. 比如:
// bj-company/adapter.js
generateIdForRecord(store, type, inputProperties) {return 343;}
新创建的 Record 的 id 就会变成 343(这里只是演示作用).
handleResponse()
返回 ajax 请求的数据或错误, 如果想修改返回的数据规范或错误提示可以在此处进行修改.
很少使用, 视具体项目情况而使用.
isInvalid()
验证如果是 422 错误, 在 handleResponse()
返回一个 InvalidError()
的实例.
isSuccess()
请求返回成功, 相应的 status:
(status >= 200 && status < 300) || status === 304;
shouldBackgroundReloadAll()
store 使用此方法来确定在 store.findAll
使用缓存的记录数组解析后,存储是否应重新加载记录数组。
默认为 true
.
设为false
之后, 带来的效果就是在本地两个页面同时显示同一 model 实例, 从一页面跳转到另一页面的时候不会再次请求数据.
// adapter.js
shouldBackgroundReloadAll(store, snapshotArray) {return false;}
注意 这个方法只有在 store 返回缓存数据之后才被调用. 也就是当第一次请求数据的时候此方法不会被执行.
This method is only checked by the store when the store is returning a cached record array.
shouldBackgroundReloadRecord()
与上面同理.
shouldReloadAll()
当返回 true 的时候会立刻再次请求数据, 如果返回 false, 会立即使用本地缓存. 具体使用实例可以查看 文档
shouldReloadRecord()
与上面同理.
sortQueryParams()
对查询的 参数 进行自定义排列, 默认使用的是正序.
urlForCreateRecord()
为通过 store.createRecord()
创建的本地 record 在进行 record.save()
操作的时候构建 相应的 url
;
其他的 api 也类似:
- urlForDeleteRecord (id, modelName, snapshot)
- urlForFindAll (modelName, snapshot)
- urlForFindBelongsTo (id, modelName, snapshot)
- urlForFindHasMany()
- urlForFindMany()
- urlForFindRecord()
- urlForQuery()
- urlForQueryRecord
- urlForUpdateRecord()
总结
JSONAPIAdapter 的相关 API 的分析到此结束.
Written by FrankWang.
-
this.get('store').findRecord('post',1)
↩ -
this.get('store').findAll('post')
↩ -
postRecord.save()
↩ -
this.get('store').createRecord('post').save()
↩ -
postRecord.destroyRecord()
↩ - 在 MDN 中查看 ↩