Ember关联关系 #
一、关联关系概述 #
Ember Data支持三种主要的关联关系:
| 关系类型 | 装饰器 | 示例 |
|---|---|---|
| 一对一 | belongsTo | 用户-资料 |
| 一对多 | hasMany | 文章-评论 |
| 多对多 | hasMany | 文章-标签 |
二、一对一关系 #
2.1 定义 #
javascript
// app/models/user.js
import Model, { attr, hasOne } from '@ember-data/model';
export default class UserModel extends Model {
@attr('string') name;
@hasOne('profile') profile;
}
javascript
// app/models/profile.js
import Model, { attr, belongsTo } from '@ember-data/model';
export default class ProfileModel extends Model {
@attr('string') bio;
@belongsTo('user') user;
}
2.2 使用 #
javascript
// 获取关联
const profile = await user.profile;
// 设置关联
user.profile = newProfile;
await user.save();
三、一对多关系 #
3.1 定义 #
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.2 使用 #
javascript
// 获取所有评论
const comments = await post.comments;
// 添加评论
const comment = this.store.createRecord('comment', {
body: 'Great post!',
post: post,
});
await comment.save();
// 遍历评论
for (const comment of comments) {
console.log(comment.body);
}
3.3 反向关联 #
javascript
// app/models/post.js
export default class PostModel extends Model {
@hasMany('comment', { inverse: 'post' }) comments;
}
// app/models/comment.js
export default class CommentModel extends Model {
@belongsTo('post', { inverse: 'comments' }) post;
}
四、多对多关系 #
4.1 定义 #
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;
}
4.2 使用 #
javascript
// 添加标签
post.tags.pushObject(tag);
await post.save();
// 移除标签
post.tags.removeObject(tag);
await post.save();
// 获取所有标签
const tags = await post.tags;
五、异步关联 #
5.1 异步加载 #
javascript
export default class PostModel extends Model {
@belongsTo('user', { async: true }) author;
@hasMany('comment', { async: true }) comments;
}
5.2 模板中使用 #
handlebars
{{! 使用await}}
<p>作者:{{await @post.author.name}}</p>
{{#each (await @post.comments) as |comment|}}
<p>{{comment.body}}</p>
{{/each}}
5.3 JavaScript中使用 #
javascript
const author = await post.author;
const comments = await post.comments;
六、关联选项 #
6.1 async #
javascript
// 异步加载(默认)
@belongsTo('user', { async: true }) author;
// 同步加载
@belongsTo('user', { async: false }) author;
6.2 inverse #
javascript
// 显式指定反向关联
@hasMany('comment', { inverse: 'post' }) comments;
// 禁用反向关联
@hasMany('comment', { inverse: null }) comments;
6.3 polymorphic #
javascript
// 多态关联
export default class CommentModel extends Model {
@attr('string') body;
@belongsTo('commentable', { polymorphic: true }) commentable;
}
javascript
// 被关联的模型
export default class PostModel extends Model {
@hasMany('comment', { as: 'commentable' }) comments;
}
export default class PhotoModel extends Model {
@hasMany('comment', { as: 'commentable' }) comments;
}
七、关联操作 #
7.1 获取关联 #
javascript
// 获取关联ID(不加载)
const authorId = post.belongsTo('author').id();
// 检查关联是否存在
const hasAuthor = post.belongsTo('author').value() !== null;
// 加载关联
const author = await post.author;
7.2 设置关联 #
javascript
// 设置belongsTo
post.author = newUser;
// 添加hasMany
post.comments.pushObject(newComment);
// 移除hasMany
post.comments.removeObject(comment);
// 清空hasMany
post.comments.clear();
7.3 关联状态 #
javascript
// 检查是否已加载
const isLoaded = post.hasMany('comments').value() !== null;
// 检查是否正在加载
const isLoading = post.hasMany('comments').isLoading();
// 检查是否有未保存更改
const hasChanged = post.hasMany('comments').hasBeenFetched();
八、预加载关联 #
8.1 include参数 #
javascript
// app/routes/posts/show.js
model(params) {
return this.store.findRecord('post', params.post_id, {
include: 'author,comments',
});
}
8.2 多层预加载 #
javascript
model(params) {
return this.store.findRecord('post', params.post_id, {
include: 'author,comments.author,comments.likes',
});
}
九、自引用关联 #
9.1 树形结构 #
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;
}
9.2 使用 #
javascript
// 获取父分类
const parent = await category.parent;
// 获取子分类
const children = await category.children;
// 设置父分类
category.parent = parentCategory;
await category.save();
十、关联最佳实践 #
10.1 合理使用异步关联 #
javascript
// 推荐 - 异步关联
@belongsTo('user', { async: true }) author;
// 仅在必要时使用同步关联
@belongsTo('user', { async: false }) author;
10.2 明确反向关联 #
javascript
// 好的做法 - 明确指定
@hasMany('comment', { inverse: 'post' }) comments;
@belongsTo('post', { inverse: 'comments' }) post;
// 避免 - 依赖隐式推断
@hasMany('comment') comments;
@belongsTo('post') post;
10.3 预加载关联 #
javascript
// 好的做法 - 预加载
model(params) {
return this.store.findRecord('post', params.post_id, {
include: 'author,comments',
});
}
// 避免 - N+1查询
model(params) {
return this.store.findRecord('post', params.post_id);
// 然后在模板中逐个加载关联
}
十一、总结 #
关联关系要点:
| 关系 | 装饰器 | 说明 |
|---|---|---|
| 一对一 | belongsTo | 单向或双向 |
| 一对多 | hasMany + belongsTo | 双向关联 |
| 多对多 | hasMany | 双向关联 |
| 多态 | polymorphic | 动态类型 |
合理设计关联关系是数据建模的关键。
最后更新:2026-03-28