Ember适配器与序列化器 #

一、适配器概述 #

适配器负责处理与后端API的通信,决定如何发送请求和接收响应。

1.1 适配器类型 #

类型 说明
RESTAdapter RESTful API
JSONAPIAdapter JSON API规范
自定义Adapter 自定义API格式

1.2 适配器层次 #

text
ApplicationAdapter (全局默认)
    ↓
ModelAdapter (特定模型)

二、RESTAdapter #

2.1 基本配置 #

javascript
// app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';

export default class ApplicationAdapter extends RESTAdapter {
  namespace = 'api/v1';
  host = 'https://api.example.com';
}

2.2 请求URL #

javascript
// findAll
GET /api/v1/posts

// findRecord
GET /api/v1/posts/1

// createRecord
POST /api/v1/posts

// updateRecord
PUT /api/v1/posts/1

// deleteRecord
DELETE /api/v1/posts/1

2.3 自定义headers #

javascript
export default class ApplicationAdapter extends RESTAdapter {
  get headers() {
    return {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${this.session.token}`,
    };
  }
}

2.4 路径自定义 #

javascript
export default class ApplicationAdapter extends RESTAdapter {
  pathForType(modelName) {
    const decamelized = modelName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
    return decamelized.pluralize();
  }
}

三、JSONAPIAdapter #

3.1 基本配置 #

javascript
// app/adapters/application.js
import JSONAPIAdapter from '@ember-data/adapter/json-api';

export default class ApplicationAdapter extends JSONAPIAdapter {
  namespace = 'api/v1';
}

3.2 JSON API格式 #

json
{
  "data": {
    "type": "posts",
    "id": "1",
    "attributes": {
      "title": "Hello World",
      "body": "Content..."
    },
    "relationships": {
      "author": {
        "data": { "type": "users", "id": "1" }
      }
    }
  }
}

四、自定义适配器 #

4.1 模型特定适配器 #

javascript
// app/adapters/post.js
import ApplicationAdapter from './application';

export default class PostAdapter extends ApplicationAdapter {
  namespace = 'api/v2';

  urlForFindRecord(id, modelName, snapshot) {
    return `${this.host}/${this.namespace}/posts/${id}/detail`;
  }
}

4.2 自定义请求方法 #

javascript
export default class PostAdapter extends ApplicationAdapter {
  publish(id) {
    const url = this.buildURL('post', id) + '/publish';
    return this.ajax(url, 'POST');
  }

  archive(id) {
    const url = this.buildURL('post', id) + '/archive';
    return this.ajax(url, 'PUT');
  }
}

4.3 查询参数处理 #

javascript
export default class PostAdapter extends ApplicationAdapter {
  urlForQuery(query, modelName) {
    if (query.category) {
      return `${this.host}/${this.namespace}/categories/${query.category}/posts`;
    }
    return super.urlForQuery(...arguments);
  }
}

五、序列化器概述 #

序列化器负责数据格式的转换,处理请求和响应的数据格式化。

5.1 序列化器类型 #

类型 说明
RESTSerializer REST API格式
JSONAPISerializer JSON API格式
自定义Serializer 自定义格式

六、RESTSerializer #

6.1 基本配置 #

javascript
// app/serializers/application.js
import RESTSerializer from '@ember-data/serializer/rest';

export default class ApplicationSerializer extends RESTSerializer {
}

6.2 期望的响应格式 #

json
{
  "posts": [
    {
      "id": 1,
      "title": "Post 1",
      "body": "Content...",
      "author_id": 1
    }
  ]
}

6.3 自定义键映射 #

javascript
export default class ApplicationSerializer extends RESTSerializer {
  keyForAttribute(key) {
    return key; // 保持驼峰命名
  }

  keyForRelationship(key) {
    return key; // 保持驼峰命名
  }
}

七、JSONAPISerializer #

7.1 基本配置 #

javascript
// app/serializers/application.js
import JSONAPISerializer from '@ember-data/serializer/json-api';

export default class ApplicationSerializer extends JSONAPISerializer {
}

7.2 自定义属性映射 #

javascript
export default class ApplicationSerializer extends JSONAPISerializer {
  attrs = {
    createdAt: 'created_at',
    updatedAt: 'updated_at',
  };
}

八、自定义序列化器 #

8.1 处理自定义响应 #

javascript
// app/serializers/post.js
import RESTSerializer from '@ember-data/serializer/rest';

export default class PostSerializer extends RESTSerializer {
  normalizeResponse(store, primaryModelClass, payload, id, requestType) {
    // 处理自定义响应格式
    if (payload.data) {
      payload.posts = payload.data;
      delete payload.data;
    }

    return super.normalizeResponse(...arguments);
  }

  normalize(modelClass, resourceHash) {
    // 自定义字段映射
    resourceHash.attributes = resourceHash.attributes || {};
    resourceHash.attributes.title = resourceHash.title;
    resourceHash.attributes.body = resourceHash.body;

    return super.normalize(...arguments);
  }
}

8.2 序列化请求 #

javascript
export default class PostSerializer extends RESTSerializer {
  serialize(snapshot, options) {
    const json = super.serialize(...arguments);

    // 添加额外字段
    json.clientTimestamp = Date.now();

    return json;
  }

  serializeAttribute(snapshot, json, key, attribute) {
    // 排除某些字段
    if (key === 'internalField') {
      return;
    }
    super.serializeAttribute(...arguments);
  }
}

8.3 处理关联 #

javascript
export default class PostSerializer extends RESTSerializer {
  serializeBelongsTo(snapshot, json, relationship) {
    const key = relationship.key;
    const belongsTo = snapshot.belongsTo(key);

    if (belongsTo) {
      json[`${key}Id`] = belongsTo.id;
    }
  }

  serializeHasMany(snapshot, json, relationship) {
    const key = relationship.key;
    const hasMany = snapshot.hasMany(key);

    if (hasMany) {
      json[`${key}Ids`] = hasMany.map((item) => item.id);
    }
  }
}

九、常见场景 #

9.1 嵌套路由 #

javascript
// app/adapters/comment.js
import ApplicationAdapter from './application';

export default class CommentAdapter extends ApplicationAdapter {
  buildURL(modelName, id, snapshot, requestType, query) {
    let url = super.buildURL(...arguments);

    if (snapshot && snapshot.record && snapshot.record.post) {
      const postId = snapshot.record.post.get('id');
      url = url.replace('/comments', `/posts/${postId}/comments`);
    }

    return url;
  }
}

9.2 认证处理 #

javascript
// app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
import { inject as service } from '@ember/service';

export default class ApplicationAdapter extends RESTAdapter {
  @service session;

  get headers() {
    if (this.session.isAuthenticated) {
      return {
        Authorization: `Bearer ${this.session.token}`,
      };
    }
    return {};
  }

  handleResponse(status, headers, payload) {
    if (status === 401) {
      this.session.invalidate();
    }
    return super.handleResponse(...arguments);
  }
}

9.3 错误处理 #

javascript
export default class ApplicationAdapter extends RESTAdapter {
  handleResponse(status, headers, payload) {
    if (this.isInvalid(status, headers, payload)) {
      return payload.errors || payload;
    }
    return super.handleResponse(...arguments);
  }

  isInvalid(status) {
    return status === 422;
  }
}

十、最佳实践 #

10.1 使用约定 #

javascript
// 好的做法 - 遵循约定
// 后端返回
{ "posts": [{ "id": 1, "title": "..." }] }

// 前端自动处理

10.2 合理分层 #

javascript
// application.js - 全局配置
export default class ApplicationAdapter extends RESTAdapter {
  namespace = 'api/v1';
}

// post.js - 模型特定配置
export default class PostAdapter extends ApplicationAdapter {
  // 只覆盖需要的方法
}

10.3 错误处理 #

javascript
export default class ApplicationAdapter extends RESTAdapter {
  handleResponse(status, headers, payload) {
    if (status >= 400) {
      console.error('API Error:', payload);
    }
    return super.handleResponse(...arguments);
  }
}

十一、总结 #

适配器与序列化器要点:

组件 职责
Adapter 处理API请求
Serializer 处理数据格式
RESTAdapter RESTful API
JSONAPIAdapter JSON API规范

理解适配器和序列化器是对接各种后端API的关键。

最后更新:2026-03-28