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
这有篇文章讲的这个属性的使用。下面是具体的使用:
我们先来看不设置此属性的时候:
// 后端返回的数据
'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.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。