Emberjs-中-JSONAPIAdapter-的常用-API

27次阅读

共计 5011 个字符,预计需要花费 13 分钟才能阅读完成。

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 消息头 允许客户端和服务器通过 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:

  • [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.


  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 中查看 ↩

正文完
 0