在没有初始化方法的情况下定义子视图

Defining child views without an initialize method

我有大量视图(超过 50 个),它们都是从单个抽象基础视图扩展而来的,因此具有相似的布局和许多其他共同特征(事件处理程序、一些自定义方法和属性等) ).

我目前正在使用我的基本视图的 initialize 方法来定义布局,它涉及一个子视图,有点像下面这样:

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;
  },

  //...

});

但是,我发现对于许多扩展我的基本视图的视图,我需要重写 initialize 方法,该方法调用基本 class initialize(我也扩展了我的events 哈希也很频繁)。我不喜欢这样做,尤其是有这么多扩展基础的视图 class。

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.

所以在这个模型上,我应该能够为每个继承视图 class 提供一个 initialize。这对我来说很有意义;但是我怎样才能实现我继承视图所需的那种总体布局呢?在 constructor 方法中?

我不知道我想要的东西是否可以通过 Marionette 或 LayoutManager 之类的东西开箱即用,我已经简要地看过但从未使用过这两者,但是 我现在更喜欢在香草Backbone中这样做

在哪里实现基类的初始化class?

我喜欢这样做的方式是在构造函数中初始化基础classes,将initialize函数留空。它与 Backbone 提供的 initialize function is only a convenience 一样有意义,实际上只是构造函数的扩展。

事实上,Backbone经常这样做。我们经常覆盖的大多数(如果不是全部)函数和属性只是为了轻松覆盖。

下面是此类示例的快速列表:

  • 型号:initializedefaultsidAttributevalidateurlRootparse
  • Collection:initializeurlmodelmodelIdcomparatorparse
  • 视图:initializeattributeseltemplaterendereventsclassNameid,等等

这些函数留给用户来实现他自己的行为,并将有用的模式保留在基础 class 中,它们应该保持不变,基础 class 行为应该挂钩到其他功能(如果可能)。

有时,这会变得很困难,比如如果您想在 constructor 中调用 initialize 之前,但在设置元素和其他属性之后做某事。在这种情况下,覆盖 _ensureElement (line 1223) 可能是一个可能的 钩子。

_ensureElement: function() {
    // hook before the element is set correctly
    App.BaseView.__super__._ensureElement.apply(this, arguments);
    // hook just before the initialize is called.
}

这只是一个示例,几乎总有一种方法可以在基础 class 中获得您想要的内容,而无需覆盖 child 也将覆盖的函数。


简单基础class

如果基本视图用在一个小组件中并且被少数 child 视图覆盖,并且大部分由同一程序员使用,那么以下基本视图就足够了。使用 Underscore's _.defaults and _.extend 将 child class 属性与基础 class.

合并
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

然后是一个简单的child视图:

var ChildView = App.BaseView.extend({
    events: {
        // additional events
    },
    initialize: function(options) {
        // specific initialization
    },
    onRender: function() {
        // additional rendering
    }
});

高级基础class

如果您面临以下情况之一:

  • 覆盖render有问题,不喜欢onRender
  • events 属性(或任何其他 属性)是 child 或 parent 或两者中的函数
  • 使用基的程序员class不知道它的细节

然后可以将 child 属性实现包装到新函数中,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 在保持基本 class 行为的同时看起来完全正常。

var ChildView = App.BaseView.extend({
    events: function() {
        return {
            // additional events
        };
    },
    initialize: function(options) {
        // specific initialization
    },
    render: function() {
        // additional rendering
    }
});

潜在问题

如果您想完全覆盖基本 class 行为,这可能会成为一个问题,您需要在 child class,它可能会让人感到困惑。

假设您有一个特殊的 child 使用一次需要完全覆盖 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
    }
});

所以这不是非黑即白的,应该评估需要什么,什么会阻碍并选择正确的压倒性技术。