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 消息头允许客户端和服务器通过 requestresponse传递附加信息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:

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 也类似:

总结

JSONAPIAdapter 的相关API 的分析到此结束.

Written by FrankWang.

  1. this.get('store').findRecord('post',1)
  2. this.get('store').findAll('post')
  3. postRecord.save()
  4. this.get('store').createRecord('post').save()
  5. postRecord.destroyRecord()
  6. MDN 中查看

法研鲁迅
40 声望6 粉丝

引用和评论

0 条评论