javascript - 定义没有初始化方法的 subview

标签 javascript backbone.js view subview

我有大量 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模板渲染事件classNameid

这些函数留给用户实现自己的行为,并在基类中保留有用的模式,它们应该保持不变,并且如果可能的话,基类行为应该挂接到其他函数中。

有时,这可能会变得很困难,例如,如果您想在构造函数中调用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/

相关文章:

backbone.js - Backbone.js-嵌套 View 应该维护彼此的引用吗?

backbone.js - 直接从模型重写 fetch() 和 save() 是个好习惯吗?

php - 请求无法识别上传图片

android - 在 DialogFragment 中单击 "Cancel"按钮后如何启动软键盘?

Android TextView 完全居中文本

javascript - Materialise CSS Collapsible 不展开

javascript - 更改 Angular JS 中按钮单击的下拉选项

javascript - 如何永久设置高亮持续时间

JavaScript 跳过 document.write 转到 window.prompt?

javascript - 了解 Backbone.js REST 调用