[javascript] 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

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


답변

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

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


답변

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

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