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


133

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

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

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?

삭제하지 않고 새로운 것에 대해 조심할 것입니다. 메모리 누수가 있습니다.
vimdude

1
걱정하지 마십시오. 아이들을 정리 하는 close방법과 방법이 onClose있지만 처음에 인스턴스화하고 렌더링하는 방법에 대해 궁금합니다.
이안 스톰 테일러

3
@abdelsaid : JavaScript에서 GC는 메모리 할당 해제를 처리합니다. deleteJS의 deleteC ++ 과 다릅니다 . 당신이 나에게 묻는다면 그것은 매우 가난한 키워드입니다.
Mike Bailey

@ MikeBantegui는 그것을 얻었지만 메모리를 확보하기 위해 JS에서 null을 할당해야한다는 점을 제외하면 Java와 동일합니다. 의미를 명확히하기 위해 내부에 새 객체가 포함 된 루프를 만들고 메모리를 모니터링하십시오. 물론 GC는 그것에 도달하지만 당신은 그것에 도달하기 전에 메모리를 잃을 것입니다. 이 경우 여러 번 호출 될 수있는 Render입니다.
vimdude

3
저는 초보자 백본 개발자입니다. 왜 예제 1이 우리에게 이벤트를 다시 위임해야하는지 설명해 줄 수 있습니까? (또는이 질문에 답해야합니까?) 감사합니다.
pilau

답변:


58

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

해결책 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"일 필요는 없습니다.


1
이러한 솔루션 중 어느 것도 View.remove를 호출하는 하위 뷰 트리를 작동시키지 않습니다. 뷰에서 사용자 정의 정리를 수행하는 데 필수적 일 수 있습니다. 그렇지 않으면 가비지 수집을 방지 할 수 있습니다.
Dominic

31

이것은 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 이며 하위 뷰를 추가하는 더 건강한 방법 인 것 같습니다. 그러나 그들조차도 이와 비슷한 문제에 대한 최근 논쟁 였습니다.


4
끝에서 두 번째 줄은- model.bind('remove', view.remove);약속의 초기화 기능에서 분리하여 구분하지 않아야합니까?
atp

2
상태를 유지하기 때문에 부모가 렌더링 할 때마다 뷰를 다시 인스턴스화 할 수없는 경우는 어떻습니까?
mor

이 모든 괴짜를 멈추고 Backbone.subviews 플러그인을 사용하십시오 !
Brave Dave

6

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

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

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


3
이것은 질문에 대답하지 않습니다.
Ceasar Bautista

2
@CeasarBautista Marionette를 사용하여이 작업을 수행하는 방법에 대해서는 다루지 않지만 Marionette는 실제로 위의 문제를 해결합니다
Dana Woodman

4

나는이 문제에 대한 매우 포괄적 인 해결책을 가지고 있다고 생각합니다. 컬렉션 내의 모델을 변경할 수 있으며 전체 컬렉션이 아닌 뷰만 다시 렌더링 할 수 있습니다. 또한 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
});

2

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

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

렌더링 순서, 이벤트를 다시 위임 할 필요 등을 포함하여이 스레드에서 논의 된 많은 문제를 해결하는 미니멀리스트 솔루션입니다. 컬렉션 뷰의 경우 (컬렉션의 각 모델이 하나로 표시됨) subview)는 다른 주제입니다. 내가 알고있는 가장 일반적인 해결책 은 MarionetteCollectionView입니다 .


0

위의 솔루션 중 어느 것도 정말로 좋아하지 않습니다. 렌더 방법에서 수동으로 작업 해야하는 각보기 보다이 구성을 선호합니다.

  • views 뷰 정의의 객체를 반환하는 함수 또는 객체 일 수 있습니다.
  • 부모 .remove를 호출 할 때 .remove가장 낮은 순서의 중첩 된 자식을 호출해야합니다 (하위 서브-하위 뷰에서 항상).
  • 기본적으로 상위 뷰는 자체 모델 및 컬렉션을 전달하지만 옵션을 추가하고 재정의 할 수 있습니다.

예를 들면 다음과 같습니다.

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

0

백본은 의도적으로 구축되어이 문제와 다른 많은 문제와 관련하여 "공통적 인"관행이 없었습니다. 가능한 한 의견이 맞지 않아야합니다. 이론적으로 Backbone에 템플릿을 사용할 필요조차 없습니다. render뷰 의 함수 에서 javascript / jquery를 사용 하여 뷰의 모든 데이터를 수동으로 변경할 수 있습니다. 더 극단적으로 만들기 위해 하나의 특정 render기능 조차 필요하지 않습니다 . renderFirstNamedom에서 이름 renderLastName을 업데이트하고 dom에서 성을 업데이트 하는 함수가있을 수 있습니다 . 이 방법을 사용하면 성능 측면에서 더 좋을 것이므로 이벤트를 수동으로 다시 위임 할 필요가 없습니다. 코드는 코드를 읽는 사람에게도 의미가 있습니다 (더 길거나 더 간결한 코드 임에도 불구하고).

그러나 일반적으로 템플릿을 사용하고 단순히 전체 뷰를 파괴하고 다시 작성하는 것에 대한 단점은 없으며 질문자가 다른 작업을 수행하지 않았기 때문에 각 렌더 호출마다 하위 뷰입니다. 그것이 대부분의 사람들이 그들이 접하는 거의 모든 상황에 대해하는 일입니다. 이것이 바로 의견이있는 프레임 워크가이를 기본 동작으로 만드는 이유입니다.


0

렌더링 된 서브 뷰를 변수로 기본 템플리트에 변수로 삽입 할 수도 있습니다.

먼저 하위 뷰를 렌더링하고 다음과 같이 html로 변환하십시오.

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

( subview1 + subview2그러면 루프에서 사용될 때 와 같이 뷰를 동적으로 문자열로 연결 하여 마스터 템플릿에 전달할 수 있습니다.) ... some header stuff ... <%= sub1 %> <%= sub2 %> ... some footer stuff ...

마지막으로 다음과 같이 주입하십시오.

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

하위보기 내의 이벤트와 관련하여 : 하위보기가 아닌이 방법으로 상위 (masterView)에서 이벤트를 연결해야합니다.


0

자식 뷰를 올바르게 제거하는 다음 방법을 사용하고 싶습니다. 다음은 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() {
    } });

0

비용이 많이 드는 이벤트를 다시 위임 할 필요가 없습니다. 아래를보십시오 :

    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);            
    }
});
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.