BackboneJS开发总结
-
June 21, 2016 07:38
-
Posted by dino
-
0 comments
BackboneJS - A MVC Javascript Structure
Background: 一直致力于尝试各种技术的豆厂,在某个客户项目中使用了Backbone.js作为前端JS框架。
Technology Stack:Rails, jQuery, Coffeescript, Backbone.js
What is Backbone.js
Backbone.js is a MVC Javascript Structure
Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.
如果你熟悉Rails或者熟悉MVC,我敢保证你可以很快地掌握Backbone.js
Backbone.Model
Models是任何MVC框架的核心,包括数据交互及与其相关的大量逻辑:字段(属性),逻辑,方法,验证,业务模型关联关系,权限扩展等等。
1. Extend:
我们可以这样来建立一个model实例:
var Photo = Backbone.Model.extend({
default: function(){
return {title: "Canton Tower",
author: "Dino Chan",
category: "Architectural Photographing"};
}
});
这个时候,我们就拥有了一个model实例:Photo,并且这个实例内定义了一个实例方法default。
photo_1 = new Photo({})
photo_1.default() #=> Object {title: "Canton Tower", ...}
当然你还可以定义initialize来做一些初始化的工作,再来看看Rails的Model:
class Photo
def default
{title: "Canton Tower",
author: "Dino Chan",
category: "Architectural Photographing"}
end
end
photo_1 = Photo.new
photo_1.default #=> {title: "Canton Tower", ...}
简直一模一样啊是不是!
2. Set
我们为一个model实例添加attributes(属性)有以下方法:
photo_1 = new Photo({file_url: "/uploaded/photos/:id"})
或者
photo_1 = new Photo()
photo_1.set({file_url: "/uploaded/photos/:id"})
以上两个是较为常见的两种方法。
其他一些与set
有关的方法:
- unset: 删除某个attribute
- clear: 从model实例中删除所有属性, 包括
id
属性。
3. Get
当model实例有了attributes之后我们该怎么访问呢?这时候就需要用到get
方法了:
photo_1.get('author') #=> "Dino Chan"
再也不能更简单啦~
Backbone.js还提供了has
方法来判断model实例的属性值是否为null
或undefined
:
photo_1.has('author') #=> true
4. Sync
通过sync
可以同步model实例与服务器中model实例的数据,model.sync(method, model, [options])
。但是首先要通过model.url = "model_url"
或者在初始化model实例的时候来定义model实例在服务器上的url。
常见有如下sync method:
- ftech: 从服务器获取数据并更新当前model实例,在初始化一个空model实例的时候非常有用
photo_1.fetch()
- save:提交model实例的attributes到服务器并保存
photo_1.save({author: "Dino2 Chan"})
- destroy:通过提交请求删除服务器中相应的数据
photo_1.destroy()
Backbone.Collection
Backbone.Collection是多个model实例的有序集合
1. Extend
通过extend
可以创建一个Backbone.Collection类,常见方式:
var Photos = Backbone.Collection.extend({
model: Photo
});
其中model
定义了这个Collection内的model实例是Photo
,也就是说这是一个Photo
的模型的集合。
2. add
add
可以将一个或者一组model实例塞进Collection实例中:
photos = new Photos()
photos.add(photo_1, photo_2)
photos.models #=> [photo_1, photo_2]
同样可以添加model实例到collection实例中的方法还有:
-
push
,在collection的尾部添加一个model实例 -
unshift
,在collection的头部添加一个model实例
3. remove
remove
可以从collection实例中删除一个或者一组model
实例:
photos.remove(model/models)
同样可以从collection实例中删除model实例的方法还有:
-
pop
,删除并返回collection中的最后一个model实例 -
shift
,删除并返回collection中的第一个model实例
4. create
方便快捷的在collection实例内创建追加
一个model实例
photos.create({title: photo_3, ...})
5. fetch
通过设置好的url属性向对应url(服务器)请求数据并更新collection实例
photos.fetch()
Backbone.View
我觉得backbone的view包括两部分,一部分是负责页面架构的Template
,包括HTML和CSS,当然在rails项目里css可以单独放在assets。
第二部分就是Backbone.View了,可以说Backbone.View是我们的接口、数据和Template的中介。我们通过在这里约定(定义)好Backbone.Model、Backbone.Collection等与具体Template、DOM的关联关系,这样在model实例或者collection实例更新的时候,对应的Template、DOM都可以单独、自动地更新。不需要重新渲染页面,也不需要重新手动处理数据、查找DOM、更新Template了
下面是一个简单的Backbone.View的例子:
var PhotosView = Backbone.View.extend({
tagName: "ul",
className: "photos-list",
events: {
"click .photo": "showPhotoInfo",
},
initialize: function() {
this.listenTo(this.model, "change", this.render);
},
render: function() {
...
}
});
创建View的时候,通常我们需要重新加载render
函数,声明events
,以及通过 tagName
, className
, 或 id
为View指定在整个HTML中的根节点。这样整个View将会被渲染到该节点中,例如例子中的View会被渲染成<ul class="photos-lists">
这样的DOM节点。
注意:(个人觉得这是使用Backbone最需要注意的地方)
我们往往习惯使用这样的方式去操作DOM元素:
# coffee
someFunction ->
$(".photos").css("padding-bottom", "20px")
$(".some-icon").click ->
$(".photos .photo-1").hide()
render ->
# phonePhtotsView
someFunction()
但是在Backbone.View里,这样存在两个问题:
- 假设我们有很多个
photos view
,按照category
分类,例如architecturalPhotosView
,phonePhotosView
等等,用的都是同一个template,但是View的实现细节不同。现在页面上已经渲染了好几个了。这个时候,$(".photos").css("padding-bottom", "20px")
这行代码就会将页面上所有 ".photos"都设一个20px的padding-bottom,而且所有的".some-icon"都会被绑定一个click事件。(特别注意所有Remove DOM的操作) - 而此时,
phonePhtotsView
的template还没有渲染到页面上,所以这个View内的".photos"和".some-icon"将不会被找到并执行上述代码。
解决办法:操作DOM的时候尽量使用view.$(selector)
,这样,作用域就会被限制在这个View里。而且,就算此时元素还没有渲染出来,可以绑定事件。
Backbone.Router
为应用的重要位置设置hash(#/page)
链接,并能链接到指定的action
和event
上。
注意: 页面加载期间,当应用已经创建了所有的路由,需要调用 Backbone.history.start(),或 Backbone.history.start({pushState: true}) 来确保驱动初始化 URL 的路由。
下面是一个例子:
var PhotoRouter = Backbone.Router.extend({
routes: {
"photos": "photos", // #photos
"photos/:id": "photo", // #photo
},
photos: function() {
...
},
photo: function() {
...
},
});
routes
将URLs映射到路由实例的方法上, 例如"#photos/1"
会将1
作为:params
传到路由对应的action(photo)中去了。
注意: 定义routes的时候应该避免用到\
。
由于本人对BackboneJS路由的知识还未理解透彻,所以这里就不在继续展开说了。 中文版文档
小结:
-
BackboneJS大概的工作流程:
浏览器访问一个url ->
Router
映射url到具体的event
->在
event
中初始化相关的Model
实例与Collection
实例,并且从服务器中获取数据 ->用构造好的Model实例与Collection实例,
render
一个View并且渲染最终的Template -
不适合的场景:
使用BackboneJS,会将一个页面切分成大大小小一个个的template。有一个比较大的问题就是每一个template之间的交互,下面是具体需求:
Tutorials of Application(应用使用指南),要求为应用的核心、主要功能做一个Tutorial,也就是客户操作引导之类的东西。
在这个任务中我使用了
introJs
,刚开始想着不就是一步步往下走嘛,So easy啦!绑定一堆元素,把tooltip复制一下就搞掂啦!殊不知!!!我还是太年轻啊!!!
tutorials start -> highlight A -> click A -> B View refresh -> highlight B(此时初始化好的
introJs()
并没有绑定新的B) -> ....具体就是某一个tutorial涉及多个View,并且在操作过程中,大多数View会重新render刷新。导致早早初始化好的
introJs()
所绑定的元素消失,新的元素又没有绑定上。解决办法很简单,也很麻烦~ 就是基本上要针对tutorial的每一个
step
写一个callback。在callback里基本都是在做这两件事:停止并退出当前tutorial,等待新的DOM记载完再重新初始化tutorial。=_= 有时候也需要到个个View里面去判断当前Route是否是tutorial,然后重复上面两件事。
所以在Backbone里,如果是基于数据的交互,实现起来将会非常舒服!而那些一次初始化,绑定大量DOM,涉及到多个template的交互,会很麻烦!
以上。
谢谢。
Comments