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实例的属性值是否为nullundefined

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里,这样存在两个问题:

  1. 假设我们有很多个photos view,按照category分类,例如architecturalPhotosView, phonePhotosView等等,用的都是同一个template,但是View的实现细节不同。现在页面上已经渲染了好几个了。这个时候,$(".photos").css("padding-bottom", "20px") 这行代码就会将页面上所有 ".photos"都设一个20px的padding-bottom,而且所有的".some-icon"都会被绑定一个click事件。(特别注意所有Remove DOM的操作
  2. 而此时,phonePhtotsView的template还没有渲染到页面上,所以这个View内的".photos"和".some-icon"将不会被找到并执行上述代码。

解决办法:操作DOM的时候尽量使用view.$(selector),这样,作用域就会被限制在这个View里。而且,就算此时元素还没有渲染出来,可以绑定事件。

Backbone.Router

为应用的重要位置设置hash(#/page)链接,并能链接到指定的actionevent上。

注意: 页面加载期间,当应用已经创建了所有的路由,需要调用 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路由的知识还未理解透彻,所以这里就不在继续展开说了。 中文版文档

小结:

  1. BackboneJS大概的工作流程:

    浏览器访问一个url ->

    Router映射url到具体的event ->

    event中初始化相关的Model实例与Collection实例,并且从服务器中获取数据 ->

    用构造好的Model实例与Collection实例,render一个View并且渲染最终的Template

  2. 不适合的场景:

    使用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