我有大量 View (超过 50 个),它们都从单个抽象基本 View 扩展而来,因此具有相似的布局和许多其他共同功能(事件处理程序、一些自定义方法和属性等)。
我目前正在使用基本 View 的 initialize
方法来定义布局,其中涉及 subview ,有点类似于以下内容:
App.BaseView = Backbone.View.extend({
//...
initialize: function() {
this.subView = new App.SubView();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.subView.$el = this.$('#subview-container');
this.subView.render();
return this;
},
//...
});
但是,我发现,对于扩展我的基本 View 的许多 View ,我需要重写 initialize
方法,该方法调用基类 initialize
(我还扩展了我的事件
也经常散列)。我不喜欢这样做,尤其是有这么多扩展基类的 View 。
在 this post来自 Backbone Github 存储库问题的 Derick Bailey 说:
I'm also not a fan of requiring extending classes to call super methods for something like
initialize
. This method is so basic and so fundamental to any object that extends from a Backbone construct. It should never be implemented by a base type - a type that is built with the explicit intent of never being instantiated directly, but always extended from.
因此,在这个模型上,我应该能够为每个继承 View 类提供一个可用的initialize
。这对我来说完全有道理;但是我怎样才能实现继承 View 所需的总体布局呢?在构造函数
方法中?
我不知道我想要的东西是否可以通过像 Marionette 或 LayoutManager 这样的东西开箱即用,这两个我都简单地看过但从未使用过,但是我更喜欢目前正在 Vanilla Backbone 中执行此操作。
最佳答案
在哪里实现基类的初始化?
我喜欢的方式是在构造函数中初始化基类,将initialize
函数留空。这是有道理的 initialize
function is only a convenience由 Backbone 提供,实际上只是构造函数的扩展。
事实上,Backbone 经常这样做。我们经常重写的大多数(如果不是全部)函数和属性只是为了轻松重写。
以下是此类示例的快速列表:
- 模型:
初始化
、默认值
、idAttribute
、验证
、urlRoot
、解析
等 - 集合:
初始化
、url
、模型
、modelId
、比较器
、解析
等 - View :
初始化
、属性
、el
、模板
、渲染
、事件
、className
、id
等
这些函数留给用户实现自己的行为,并在基类中保留有用的模式,它们应该保持不变,并且如果可能的话,基类行为应该挂接到其他函数中。
有时,这可能会变得很困难,例如,如果您想在构造函数
中调用initialize
之前、但在设置元素和其他属性之后执行某些操作。在这种情况下,覆盖 _ensureElement
( line 1223 )可能是一个可能的钩子(Hook)。
_ensureElement: function() {
// hook before the element is set correctly
App.BaseView.__super__._ensureElement.apply(this, arguments);
// hook just before the initialize is called.
}
这只是一个示例,几乎总有一种方法可以在基类中获得您想要的内容,而无需重写子级也将重写的函数。
简单基类
如果基本 View 用于小型组件并被少数 subview 覆盖,并且大部分由同一程序员使用,则以下基本 View 就足够了。使用Underscore's _.defaults
和 _.extend
将子类属性与基类合并。
App.BaseView = Backbone.View.extend({
events: {
// default events
},
constructor: function(opt) {
var proto = App.BaseView.prototype;
// extend child class events with the default if not already defined
this.events = _.defaults({}, this.events, proto.events);
// Base class specifics
this.subView = new App.SubView();
// then Backbone's default behavior, which includes calling initialize.
Backbone.View.apply(this, arguments);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
// don't set `$el` directly, use `setElement`
this.subView
.setElement(this.$('#subview-container'))
.render();
// make it easy for child view to add their custom rendering.
this.onRender();
return this;
},
onRender: _.noop,
});
不要直接设置$el
,使用setElement
相反。
然后是一个简单的 subview :
var ChildView = App.BaseView.extend({
events: {
// additional events
},
initialize: function(options) {
// specific initialization
},
onRender: function() {
// additional rendering
}
});
高级基类
如果您遇到以下情况之一:
- 重写
render
是有问题的,不喜欢onRender
events
属性(或任何其他属性)是子级或父级或两者中的函数- 使用基类的程序员不知道其细节
然后就可以将子属性实现包装到新函数中并 Underscore's _.wrap
函数就是这样做的。
App.BaseView = Backbone.View.extend({
// works with object literal or function returning an object.
events: function() {
return { /* base events */ };
},
// wrapping function
_events: function(events, parent) {
var parentEvents = App.BaseView.prototype.events;
if (_.isFunction(parentEvents)) parentEvents = parentEvents.call(this);
if (parent) return parentEvents; // useful if you want the parent events only
if (_.isFunction(events)) events = events.call(this);
return _.extend({}, parentEvents, events);
},
constructor: function(opt) {
var proto = App.BaseView.prototype;
// wrap the child properties into the parent, so they are always available.
this.events = _.wrap(this.events, this._events);
this.render = _.wrap(this.render, proto.render);
// Base class specifics
this.subView = new App.SubView();
// then Backbone's default behavior, which includes calling initialize.
Backbone.View.apply(this, arguments);
},
/**
* render now serves as both a wrapping function and the base render
*/
render: function(childRender) {
// base class implementation
// ....
// then call the child render
if (childRender) childRender.call(this);
return this
},
});
所以 child 看起来完全正常,同时保持基类行为。
var ChildView = App.BaseView.extend({
events: function() {
return {
// additional events
};
},
initialize: function(options) {
// specific initialization
},
render: function() {
// additional rendering
}
});
潜在问题
如果您想完全覆盖基类行为,这可能会成为一个问题,您需要在子类中手动取消一些基类行为,这可能会造成困惑。
假设您有一个特殊的子元素,需要完全覆盖 render
一次:
var SpecialChildView = App.BaseView.extend({
initialize: function(options) {
// Cancel the base class wrapping by putting
// the this class's prototype render back.
this.render = SpecialChildView.prototype.render;
// specific initialization
},
render: function() {
// new rendering
}
});
因此,这不是黑白分明的,人们应该评估需要什么以及会出现什么问题,然后选择正确的首要技术。
关于javascript - 定义没有初始化方法的 subview ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40982422/