Ember模型定义 #

一、模型基础 #

1.1 创建模型 #

bash
ember generate model post
ember generate model user
ember generate model comment

1.2 基本模型 #

javascript
// app/models/post.js
import Model, { attr } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;
  @attr('string') body;
  @attr('date') createdAt;
}

二、属性定义 #

2.1 基本属性类型 #

javascript
import Model, { attr } from '@ember-data/model';

export default class UserModel extends Model {
  @attr('string') name;
  @attr('number') age;
  @attr('boolean') isActive;
  @attr('date') birthDate;
  @attr('array') tags;
  @attr('object') metadata;
}

2.2 默认值 #

javascript
export default class PostModel extends Model {
  @attr('string', { defaultValue: '' }) title;
  @attr('number', { defaultValue: 0 }) viewCount;
  @attr('boolean', { defaultValue: false }) isPublished;
  @attr('array', { defaultValue: () => [] }) tags;
  @attr('object', { defaultValue: () => ({}) }) metadata;
}

2.3 自定义转换 #

javascript
// app/transforms/point.js
import Transform from '@ember-data/serializer/transform';

export default class PointTransform extends Transform {
  deserialize(serialized) {
    return { x: serialized[0], y: serialized[1] };
  }

  serialize(deserialized) {
    return [deserialized.x, deserialized.y];
  }
}
javascript
// app/models/location.js
import Model, { attr } from '@ember-data/model';

export default class LocationModel extends Model {
  @attr('point') coordinates;
}

三、关联关系 #

3.1 belongsTo(一对一/多对一) #

javascript
// app/models/post.js
import Model, { attr, belongsTo } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;
  @belongsTo('user') author;
}
javascript
// app/models/user.js
import Model, { attr, hasMany } from '@ember-data/model';

export default class UserModel extends Model {
  @attr('string') name;
  @hasMany('post') posts;
}

3.2 hasMany(一对多) #

javascript
// app/models/post.js
import Model, { attr, hasMany } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;
  @hasMany('comment') comments;
}
javascript
// app/models/comment.js
import Model, { attr, belongsTo } from '@ember-data/model';

export default class CommentModel extends Model {
  @attr('string') body;
  @belongsTo('post') post;
}

3.3 多对多 #

javascript
// app/models/post.js
import Model, { attr, hasMany } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;
  @hasMany('tag') tags;
}
javascript
// app/models/tag.js
import Model, { attr, hasMany } from '@ember-data/model';

export default class TagModel extends Model {
  @attr('string') name;
  @hasMany('post') posts;
}

3.4 关联选项 #

javascript
export default class PostModel extends Model {
  // 异步加载
  @belongsTo('user', { async: true }) author;

  // 反向关联
  @hasMany('comment', { inverse: 'post' }) comments;

  // 多态关联
  @belongsTo('commentable', { polymorphic: true }) commentable;
}

3.5 自引用关联 #

javascript
// app/models/category.js
import Model, { attr, belongsTo, hasMany } from '@ember-data/model';

export default class CategoryModel extends Model {
  @attr('string') name;
  @belongsTo('category', { inverse: 'children' }) parent;
  @hasMany('category', { inverse: 'parent' }) children;
}

四、计算属性 #

4.1 基本计算属性 #

javascript
import Model, { attr, belongsTo } from '@ember-data/model';

export default class UserModel extends Model {
  @attr('string') firstName;
  @attr('string') lastName;

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  get initials() {
    return `${this.firstName[0]}${this.lastName[0]}`.toUpperCase();
  }
}

4.2 基于关联的计算属性 #

javascript
import Model, { attr, hasMany } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;
  @hasMany('comment') comments;

  get commentCount() {
    return this.comments.length;
  }

  get hasComments() {
    return this.commentCount > 0;
  }
}

4.3 异步计算属性 #

javascript
import Model, { attr, hasMany } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;
  @hasMany('comment') comments;

  get asyncComments() {
    return this.comments;
  }
}
handlebars
{{! 使用async/await}}
{{#each (await @post.asyncComments) as |comment|}}
  <p>{{comment.body}}</p>
{{/each}}

五、模型方法 #

5.1 自定义方法 #

javascript
import Model, { attr } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;
  @attr('string') body;
  @attr('date') createdAt;

  get excerpt() {
    return this.body?.substring(0, 100) + '...';
  }

  isOlderThan(days) {
    const diff = Date.now() - this.createdAt.getTime();
    return diff > days * 24 * 60 * 60 * 1000;
  }
}

5.2 静态方法 #

javascript
import Model, { attr } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;
  @attr('string') status;

  static findPublished(store) {
    return store.query('post', { status: 'published' });
  }
}

六、模型验证 #

6.1 内置验证 #

javascript
import Model, { attr } from '@ember-data/model';
import { validates } from 'ember-cp-validations';

export default class UserModel extends Model {
  @attr('string') name;
  @attr('string') email;

  get validations() {
    return {
      name: {
        presence: true,
        length: { minimum: 2, maximum: 50 },
      },
      email: {
        presence: true,
        format: { type: 'email' },
      },
    };
  }
}

6.2 自定义验证 #

javascript
import Model, { attr } from '@ember-data/model';

export default class UserModel extends Model {
  @attr('string') password;
  @attr('string') passwordConfirmation;

  get isValid() {
    return this.password === this.passwordConfirmation;
  }

  get errors() {
    const errors = [];
    if (this.password !== this.passwordConfirmation) {
      errors.push({ field: 'passwordConfirmation', message: '密码不匹配' });
    }
    return errors;
  }
}

七、模型钩子 #

7.1 ready钩子 #

javascript
import Model, { attr } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;

  ready() {
    console.log('模型已加载');
  }
}

7.2 didCreate钩子 #

javascript
import Model, { attr } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;

  didCreate() {
    console.log('记录已创建');
  }
}

7.3 didUpdate钩子 #

javascript
import Model, { attr } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;

  didUpdate() {
    console.log('记录已更新');
  }
}

7.4 didDelete钩子 #

javascript
import Model, { attr } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;

  didDelete() {
    console.log('记录已删除');
  }
}

八、模型继承 #

8.1 基础模型 #

javascript
// app/models/base/content.js
import Model, { attr } from '@ember-data/model';

export default class ContentModel extends Model {
  @attr('string') title;
  @attr('date') createdAt;
  @attr('date') updatedAt;
}

8.2 继承模型 #

javascript
// app/models/post.js
import ContentModel from './base/content';
import { attr, hasMany } from '@ember-data/model';

export default class PostModel extends ContentModel {
  @attr('string') body;
  @hasMany('comment') comments;
}

九、最佳实践 #

9.1 命名约定 #

javascript
// 好的命名
@attr('string') firstName;
@attr('string') lastName;
@belongsTo('user') author;

// 避免
@attr('string') fn;
@attr('string') ln;
@belongsTo('u') a;

9.2 合理的关联 #

javascript
// 好的做法 - 明确的关联
@belongsTo('user', { async: true }) author;
@hasMany('comment', { inverse: 'post' }) comments;

// 避免 - 过度关联
@belongsTo('user') author;
@belongsTo('user') editor;
@belongsTo('user') reviewer;
// ...

9.3 文档化 #

javascript
/**
 * Post模型
 * 
 * @property {string} title - 文章标题
 * @property {string} body - 文章内容
 * @property {Date} createdAt - 创建时间
 * @property {User} author - 作者
 * @property {Comment[]} comments - 评论列表
 */
export default class PostModel extends Model {
  @attr('string') title;
  @attr('string') body;
  @attr('date') createdAt;
  @belongsTo('user') author;
  @hasMany('comment') comments;
}

十、总结 #

模型定义要点:

概念 说明
@attr 定义属性
@belongsTo 定义一对一关联
@hasMany 定义一对多关联
getter 计算属性
钩子 生命周期回调

良好的模型设计是应用数据层的基础。

最后更新:2026-03-28