Press "Enter" to skip to content

Backbone.js基础教程(二)——V组件

本文略译自《Backbone.js Basics: Models, Views, Collections and Templates》。

在上一篇教程里,我们展示了Backbone的Model组件的基本使用方法,使用了一个例子——冲浪板数据——展示如何用Model组件「结构化」业务数据,并且了为测试和完整,手动渲染出模型的数据。本文接着展示如何将「手动渲染UI的任务」结构化为V组件——使用Backbone的View组件API。

Views模板

在开始使用Backbon 的View组件API创建「视图对象」完成我们的「渲染任务」之前, 让我们先说一个基础的知识主题——视图模板(templates)。templates 能帮我们轻松地渲染 UI,因为免去用JS直接操作DOM(主是创建界面元素,v.render()里也要有操作DOM)。

模板、模板引擎是什么?为什么使用它们?

「模板」是指UI模板,GUI应用一定有V(交互界面)部分,而模板是UI界面的通用片断。前一文,我们看到手动渲染UI的例子(使用静态HTML模板,然后用JS更新DOM来完成渲染),手动渲染JS代码常常混杂HTML标签字符,这在UI较复杂时是不利于维护,也不利于重用,因为UI常常重复模式。将通用UI片断独立出来,既利于开发维护,又能提高复用。

「模板引擎」就是处理通用UI模板的JS库——将业务数据模型上的具体数据套上通用模板,来生成/渲染具体的UI。

虽然我们可以使用任意一种流行的模板引擎,为方简单,这里我们选用Underscore 本身自带的模板引擎来讲解。

如下是我们例子的一个视图模板,非常简单模板,用于展示单个冲浪板数据的,注意,模板只有表格单元(td)没有表格行(tr),后面来看到为什么没有表格行:

<script type="text/template" id="surfboard-template">
  <td><%= manufacturer %></td>
  <td><%= model %></td>
  <td><%= stock %></td>
</script>

View的形式理论定义

在具体创建视图对象之前,我们给出「视图」的一个理论。我们看文档的解释:

MVC中的,视图(V)是「界面交互接口」的逻辑组成,且背后关联着一个业务模型(M),当业务模型的数据变动时,被关联的视图能独立的响应变动,而不必重绘整个页面。

EM:V的状态有一部分一定和M有关联的,但不是全部状态,富交互UI有「交互上的状态数据」需要管理,这是V后来越来越独立的原因。

View的嵌套

现在想想我们的应用,我们的渲染任务是一个模型集合,和三个模型实例,根据上面的理论,那我们是需创建一个,还是两个相关的视图呢?我们如果想让它在将来的更可用,可能需更细致的设计。例如,在本教程中, 我们只对数据渲染了一次,但是,如果将来我们希望能够更新模型实例,也就是渲染多次,该怎么办?我们肯定希望能「对每个模型实例进行单独引用」(而不引整个模型集合),并将必要的控制器逻辑附加到该特定实例。考虑到这一点,我们的应用需要两个视图:

  • 主视图(或父视图),关联模型集合,可包含所有子视图,名为SurfboardsView
  • 子视图,关联单个模型实例,负责渲染一个模型实例,名为SurfboardView

父视图与冲浪板集合

好,我先创建一个 父视图SurfboardsView

var SurfboardsView = Backbone.View.extend({

});

这里SurfboardsView 有以下三个基本的对象成员:

逻辑上的V,与技术上的V

具体的「view对象」只是一个特殊的JS对象,那这个特殊的JS对象「应该」有怎样的内部数据和方法呢?由上面的V的逻辑定义,我们能够找到一些指导,界面、业务模型和用户交互处理,然而这,还不够技术。

  1. 第一个是 el ,是(或指向)一个DOM对象。逻辑上,V必有一可视部分/UI,而技术实现上,就是DOM对象;就像M组件,逻辑上有状态数据,技术实现上,有内存对象或磁盘文件。V必须有一个el对象,如果不给出,BB则根据tagName, className, idattributes 创建一个,如果都没有,用空div,详细看文档
  2. 第二,是initialize函数;这是个对象构造函数,V对象实例创建时会被调用;一般是作渲染前的准备;
  3. 第三,是 render函数;这是V的核心工作函数——渲染,函数模块理论里三元素中(依赖、内部和对外)的内部计算。这里就是用model加工 template的地方

el是V的数据成员

Just as Models wrap an attributes object and Collections wrap a models array,
Views wrap a DOM element inside a property called el .

 

三个el

view对象处理中有三个el:

view.el, view.$el, Backbone.View.extend({el:’#some_id’ })

第一个才是view的DOM对象,第二是el的jQuery封装,从名字可知;第三,只是初始化option的一个key。这个key的值可有三种:

A View’s el option can be defined in one of the following three ways:
• HTML ( new Backbone.View ({el: <div id=’foo’></div>}) )
• jQuery Selector ( new Backbone.View ({el: ‘#foo’}) )
• DOM element ( new Backbone.View ({el: document.getElementById(‘foo’)}) )

代码是这个样子:

var SurfboardsView = Backbone.View.extend({

  el: '#table-body',

  initialize: function() {

  },

  render: function() {

  }

});

initialize 函数

由于我们这里已经手动的准备好model实例集合,V在渲染前无什么事,所以可以直接调用V对象的渲染函数(这意味着创建时自动渲染,当然也可以调用者手动渲染),initialize 函数是这样的:

initialize: function() {
  this.render();
}

render 函数

render 函数是V组件的主角,它负责生成「V的界面」并将其挂接上DOM。

render是什么意思?

el option只制作了一个DOM对象,界面实际上有很多内容,例如给列表填充表项目

While Views can take an el option to define their initial element, it is rare for them
to leave this el option unchanged. For instance, a list View might take an <ul>
element as its el option but then fill this list with the <li> elements (possibly using
data from a Collection). In Backbone, this generation of inner HTML is done inside
the View’s render method.

由于我们现在这个V是一个父视图,关联着一个模型集合,它的渲染任务主要是:

  • 第一,清理DOM,因为可能不是第一次渲染;
  • 第二,渲染子视图;通过循环模型集合逐个渲染;
  • 第三,返回this;这是个良好的习惯,因为可用于链式调用;

代码是这样子:

render: function() {
  this.$el.html('');

  Surfboards.each(function(model) {
    // do something...
  });

  return this;
}

渲染子视图的代码没有给出,让我们仔细想一想,子视图该怎么渲染:

  • 第一,在每次循环迭代中,我们需要创建一个新的 Surfboard view 视图,并确保正确的数据被传入(即当前循环的模型实例);
  • 第二,从创建的视图中,我们需要调用该视图的 render 函数,并返回渲染结果;
  • 第三,将返回渲染好的 Surfboard View 追加到我们的父视图DOM对象上(表格正文元素)

代码大略如下(注意,我们可随时访问view的DOM对象成员,通过el):

render: function() {
  this.$el.html('');

  Surfboards.each(function(model) {
    var surfboard = new SurfboardView({
      model: model
    });

    this.$el.append(surfboard.render().el);
  }.bind(this));

  return this;
}

这里有两点需要补充,第一,每个SurfboardView 被添加一个model属性,指向当前循环的model实例,这样将model传给子视图;第二,调用each的高阶参数越出了当前的作用域,需绑定当前视图对象(this)作为其执行的父对象(不然里面的this.$e无效)。这个问题其实可以用箭头函数解决。

为什么给变量加一个$

A brief aside on $Variable names
When working with jQuery objects in Backbone (or even just in JavaScript, in
general), it may often be difficult to tell whether a given variable refers to a View
element or to its el element. In order to avoid confusion, many programmers
(including the authors of both Backbone and jQuery) preface any variable that
points to a jQuery object with the $ symbol, as follows:
var fooForm = new Backbone.View({id: ‘foo’, tagName: ‘form’});
var $fooForm = fooForm.$el;
While this practice is certainly not necessary to use Backbone, adopting it will likely
save you from confusion in the future.

——《BB.js essentials》

子视图与冲浪板

父视图编程好了,但是如果现在就执行代码,会没有任务效果,因为我们还没有编子视图 SurfboardView 。OK:

var SurfboardView = Backbone.View.extend({

});

有了一个架子,让我们来好好想想子视图 SurfboardView 的形式。子视图与父视图都是视图,第一个考虑当然是el了。我们的目标是 用SurfboardView 来渲染一个model实例,而我们的UI模板已经规划好用表格单元(td)来渲染model的属性,所以很明显 el是 tr 元素。而这次我们可以使用 tagName option key来创建el。

第二个考量的是render方式。我们设计了使用模板来渲染model,所以我们要配置template属性;

第三,我们在render方法,将model套上template,并挂接上DOM;

第四,返回this

代码最后是这个样子:

var SurfboardView = Backbone.View.extend({

  tagName: 'tr',

  template: _.template($('#surfboard-template').html()),

  render: function() {
    this.$el.html(this.template(this.model.attributes));
    return this;
  }

});

好了,子视图还是比较简单的,现在父视图调用它时有东西返回了。

现在我们已经基本完成了使用Backbone的View组件API派生了两个视图(嵌套的子父视图),最后我们在主程序中初始化一个父视图来触发渲染:

var app = new SurfboardsView;

OK,运行的效果如下:

小结

本节和上一节介绍了Backbone.js的M组件和V组件很基础的使用,下一节我们继续介绍 controller 组件, view-to-model 事件,和 view更新,将整个程序完整化。

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *