最近比较忙,换了新工作还要学习很多全新的技术栈,并给自己找了很多借口来不去坚持写博客。常常具有讽刺意味的是,更多剩下的时间并没有利用而更多的是白白浪费,也许这就是青春吧,挥霍吧,这不是我想要的,既然这样,我还要继续写下去,坚持把博客做好,争取进前100博客,在此谨记。
2015年5月7日深夜,于电脑旁。
JS前端框架之Ember.js系列
Ember-data中所有的模型都继承自DS.Model,模型内部的属性是通过DS.attr来声明的。
var attr = DS.attr;App.Person = DS.Model.extend({ firstName: attr(), lastName: attr(), birthday: attr()});
此外,DS.Model中还可以添加“计算属性”:
App.Person = Ember.Object.extend({ // these will be supplied by `create` firstName: null, lastName: null, fullName: function() { return this.get('firstName') + ' ' + this.get('lastName'); }.PRoperty('firstName', 'lastName')}); var ironMan = App.Person.create({ firstName: "Tony", lastName: "Stark"}); ironMan.get('fullName') // "Tony Stark"
特别的,还可以显式指定属性返回值类型:
App.Person = DS.Model.extend({ birthday: DS.attr('date')});
默认情况下,REST 适配器支持的属性类型有string
,number
,boolean
和date
。 传统的适配器会提供额外的属性类型,并支持你注册自定义的属性类型。 详情请查看documentation section on the REST Adapter。
请注意:date类型参考ISO8601,例如:2014-05-27T12:54:01
目前,Ember-data只支持一种默认的选项:
export default DS.Model.extend({ username: DS.attr('string'), email: DS.attr('string'), verified: DS.attr('boolean', {defaultValue: false}), createdAt: DS.attr('string', { defaultValue: function() { return new Date(); } }) });
Ember Data 包括了几个内置的关联类型,以帮助你确定你的模型如何相互关联的。
理解关联模型
使用DS.belongsTo
在两个模型间声明一对一的关系。
App.User = DS.Model.extend({ profile: DS.belongsTo('profile')}); App.Profile = DS.Model.extend({ user: DS.belongsTo('user')});
使用DS.belongsTo
结合DS.hasMany
来声明两个模型间的一对多关系,示例如下:
App.Post = DS.Model.extend({ comments: DS.hasMany('comment')}); App.Comment = DS.Model.extend({ post: DS.belongsTo('post')});
使用DS.hasMany
来声明两个模型间的多对多关系。
App.Post = DS.Model.extend({ tags: DS.hasMany('tag')}); App.Tag = DS.Model.extend({ posts: DS.hasMany('post')});
Ember Data会尽最大努力去自动发现关联关系的映射关系。在上例的一对多的情况下,修改了comments
会自动更新post
,应为这是唯一的一个关联模型。
但是,有时候对同一个类型有多个belongsTo
/hasMany
关联关系。这时可以通过指定在反向端使用DS.hasMany
的inverse
选项来指定其关联的模型:
var belongsTo = DS.belongsTo, hasMany = DS.hasMany; App.Comment = DS.Model.extend({ onePost: belongsTo("post"), twoPost: belongsTo("post"), redPost: belongsTo("post"), bluePost: belongsTo("post")}); App.Post = DS.Model.extend({ comments: hasMany('comment', { inverse: 'redPost' })});
当然也可以在belongsTo
一侧指定,它将按照预期那样工作。
通过调用仓库的createRecord
方法,可以创建记录:
store.createRecord('post', { title: 'Rails is Omakase', body: 'Lorem ipsum'});
尽管createRecord
的使用已经非常直接,但是还需要注意一点,就是目前还不支持将一个承诺赋值给一个关联。
例如,如果希望给文章设置author
属性,如果指定ID的user
并没有加载到仓库中的话,下面的代码将不会正常工作。
var store = this.store; store.createRecord('post', { title: 'Rails is Omakase', body: 'Lorem ipsum', author: store.find('user', 1)});
不过在承诺履行时可以非常方便的进行关联关系的设置:
var store = this.store; var post = store.createRecord('post', { title: 'Rails is Omakase', body: 'Lorem ipsum'}); store.find('user', 1).then(function(user) { post.set('author', user);});
删除记录与创建记录一样简单。只需要调用DS.Model
实例的deleteRecord()
方法即可。这将会吧记录标记为isDeleted
,并且不在store
的all()
查询中返回。删除操作之后会通过使用save()
来进行持久化。此外,也可以使用destroyRecord
来将删除和持久化一次完成。
var post = store.find('post', 1);post.deleteRecord();post.get('isDeleted'); // => truepost.save(); // => DELETE to /posts/1 // ORvar post = store.find('post', 2);post.destroyRecord(); // => DELETE to /posts/2
上节讲到仓库的概念,仓库类似于数据的缓冲池,当应用程序向仓库中查找不存在数据时,仓库则向数据源发起数据请求,如果存在的话,直接返回仓库中的数据。
为了将记录推入仓库,需要调用仓库的push()
方法。
var attr = DS.attr; App.Album = DS.Model.extend({ title: attr(), artist: attr(), songCount: attr()}); App.applicationRoute = Ember.Route.extend({ model: function() { this.store.push('album', { id: 1, title: "Fewer Moving Parts", artist: "David Bazan", songCount: 10 }); this.store.push('album', { id: 2, title: "Calgary b/w I Can't Make You Love Me/Nick Of Time", artist: "Bon Iver", songCount: 2 }); }});
Ember Data中的记录都基于实例来进行持久化。调用DS.Model
实例的save()
会触发一个网络请求,来进行记录的持久化。
var post = store.createRecord('post', { title: 'Rails is Omakase', body: 'Lorem ipsum'});post.save(); // => POST to '/posts'
save()
会返回一个承诺,这使得可以非常容易的来处理保存成功和失败的场景。下面是一个通用的模式:
var post = store.createRecord('post', { title: 'Rails is Omakase', body: 'Lorem ipsum'}); var self = this; function transitionToPost(post) { self.transitionToRoute('posts.show', post);} function failure(reason) { // handle the error} post.save().then(transitionToPost).catch(failure); // => POST to '/posts'// => transitioning to posts.show route
对于失败的网络请求,承诺也可以方便的来处理:
var post = store.createRecord('post', { title: 'Rails is Omakase', body: 'Lorem ipsum'}); var onSuccess = function(post) { this.transitionToRoute('posts.show', post);}; var onFail = function(post) { // deal with the failure here}; post.save().then(onSuccess, onFail); // => POST to '/posts'// => transitioning to posts.show route
更多关于承诺的内容请参看这里,下面是一个示例展示了如何在重试失败的持久化操作:
function retry(callback, nTimes) { // if the promise fails return callback().fail(function(reason) { // if we haven't hit the retry limit if (nTimes-- > 0) { // retry again with the result of calling the retry callback // and the new retry limit return retry(callback, nTimes); } // otherwise, if we hit the retry limit, rethrow the error throw reason; });} // try to save the post up to 5 timesretry(function() { return post.save();}, 5);
Ember Data仓库提供了一个非常简单的查询一类记录的接口,该接口就是store
对象的find
方法。在内部,store
根据传入的参数使用find
、findAll
和findQuery
完成查询。store.find()
的第一个参数是记录的类型,第二个可选参数确定查询是获取所有记录,还是一条记录,还是特定的记录。
var posts = this.store.find('post');
如果希望获取已经加载到仓库中的记录的列表,而不希望通过一个网络请求去获取,可以使用all
方法:
var posts = this.store.all('post'); // => no network request
find
会返回一个将使用DS.RecordArray
来履行的DS.PromiseArray
,而all
直接返回DS.RecordArray
。
需要重点注意的一点是DS.RecordArray
不是一个javascript数组。它是一个实现了Ember.Enumerable
的对象。这一点非常重要,因为例如希望通过索引获取记录,那么[]
将无法工作,需要使用objectAt(index)
来获取。
如果调用store.find()
方法时,第二个参数是一个数字或者字符串,Ember Data将尝试获取对应ID的记录。find()
方法将返回一个用请求的记录来履行的承诺。
var aSinglePost = this.store.find('post', 1); // => GET /posts/1
如果传递给find
方法的第二个参数是一个对象,Ember Data会发送一个使用该对象来序列化出来的查询参数的GET
请求。这是方法返回与不加第二个参数时候一样的DS.PromiseArray
。
例如,可以查询名为Peter
的person
模型的所有记录:
var peters = this.store.find('person', { name: "Peter" }); // => GET to /persons?name='Peter'
如同在指定路由的模型一节中讨论的一样,路由是负责告诉模板将渲染哪个模型。
Ember.Route
的model
钩子支持立即可用的异步值。如果model
钩子返回一个承诺,路由将等待承诺履行条件满足时才渲染模板。
这使得使用Ember Data的异步数据来编写应用变得容易。只需要通过model
钩子返回请求的记录,交个Ember来处理是否需要一个网络请求。
App.Router.map(function() { this.resource('posts'); this.resource('post', { path: ':post_id' });}); App.PostsRoute = Ember.Route.extend({ model: function() { return this.store.find('post'); }}); App.PostRoute = Ember.Route.extend({ model: function(params) { return this.store.find('post', params.post_id); }}
新闻热点
疑难解答