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.jsimport './modules/custom-inflector-rules';
// app/modules/custom-inflector-rules.jsimport 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 hereexport 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.jsimport DS from 'ember-data';export default DS.JSONAPIAdapter.extend({ coalesceFindRequests: true});
可以看到现在只发送一条请求:
GET '/comments?filter[id]=1,2
defaultSerializer
defaultSerializer
这个属性设置使用的 serializer
:
// post/adapter.jsimport 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.jsimport 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.jsshouldBackgroundReloadAll(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 中查看 ↩