IT story

Backbone.js에서 하위 뷰를 렌더링하고 추가하는 방법

hot-time 2020. 6. 28. 18:08
반응형

Backbone.js에서 하위 뷰를 렌더링하고 추가하는 방법


내 응용 프로그램에서 다소 깊을 수있는 중첩보기 설정이 있습니다. 하위 뷰를 초기화, 렌더링 및 추가하는 방법에는 여러 가지가 있지만 일반적인 관행이 무엇인지 궁금합니다.

내가 생각한 커플은 다음과 같습니다.

initialize : function () {

    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});
},

render : function () {

    this.$el.html(this.template());

    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

장점 : 추가로 올바른 DOM 순서를 유지하는 것에 대해 걱정할 필요가 없습니다. 뷰는 초기에 초기화되므로 렌더링 기능에서 한 번에 수행 할 작업이 많지 않습니다.

단점 : 비싼 이벤트 일 수있는 이벤트를 다시 위임해야합니까? 부모 뷰의 렌더 기능은 발생해야하는 모든 서브 뷰 렌더링으로 인해 복잡합니까? tagName요소 를 설정할 수 없으므로 템플릿에서 올바른 tagName을 유지해야합니다.

또 다른 방법:

initialize : function () {

},

render : function () {

    this.$el.empty();

    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});

    this.$el.append(this.subView1.render().el, this.subView2.render().el);
}

장점 : 이벤트를 다시 위임 할 필요가 없습니다. 빈 자리 표시자를 포함하는 템플릿이 필요하지 않으며 tagName이 뷰에서 다시 정의됩니다.

단점 : 이제 올바른 순서로 항목을 추가해야합니다. 부모 뷰의 렌더링은 여전히 ​​서브 뷰 렌더링에 의해 복잡해집니다.

onRender이벤트 와 함께 :

initialize : function () {
    this.on('render', this.onRender);
    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});
},

render : function () {

    this.$el.html(this.template);

    //other stuff

    return this.trigger('render');
},

onRender : function () {

    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

장점 : 이제 서브 뷰 로직이 뷰의 render()메소드 와 분리되었습니다 .

onRender이벤트 와 함께 :

initialize : function () {
    this.on('render', this.onRender);
},

render : function () {

    this.$el.html(this.template);

    //other stuff

    return this.trigger('render');
},

onRender : function () {
    this.subView1 = new Subview();
    this.subView2 = new Subview();
    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

나는이 모든 예제에서 혼합되어 다양한 사례를 일치 시켰습니다 (죄송합니다)하지만 유지하거나 추가 할 것은 무엇입니까? 그리고 당신은 무엇을하지 않겠습니까?

사례 요약 :

  • initialize또는에서 하위 뷰를 인스턴스화 render합니까?
  • render또는 안에 모든 서브 뷰 렌더링 로직을 수행 onRender합니까?
  • 사용 setElement또는 append/appendTo?

나는 일반적으로 몇 가지 다른 솔루션을 보았거나 사용했습니다.

해결책 1

var OuterView = Backbone.View.extend({
    initialize: function() {
        this.inner = new InnerView();
    },

    render: function() {
        this.$el.html(template); // or this.$el.empty() if you have no template
        this.$el.append(this.inner.$el);
        this.inner.render();
    }
});

var InnerView = Backbone.View.extend({
    render: function() {
        this.$el.html(template);
        this.delegateEvents();
    }
});

이것은 첫 번째 예와 비슷하며 몇 가지 변경 사항이 있습니다.

  1. 하위 요소를 추가하는 순서는 중요합니다
  2. 외부 뷰에는 내부 뷰에 설정할 html 요소가 포함되어 있지 않습니다 (내부 뷰에서 tagName을 지정할 수 있음)
  3. render()내부 뷰의 요소가 DOM에 배치 된 후에는 내부 뷰의 render()메소드가 다른 요소의 위치 / 크기 (내 경험에서 일반적인 사용 사례)를 기반으로 페이지에 배치 / 크기 조정 하는 경우 유용합니다

해결책 2

var OuterView = Backbone.View.extend({
    initialize: function() {
        this.render();
    },

    render: function() {
        this.$el.html(template); // or this.$el.empty() if you have no template
        this.inner = new InnerView();
        this.$el.append(this.inner.$el);
    }
});

var InnerView = Backbone.View.extend({
    initialize: function() {
        this.render();
    },

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

해결 방법 2가 더 깨끗해 보일 수 있지만 경험상 이상한 일이 발생하여 성능에 부정적인 영향을 미쳤습니다.

나는 일반적으로 몇 가지 이유로 솔루션 1을 사용합니다.

  1. 내 견해의 많은 부분은 이미 render()메소드 에서 DOM에 의존하고 있습니다.
  2. 외부 뷰를 다시 렌더링하면 뷰를 다시 초기화 할 필요가 없습니다. 다시 초기화하면 메모리 누수가 발생할 수 있으며 기존 바인딩에 이상한 문제가 발생할 수 있습니다

호출 new View()때마다 초기화하는 경우 어쨌든 render()해당 초기화가 호출 delegateEvents()됩니다. 당신이 표현한 것처럼 반드시 "con"일 필요는 없습니다.


이것은 Backbone의 영원한 문제이며 내 경험상이 질문에 대한 만족스러운 답변은 없습니다. 특히이 사용 사례가 얼마나 일반적인 지에도 불구하고 지침이 거의 없기 때문에 좌절감을 공유합니다. 즉, 나는 보통 두 번째 예와 비슷한 것을 간다.

우선, 나는 당신이 사건을 다시 위임 할 것을 요구하는 모든 것을 기각 할 것입니다. 백본의 이벤트 중심 뷰 모델은 가장 중요한 구성 요소 중 하나이며, 응용 프로그램이 사소한 것이 아니기 때문에 해당 기능을 잃어 버리면 프로그래머의 입에 나쁜 취향을 남길 수 있습니다. 첫 번째 스크래치입니다.

세 번째 예제와 관련해서는 기존 렌더링 실습을 마무리 짓고 많은 의미를 부여하지 않는다고 생각합니다. 아마도 실제 이벤트 트리거링 (예를 들어, " onRender"이벤트가 아닌 이벤트)을 수행하는 경우 해당 이벤트를 render자체에 바인딩하는 것이 좋습니다. 당신이 찾아내는 경우에 render다루기 힘든 복잡한되고, 당신은 너무 적은 파단이있다.

두 번째 예를 다시 살펴보면, 아마도 세 가지 악 가운데 작은 것입니다. 다음은 내 PDF 버전의 42 페이지에있는 Recipe with With Backbone 에서 가져온 예제 코드입니다 .

...
render: function() {
    $(this.el).html(this.template());
    this.addAll();
    return this;
},
  addAll: function() {
    this.collection.each(this.addOne);
},
  addOne: function(model) {
    view = new Views.Appointment({model: model});
    view.render();
    $(this.el).append(view.el);
    model.bind('remove', view.remove);
}

이것은 단지 두 번째 예보다 약간 더 복잡한 설정입니다 : 그들은 기능의 설정에서 지정할 addAlladdOne더러운 작업을 수행합니다. 나는이 접근법이 효과적이라고 생각한다. 그러나 여전히 기괴한 뒷맛을 남깁니다. (이 모든 혀 은유를 용서하십시오.)

올바른 순서로 추가하는 요점 : 엄격하게 추가하는 경우 제한 사항입니다. 그러나 가능한 모든 템플릿 구성표를 고려해야합니다. 실제로 자리 표시 자 요소 (예 : 비어있는 div또는 ul)를 replaceWith원할 경우 적절한 하위보기를 보유하는 새 (DOM) 요소를 사용할 수 있습니다. Appending이 유일한 해결책은 아니며 주문 문제를 그렇게 많이 신경 쓰면 확실히 해결할 수는 있지만 디자인 문제로 인해 문제가 발생한다고 생각합니다. 서브 뷰에는 서브 뷰가있을 수 있으며, 적절하다면 서브 뷰를 사용해야합니다. 이렇게하면 다소 트리와 같은 구조가 있습니다. 각 하위보기는 상위보기가 다른 하위보기를 추가하기 전에 모든 하위보기를 순서대로 추가합니다.

안타깝게도 솔루션 # 2는 기본 제공 백본을 사용하기에 가장 좋은 방법 일 것입니다. 타사 라이브러리를 체크 아웃하는 데 관심이 있다면, 내가 보았지만 실제로 아직 시간을 보지 못한 것은 Backbone.LayoutManager 이며 하위 뷰를 추가하는 더 건강한 방법 인 것 같습니다. 그러나 그들조차도 이와 비슷한 문제에 대해 최근 논쟁 였습니다.


아직 언급되지 않은 것에 놀랐지 만 마리오네트 사용을 진지하게 고려하고 있습니다.

그것은 특정 뷰 타입 (포함 백본 앱 좀더 구조를 적용 ListView, ItemView, RegionLayout), 적절한 추가 Controller들 및 더 많은.

다음은 Github의 프로젝트 이며 Addy Osmani의 Backbone Fundamentals 책에 있는 훌륭한 안내서 입니다.


나는이 문제에 대한 매우 포괄적 인 해결책을 가지고 있다고 생각합니다. 컬렉션 내의 모델을 변경할 수 있으며 전체 컬렉션이 아닌 뷰만 다시 렌더링 할 수 있습니다. 또한 close () 메소드를 통해 좀비보기 제거를 처리합니다.

var SubView = Backbone.View.extend({
    // tagName: must be implemented
    // className: must be implemented
    // template: must be implemented

    initialize: function() {
        this.model.on("change", this.render, this);
        this.model.on("close", this.close, this);
    },

    render: function(options) {
        console.log("rendering subview for",this.model.get("name"));
        var defaultOptions = {};
        options = typeof options === "object" ? $.extend(true, defaultOptions, options) : defaultOptions;
        this.$el.html(this.template({model: this.model.toJSON(), options: options})).fadeIn("fast");
        return this;
    },

    close: function() {
        console.log("closing subview for",this.model.get("name"));
        this.model.off("change", this.render, this);
        this.model.off("close", this.close, this);
        this.remove();
    }
});
var ViewCollection = Backbone.View.extend({
    // el: must be implemented
    // subViewClass: must be implemented

    initialize: function() {
        var self = this;
        self.collection.on("add", self.addSubView, self);
        self.collection.on("remove", self.removeSubView, self);
        self.collection.on("reset", self.reset, self);
        self.collection.on("closeAll", self.closeAll, self);
        self.collection.reset = function(models, options) {
            self.closeAll();
            Backbone.Collection.prototype.reset.call(this, models, options);
        };
        self.reset();
    },

    reset: function() {
        this.$el.empty();
        this.render();
    },

    render: function() {
        console.log("rendering viewcollection for",this.collection.models);
        var self = this;
        self.collection.each(function(model) {
            self.addSubView(model);
        });
        return self;
    },

    addSubView: function(model) {
        var sv = new this.subViewClass({model: model});
        this.$el.append(sv.render().el);
    },

    removeSubView: function(model) {
        model.trigger("close");
    },

    closeAll: function() {
        this.collection.each(function(model) {
            model.trigger("close");
        });
    }
});

용법:

var PartView = SubView.extend({
    tagName: "tr",
    className: "part",
    template: _.template($("#part-row-template").html())
});

var PartListView = ViewCollection.extend({
    el: $("table#parts"),
    subViewClass: PartView
});

서브 뷰 작성 및 렌더링에 대해서는이 믹스 인을 확인하십시오.

https://github.com/rotundasoftware/backbone.subviews

It is a minimalist solution that addresses a lot of the issues discussed in this thread, including rendering order, not having to re-delegate events, etc. Note that the case of a collection view (where each model in the collection is represented with one subview) is a different topic. Best general solution I am aware of to that case is the CollectionView in Marionette.


I don't really like any of the above solutions. I prefer for this configuration over each view having to manually do work in the render method.

  • views can be a function or object returning an object of view definitions
  • When a parent's .remove is called, the .remove of nested children from the lowest order up should be called (all the way from sub-sub-sub views)
  • By default the parent view passes it's own model and collection, but options can be added and overridden.

Here's an example:

views: {
    '.js-toolbar-left': CancelBtnView, // shorthand
    '.js-toolbar-right': {
        view: DoneBtnView,
        append: true
    },
    '.js-notification': {
        view: Notification.View,
        options: function() { // Options passed when instantiating
            return {
                message: this.state.get('notificationMessage'),
                state: 'information'
            };
        }
    }
}

Backbone was intentionally built so that there was no "common" practice in regards to this and many other issues. It is meant to be as unopinionated as possible. Theoretically, you don't even have to use templates with Backbone. You could use javascript/jquery in the render function of a view to manually change all of the data in the view. To make it more extreme, you don't even need one specific render function. You could have a function called renderFirstName which updates the first name in the dom and renderLastName which updates the last name in the dom. If you took this approach, it would be way better in terms of performance and you'd never have to manually delegate events again. The code would also make total sense to someone reading it (although it would be longer/messier code).

However, usually there is no downside to using templates and simply destroying and rebuilding the entire view and it's subviews on each and every render call, as it didn't even occur to the questioner to do anything otherwise. So that's what most people do for pretty much every situation they come across. And that's why opinionated frameworks just make this the default behavior.


You could also inject the rendered subviews as variables into the main template as variables.

first render the subviews and convert them to html like this:

var subview1 = $(subview1.render.el).html(); var subview2 = $(subview2.render.el).html();

(that way you could also dynamically string concatenate the views like subview1 + subview2 when used in loops) and then pass it to the master template which looks like this: ... some header stuff ... <%= sub1 %> <%= sub2 %> ... some footer stuff ...

and inject it finally like this:

this.$el.html(_.template(MasterTemplate, { sub1: subview1, sub2: subview2 } ));

Regarding the Events within the subviews: They will be most likely have to be connected in the parent (masterView) with this approach not within the subviews.


I like to use the following approach which also make sure to remove the child views properly. Here is an example from the book by Addy Osmani.

Backbone.View.prototype.close = function() {
    if (this.onClose) {
        this.onClose();
    }
    this.remove(); };

NewView = Backbone.View.extend({
    initialize: function() {
       this.childViews = [];
    },
    renderChildren: function(item) {
        var itemView = new NewChildView({ model: item });
        $(this.el).prepend(itemView.render());
        this.childViews.push(itemView);
    },
    onClose: function() {
      _(this.childViews).each(function(view) {
        view.close();
      });
    } });

NewChildView = Backbone.View.extend({
    tagName: 'li',
    render: function() {
    } });

There is no need to re-delegate events as it is costly. See below:

    var OuterView = Backbone.View.extend({
    initialize: function() {
        this.inner = new InnerView();
    },

    render: function() {
        // first detach subviews            
        this.inner.$el.detach(); 

        // now can set html without affecting subview element's events
        this.$el.html(template);

        // now render and attach subview OR can even replace placeholder 
        // elements in template with the rendered subview element
        this.$el.append(this.inner.render().el);

    }
});

var InnerView = Backbone.View.extend({
    render: function() {
        this.$el.html(template);            
    }
});

참고URL : https://stackoverflow.com/questions/9271507/how-to-render-and-append-sub-views-in-backbone-js

반응형