Chế độ xem xương sống: Kế thừa và mở rộng các sự kiện từ cha mẹ


115

Tài liệu của Backbone tuyên bố:

Thuộc tính sự kiện cũng có thể được định nghĩa là một hàm trả về hàm băm sự kiện, để giúp xác định chương trình các sự kiện của bạn dễ dàng hơn, cũng như kế thừa chúng từ chế độ xem chính.

Làm thế nào để bạn kế thừa các sự kiện xem của cha mẹ và mở rộng chúng?

Chế độ xem phụ huynh

var ParentView = Backbone.View.extend({
   events: {
      'click': 'onclick'
   }
});

Chế độ xem trẻ em

var ChildView = ParentView.extend({
   events: function(){
      ????
   }
});

Câu trả lời:


189

Một cách là:

var ChildView = ParentView.extend({
   events: function(){
      return _.extend({},ParentView.prototype.events,{
          'click' : 'onclickChild'
      });
   }
});

Một cách khác là:

var ParentView = Backbone.View.extend({
   originalEvents: {
      'click': 'onclick'
   },
   //Override this event hash in
   //a child view
   additionalEvents: {
   },
   events : function() {
      return _.extend({},this.originalEvents,this.additionalEvents);
   }
});

var ChildView = ParentView.extend({
   additionalEvents: {
      'click' : ' onclickChild'
   }
});

Để kiểm tra xem Sự kiện là chức năng hay đối tượng

var ChildView = ParentView.extend({
   events: function(){
      var parentEvents = ParentView.prototype.events;
      if(_.isFunction(parentEvents)){
          parentEvents = parentEvents();
      }
      return _.extend({},parentEvents,{
          'click' : 'onclickChild'
      });
   }
});

Điều đó thật tuyệt ... Có lẽ bạn có thể cập nhật điều này để cho thấy cách bạn sẽ kế thừa từ ChildView (kiểm tra xem các sự kiện nguyên mẫu là một chức năng hoặc đối tượng) ... Hoặc có thể tôi đang xem xét toàn bộ nội dung thừa kế này.
brent

@brent Chắc chắn, chỉ cần thêm trường hợp thứ ba
Soldier.moth

14
Nếu tôi không nhầm, bạn có thể sử dụng parentEvents = _.result(ParentView.prototype, 'events');thay vì kiểm tra 'thủ công' nếu đó eventslà một chức năng.
Koen.

3
@Koen. +1 để đề cập đến chức năng tiện ích gạch dưới _.result, điều mà tôi chưa nhận thấy trước đây. Đối với bất kỳ ai quan tâm, đây là một jsfiddle với một loạt các biến thể về chủ đề này: jsfiddle
EleventyOne

1
Chỉ cần ném hai xu của tôi vào đây, tôi tin rằng lựa chọn thứ hai là giải pháp tốt nhất. Tôi nói điều này bởi vì thực tế là nó là phương pháp duy nhất được gói gọn. bối cảnh duy nhất được sử dụng là thisso với việc phải gọi lớp cha bằng tên dụ. Cảm ơn bạn rất nhiều cho việc này.
jessie james jackson taylor

79

Câu trả lời của lính.moth là một câu hỏi hay. Đơn giản hóa hơn nữa bạn chỉ có thể làm như sau

var ChildView = ParentView.extend({
   initialize: function(){
       _.extend(this.events, ParentView.prototype.events);
   }
});

Sau đó, chỉ cần xác định các sự kiện của bạn trong một trong hai lớp theo cách điển hình.


8
Cuộc gọi tốt, mặc dù bạn có thể muốn trao đổi this.events& ParentView.prototype.eventsnếu không, cả hai đều xác định trình xử lý trong cùng một sự kiện, trình xử lý của Cha mẹ sẽ ghi đè lên Con.
Soldier.moth

1
@ Soldier.moth, được rồi, tôi đã chỉnh sửa nó thành{},ParentView.prototype.events,this.events
AJP

1
Rõ ràng điều này hoạt động, nhưng như tôi biết, delegateEventsđược gọi trong hàm tạo để liên kết các sự kiện. Vì vậy, khi bạn mở rộng nó trong initialize, tại sao nó không quá muộn?
SelimOber

2
Thật khó hiểu, nhưng vấn đề của tôi với giải pháp này là: Nếu bạn có một hệ thống phân cấp quan điểm đa dạng và phong phú, chắc chắn bạn sẽ thấy mình viết initializetrong một vài trường hợp (sau đó cũng phải xử lý việc phân cấp chức năng của chức năng đó) hợp nhất các đối tượng sự kiện. Có vẻ sạch hơn với tôi để giữ sự eventshợp nhất trong chính nó. Điều đó đã được nói, tôi sẽ không nghĩ về cách tiếp cận này và thật tuyệt khi bị buộc phải nhìn mọi thứ theo một cách khác :)
EleventyOne

1
câu trả lời này không còn hiệu lực vì ủy nhiệm được gọi trước khi khởi tạo (điều này đúng với phiên bản 1.2.3) - thật dễ dàng trong nguồn chú thích này.
Roey

12

Bạn cũng có thể sử dụng defaultsphương thức để tránh tạo đối tượng trống {}.

var ChildView = ParentView.extend({
  events: function(){
    return _.defaults({
      'click' : 'onclickChild'
    }, ParentView.prototype.events);
  }
});

2
Điều này làm cho trình xử lý cha mẹ bị ràng buộc sau khi xử lý con. Trong hầu hết các trường hợp không phải là một vấn đề, nhưng nếu một sự kiện con nên hủy bỏ (không ghi đè) một sự kiện cha mẹ thì không thể.
Koen.

10

Nếu bạn sử dụng CoffeeScript và đặt chức năng thành events, bạn có thể sử dụng super.

class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

class ChildView extends ParentView
  events: ->
    _.extend {}, super,
      'bar' : 'doOtherThing'

Điều này chỉ hoạt động nếu biến sự kiện cha là một hàm chứ không phải là một đối tượng.
Michael

6

Sẽ không dễ dàng hơn để tạo trình xây dựng cơ sở chuyên biệt từ Backbone.View xử lý sự kế thừa của các sự kiện theo cấu trúc phân cấp.

BaseView = Backbone.View.extend {
    # your prototype defaults
},
{
    # redefine the 'extend' function as decorated function of Backbone.View
    extend: (protoProps, staticProps) ->
      parent = this

      # we have access to the parent constructor as 'this' so we don't need
      # to mess around with the instance context when dealing with solutions
      # where the constructor has already been created - we won't need to
      # make calls with the likes of the following:   
      #    this.constructor.__super__.events
      inheritedEvents = _.extend {}, 
                        (parent.prototype.events ?= {}),
                        (protoProps.events ?= {})

      protoProps.events = inheritedEvents
      view = Backbone.View.extend.apply parent, arguments

      return view
}

Điều này cho phép chúng tôi giảm (hợp nhất) các sự kiện băm xuống cấu trúc phân cấp bất cứ khi nào chúng tôi tạo một 'lớp con' mới (hàm tạo con) bằng cách sử dụng hàm mở rộng được xác định lại.

# AppView is a child constructor created by the redefined extend function
# found in BaseView.extend.
AppView = BaseView.extend {
    events: {
        'click #app-main': 'clickAppMain'
    }
}

# SectionView, in turn inherits from AppView, and will have a reduced/merged
# events hash. AppView.prototype.events = {'click #app-main': ...., 'click #section-main': ... }
SectionView = AppView.extend {
    events: {
        'click #section-main': 'clickSectionMain'
    }
}

# instantiated views still keep the prototype chain, nothing has changed
# sectionView instanceof SectionView => true 
# sectionView instanceof AppView => true
# sectionView instanceof BaseView => true
# sectionView instanceof Backbone.View => also true, redefining 'extend' does not break the prototype chain. 
sectionView = new SectionView { 
    el: ....
    model: ....
} 

Bằng cách tạo chế độ xem chuyên biệt: BaseView xác định lại chức năng mở rộng, chúng ta có thể có các cuộc phỏng vấn (như AppView, Mục Xem) muốn kế thừa các sự kiện khai báo của chế độ xem cha mẹ của chúng chỉ đơn giản bằng cách mở rộng từ BaseView hoặc một trong các dẫn xuất của nó.

Chúng tôi tránh sự cần thiết phải lập trình các hàm sự kiện của chúng tôi trong các cuộc phỏng vấn của chúng tôi, trong hầu hết các trường hợp cần phải tham khảo rõ ràng đến hàm tạo cha mẹ.


2

Phiên bản ngắn của đề xuất cuối cùng của @ Soldier.moth:

var ChildView = ParentView.extend({
  events: function(){
    return _.extend({}, _.result(ParentView.prototype, 'events') || {}, {
      'click' : 'onclickChild'
    });
  }
});

2

Điều này cũng sẽ làm việc:

class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

class ChildView extends ParentView
  events: ->
    _.extend({}, _.result(_super::, 'events') || {},
      'bar' : 'doOtherThing')

Sử dụng thẳng superkhông làm việc cho tôi, hoặc là chỉ định thủ công ParentViewlớp hoặc kế thừa.

Truy cập vào _supervar có sẵn trong bất kỳ coffeescriptClass … extends …


2

// ModalView.js
var ModalView = Backbone.View.extend({
	events: {
		'click .close-button': 'closeButtonClicked'
	},
	closeButtonClicked: function() { /* Whatever */ }
	// Other stuff that the modal does
});

ModalView.extend = function(child) {
	var view = Backbone.View.extend.apply(this, arguments);
	view.prototype.events = _.extend({}, this.prototype.events, child.events);
	return view;
};

// MessageModalView.js
var MessageModalView = ModalView.extend({
	events: {
		'click .share': 'shareButtonClicked'
	},
	shareButtonClicked: function() { /* Whatever */ }
});

// ChatModalView.js
var ChatModalView = ModalView.extend({
	events: {
		'click .send-button': 'sendButtonClicked'
	},
	sendButtonClicked: function() { /* Whatever */ }
});

http://danhough.com/blog/backbone-view-inherribution/


1

Đối với phiên bản xương sống 1.2.3, __super__hoạt động tốt và thậm chí có thể bị xiềng xích. Ví dụ:

// A_View.js
var a_view = B_View.extend({
    // ...
    events: function(){
        return _.extend({}, a_view.__super__.events.call(this), { // Function - call it
            "click .a_foo": "a_bar",
        });
    }
    // ...
});

// B_View.js
var b_view = C_View.extend({
    // ...
    events: function(){
        return _.extend({}, b_view.__super__.events, { // Object refence
            "click .b_foo": "b_bar",
        });
    }
    // ...
});

// C_View.js
var c_view = Backbone.View.extend({
    // ...
    events: {
        "click .c_foo": "c_bar",
    }
    // ...
});

... mà - trong A_View.js- sẽ dẫn đến:

events: {
    "click .a_foo": "a_bar",
    "click .b_foo": "b_bar",
    "click .c_foo": "c_bar",
}

1

Tôi đã tìm thấy một giải pháp thú vị hơn trong bài viết này

Nó sử dụng của của Backbone siêu và hasOwnProperty ECMAScript của. Các ví dụ tiến bộ thứ hai của nó hoạt động như một nét duyên dáng. Đây là một đoạn mã:

var ModalView = Backbone.View.extend({
    constructor: function() {
        var prototype = this.constructor.prototype;

        this.events = {};
        this.defaultOptions = {};
        this.className = "";

        while (prototype) {
            if (prototype.hasOwnProperty("events")) {
                _.defaults(this.events, prototype.events);
            }
            if (prototype.hasOwnProperty("defaultOptions")) {
                _.defaults(this.defaultOptions, prototype.defaultOptions);
            }
            if (prototype.hasOwnProperty("className")) {
                this.className += " " + prototype.className;
            }
            prototype = prototype.constructor.__super__;
        }

        Backbone.View.apply(this, arguments);
    },
    ...
});

Bạn cũng có thể làm điều đó cho uithuộc tính .

Ví dụ này không quan tâm đến các thuộc tính được thiết lập bởi một hàm, nhưng tác giả của bài viết đưa ra một giải pháp trong trường hợp đó.


1

Để thực hiện điều này hoàn toàn trong lớp cha và hỗ trợ hàm băm các sự kiện dựa trên hàm trong lớp con để trẻ có thể không được thừa kế (đứa trẻ sẽ phải gọi MyView.prototype.initializenếu nó ghi đè initialize):

var MyView = Backbone.View.extend({
  events: { /* ... */ },

  initialize: function(settings)
  {
    var origChildEvents = this.events;
    this.events = function() {
      var childEvents = origChildEvents;
      if(_.isFunction(childEvents))
         childEvents = childEvents.call(this);
      return _.extend({}, MyView.prototype.events, childEvents);
    };
  }
});

0

Giải pháp CoffeeScript này hoạt động với tôi (và tính đến đề xuất của @ Soldier.moth):

class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

class ChildView extends ParentView
  events: ->
    _.extend({}, _.result(ParentView.prototype, 'events') || {},
      'bar' : 'doOtherThing')

0

Nếu bạn chắc chắn rằng các ParentViewsự kiện được xác định là đối tượng và bạn không cần xác định động các sự kiện trong ChildViewđó thì có thể đơn giản hóa câu trả lời của Soldier.moth bằng cách loại bỏ chức năng và sử dụng _.extendtrực tiếp:

var ParentView = Backbone.View.extend({
    events: {
        'click': 'onclick'
    }
});

var ChildView = ParentView.extend({
    events: _.extend({}, ParentView.prototype.events, {
        'click' : 'onclickChild'
    })
});

0

Một mẫu cho điều này mà tôi thích là sửa đổi hàm tạo và thêm một số chức năng bổ sung:

// App View
var AppView = Backbone.View.extend({

    constructor: function(){
        this.events = _.result(this, 'events', {});
        Backbone.View.apply(this, arguments);
    },

    _superEvents: function(events){
        var sooper = _.result(this.constructor.__super__, 'events', {});
        return _.extend({}, sooper, events);
    }

});

// Parent View
var ParentView = AppView.extend({

    events: {
        'click': 'onclick'
    }

});

// Child View
var ChildView = ParentView.extend({

    events: function(){
        return this._superEvents({
            'click' : 'onclickChild'
        });
    }

});

Tôi thích phương pháp này vì bạn không phải xác định biến phụ huynh ít thay đổi. Tôi sử dụng logic tương tự cho attributesdefaults.


0

Ồ, rất nhiều câu trả lời ở đây nhưng tôi nghĩ tôi sẽ cung cấp thêm một câu nữa. Nếu bạn sử dụng thư viện BackSupport, nó sẽ cung cấp extend2. Nếu bạn sử dụng, extend2nó sẽ tự động chăm sóc việc hợp nhất events(cũng như defaultscác thuộc tính tương tự) cho bạn.

Đây là một ví dụ nhanh:

var Parent = BackSupport.View.extend({
    events: {
        change: '_handleChange'
    }
});
var Child = parent.extend2({
    events: {
        click: '_handleClick'
    }
});
Child.prototype.events.change // exists
Child.prototype.events.click // exists

https://github.com/machineghost/BackSupport


3
Tôi thích khái niệm này, nhưng, về nguyên tắc, tôi sẽ chuyển qua bất kỳ thư viện nào nghĩ rằng "extend2" là một tên hàm thích hợp.
Yaniv

Tôi sẽ hoan nghênh mọi đề xuất mà bạn có thể đưa ra để đặt tên cho một chức năng chủ yếu là "Backbone.extend, nhưng với chức năng được cải thiện". Extend 2.0 ( extend2) là thứ tốt nhất tôi có thể nghĩ ra và tôi không nghĩ nó tệ đến thế: bất kỳ ai từng sử dụng Backbone đều đã quen sử dụng extend, vì vậy theo cách này họ không cần phải ghi nhớ một lệnh mới.
máy

Đã mở một vấn đề trên repo Github về nó. :)
Yaniv
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.