Backbone.js에서 서브 뷰 초기화 및 렌더링을 처리하는 방법은 무엇입니까?


199

뷰와 하위 뷰를 초기화하고 렌더링하는 세 가지 방법이 있으며 각 방법마다 문제가 있습니다. 모든 문제를 해결하는 더 좋은 방법이 있는지 궁금합니다.


시나리오 하나 :

부모의 초기화 함수에서 자식을 초기화하십시오. 이런 식으로 모든 것이 렌더링에 걸리는 것은 아니므로 렌더링에 대한 차단이 적습니다.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

    this.child.render().appendTo(this.$('.container-placeholder');
}

문제 :

  • 가장 큰 문제는 부모에서 렌더링을 두 번 호출하면 모든 자식 이벤트 바인딩이 제거된다는 것입니다. (이는 jQuery의 $.html()작동 방식 때문입니다 .) 이는 this.child.delegateEvents().render().appendTo(this.$el);대신 호출하여 완화 할 수 있지만 첫 번째로, 가장 빈번하게는 불필요하게 더 많은 작업을 수행합니다.

  • 자식을 추가하면 render 함수가 부모 DOM 구조에 대한 지식을 갖도록하여 원하는 순서를 얻을 수 있습니다. 즉, 템플릿을 변경하면 뷰의 렌더 기능을 업데이트해야 할 수도 있습니다.


시나리오 2 :

부모의 initialize()스틸 에서 자식을 초기화 하지만 추가하는 대신 setElement().delegateEvents()자식을 부모 템플릿의 요소로 설정하십시오.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

    this.child.setElement(this.$('.placeholder-element')).delegateEvents().render();
}

문제 :

  • 이로 인해 delegateEvents()지금 필요 하게 되는데, 이는 첫 번째 시나리오에서 후속 호출에만 필요하다는 것보다 약간 부정적입니다.

시나리오 3 :

render()대신 부모의 방법으로 자식을 초기화하십시오 .

initialize : function () {

    //parent init stuff
},

render : function () {

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

    this.child = new Child();

    this.child.appendTo($.('.container-placeholder').render();
}

문제 :

  • 이는 렌더링 기능이 이제 모든 초기화 로직과 연결되어야한다는 것을 의미합니다.

  • 자식 뷰 중 하나의 상태를 편집 한 다음 부모에서 렌더링을 호출하면 완전히 새로운 자식이 만들어지고 현재 상태가 모두 손실됩니다. 또한 메모리 누수로 인해 문제가 발생할 수 있습니다.


당신의 사람들이 이것에 걸릴 정말 궁금합니다. 어떤 시나리오를 사용 하시겠습니까? 아니면이 모든 문제를 해결하는 네 번째 마법이 있습니까?

뷰의 렌더링 상태를 추적 한 적이 있습니까? 발언권 renderedBefore플래그를? 정말 고약한 것 같습니다.


1
나는 일반적으로 부모 뷰에서 자식 뷰에 대한 참조를 요구하지 않습니다. 대부분의 통신은 모델 / 컬렉션 및 이러한 변경에 의해 트리거 된 이벤트를 통해 발생합니다. 세 번째 경우는 내가 가장 많이 사용하는 것에 가장 가깝지만. 의미가있는 사례 1 또한 대부분의 경우 전체 관점을 다시 렌더링하지 말고 변경된 부분을 다시 렌더링해야합니다.
— Tom Tu

물론 가능하면 변경된 부분 만 렌더링하지만 렌더링 기능을 사용할 수 있고 비파괴 적이어야한다고 생각합니다. 부모의 자녀에 대한 언급이없는 것을 어떻게 처리합니까?
— 이안 스톰 테일러

일반적으로 하위 뷰와 관련된 모델의 이벤트를 듣습니다. 더 많은 사용자 정의 작업을 수행하려는 경우 하위 뷰에 이벤트를 바인딩합니다. 이런 종류의 '통신'이 필요한 경우 일반적으로 이벤트 등을 묶는 서브 뷰를 작성하기위한 헬퍼 메소드를 작성합니다.
— Tom Tu

1
더 높은 수준의 관련 설명은 stackoverflow.com/questions/10077185/…를 참조하십시오 .
— 벤 로버츠

2
시나리오 2 delegateEvents()에서 setElement()? 를 호출해야하는 이유는 무엇입니까? 문서에 따르면 "...보기의 위임 된 이벤트를 이전 요소에서 새 요소로 이동"하면 setElement메소드 자체가 이벤트 재위임을 처리해야합니다.
— rdamborsky

답변:


260

이것은 좋은 질문입니다. 백본은 가정이 부족하기 때문에 훌륭하지만, 이와 같은 것을 직접 구현해야하는 방법을 결정해야합니다. 내 자신의 것들을 살펴본 후, 나는 (종류의) 시나리오 1과 시나리오 2의 혼합을 사용한다는 것을 알았습니다. 나는 단순히 시나리오 1과 2에서하는 모든 것이 반드시 있어야하기 때문에 네 번째 마법 시나리오가 있다고 생각하지 않습니다. 끝난.

예제로 처리하는 방법을 설명하는 것이 가장 쉽다고 생각합니다. 이 간단한 페이지가 지정된 뷰로 나뉘어 있다고 가정 해보십시오.

페이지 분류

렌더링 된 후 HTML이 다음과 같다고 가정 해 봅시다.

<div id="parent">
    <div id="name">Person: Kevin Peel</div>
    <div id="info">
        First name: <span class="first_name">Kevin</span><br />
        Last name: <span class="last_name">Peel</span><br />
    </div>
    <div>Phone Numbers:</div>
    <div id="phone_numbers">
        <div>#1: 123-456-7890</div>
        <div>#2: 456-789-0123</div>
    </div>
</div>

HTML이 다이어그램과 어떻게 일치하는지 분명히 알 수 있습니다.

는 ParentView두 아이 뷰, 보유 InfoView하고 PhoneListView, 그 중 하나뿐만 아니라 몇 가지 추가 된 div, #name어떤 시점에서 설정해야합니다. PhoneListView자체 PhoneView항목 의 자식 뷰를 보유 합니다.

실제 질문에 대해서도 마찬가지입니다. 뷰 유형에 따라 초기화 및 렌더링을 다르게 처리합니다. 뷰를 Parent뷰와 Child뷰의 두 가지 유형으로 나눕니다 .

그들 사이의 차이점은 간단합니다. Parent보기는 자식보기를 유지하지만 Child보기는 그렇지 않습니다. 그래서 내 예에서 ParentView와 PhoneListView있는 Parent동안, 뷰 InfoView및 PhoneView항목은 다음과 Child전망을 제공합니다.

앞에서 언급했듯이이 두 범주의 가장 큰 차이점은 렌더링이 허용되는 시점입니다. 완벽한 세상에서, 나는 Parent뷰가 한 번만 렌더링 되기를 원합니다 . 모델이 변경 될 때 다시 렌더링을 처리하는 것은 자녀의 관점에 달려 있습니다. Child반면에, 나는 다른 의견이 없기 때문에 필요할 때마다 다시 렌더링 할 수 있습니다.

좀 더 자세하게, Parent뷰의 경우 initialize함수가 몇 가지 작업을 수행하는 것을 좋아합니다.

  1. 내 자신의보기를 초기화
  2. 내 자신의 견해를 표현하십시오
  3. 자식 뷰를 만들고 초기화합니다.
  4. 각 자식보기에 내보기 내의 요소를 지정하십시오 (예 : InfoView할당 됨 #info).

1 단계는 설명이 필요 없습니다.

2 단계 렌더링은 하위 뷰에 의존하는 요소가 할당을 시도하기 전에 이미 존재하도록하기 위해 수행됩니다. 이렇게하면 모든 어린이 events가 올바르게 설정되어 있음을 알 수 있으며 아무 것도 다시 위임하지 않아도 걱정없이 블록을 원하는만큼 다시 렌더링 할 수 있습니다. 나는 실제로 render여기에 어떤 아이의 견해 도 없으며 , 그들 스스로의 견해를 허용합니다 initialization.

3 단계와 4 단계는 실제로 el자식보기를 만드는 동안 전달하는 동시에 처리됩니다 . 부모가 자신의 관점에서 자녀가 내용을 넣을 수있는 곳을 결정해야한다고 생각하면서 여기에 요소를 전달하고 싶습니다.

렌더링을 위해 Parent뷰를 간단하게 유지하려고합니다 . render함수가 부모보기를 렌더링하는 것 이상을 수행하기를 원합니다 . 이벤트 위임, 자식 뷰 렌더링, 아무것도 없습니다. 간단한 렌더링입니다.

때로는 이것이 항상 작동하지는 않습니다. 예를 들어 위의 예 #name에서 모델 내 이름이 변경 될 때마다 요소를 업데이트해야합니다. 그러나이 블록은 ParentView템플릿의 일부이며 전용 Child보기로 처리되지 않으므로이 문제를 해결합니다. 요소 의 내용 만 바꾸고 전체 요소 를 휴지통에 버릴 필요가없는 일종의 subRender함수를 만들 것 입니다. 이것은 해킹처럼 보일 수 있지만 전체 DOM을 다시 렌더링하고 요소 등을 다시 연결하는 것에 대해 걱정하는 것보다 더 효과적이라는 것을 알았습니다. 정말로 깨끗하게 만들고 싶다면 블록을 처리 하는 새 보기 (와 유사)를 만듭니다 .#name#parentChildInfoView#name

이제 Child뷰 의 경우 추가 뷰를 만들지 않고 뷰 initialization와 매우 유사합니다 . 그래서:ParentChild

  1. 내 관점을 초기화
  2. 내가 관심있는 모델의 변경 사항을 수신 대기하는 설정
  3. 내 관점을 렌더링

Child뷰 렌더링도 매우 간단합니다. 렌더링하고 내 내용을 설정하십시오 el. 다시 말하지만, 위임이나 그와 비슷한 것을 망칠 필요가 없습니다.

다음은 내가 어떻게 ParentView보일지에 대한 예제 코드입니다 .

var ParentView = Backbone.View.extend({
    el: "#parent",
    initialize: function() {
        // Step 1, (init) I want to know anytime the name changes
        this.model.bind("change:first_name", this.subRender, this);
        this.model.bind("change:last_name", this.subRender, this);

        // Step 2, render my own view
        this.render();

        // Step 3/4, create the children and assign elements
        this.infoView = new InfoView({el: "#info", model: this.model});
        this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});
    },
    render: function() {
        // Render my template
        this.$el.html(this.template());

        // Render the name
        this.subRender();
    },
    subRender: function() {
        // Set our name block and only our name block
        $("#name").html("Person: " + this.model.first_name + " " + this.model.last_name);
    }
});

내 구현을 볼 수 있습니다 subRender. subRender대신에 변경 사항을 적용함으로써 render블래스트를 없애고 전체 블록을 다시 작성하는 것에 대해 걱정할 필요가 없습니다.

InfoView블록 의 예제 코드는 다음과 같습니다 .

var InfoView = Backbone.View.extend({
    initialize: function() {
        // I want to re-render on changes
        this.model.bind("change", this.render, this);

        // Render
        this.render();
    },
    render: function() {
        // Just render my template
        this.$el.html(this.template());
    }
});

바인딩은 여기서 중요한 부분입니다. 내 모델에 바인딩함으로써 수동으로 전화하는 것에 대해 걱정할 필요가 없습니다 render. 모델이 변경되면이 블록은 다른 뷰에 영향을주지 않고 다시 렌더링됩니다.

(가) PhoneListView받는 유사합니다 ParentView, 당신은 모두 당신에 좀 더 논리가 필요합니다 initialization및 render핸들 컬렉션에 기능을. 컬렉션을 처리하는 방법은 실제로 당신에게 달려 있지만 최소한 컬렉션 이벤트를 듣고 렌더링하려는 방법을 결정해야합니다 (추가 / 제거 또는 전체 블록을 다시 렌더링). 개인적으로 새로운 뷰를 추가하고 이전 뷰를 제거하고 전체 뷰를 다시 렌더링하지 않습니다.

는 PhoneView받는 사람 거의 동일합니다 InfoView단지 그것을 대한 관심 모델이 변경을 듣고.

잘만되면 이것이 약간 도움이되었으므로, 혼란 스럽거나 상세하지 않은 사항이 있으면 알려주십시오.


8
정말 철저한 설명 감사합니다. 질문 : 나는 메서드 render내에서 호출 initialize하는 것이 나쁜 습관 이라고 들었습니다. 즉 , 렌더링하고 싶지 않은 경우 더 성능이 뛰어나지 않기 때문입니다. 이것에 대해 어떻게 생각하십니까?
— Ian Storm Taylor

확실한. 흠 ... 나는 지금 볼 수 있어야하거나 가까운 미래에 보여 질 수있는 뷰를 초기화하려고합니다 (예 : 현재 숨겨져 있지만 편집 링크를 클릭하면 표시되는 편집 양식). 말하자면, 뷰를 렌더링하는 데 시간이 걸리는 경우 개인적으로 뷰를 표시해야 할 때까지 뷰 자체를 만들지 않으므로 필요할 때까지 초기화 및 렌더링이 수행되지 않습니다. 당신이 예를 가지고 있다면 나는 그것을 처리하는 방법에 대해 더 자세히 시도 할 수 있습니다 ...
— Kevin Peel

5
ParentView를 다시 렌더링 할 수없는 것은 큰 한계입니다. 기본적으로 탭 컨트롤이 있고 각 탭에 다른 ParentView가 표시되는 상황이 있습니다. 탭을 두 번 클릭하면 기존 ParentView를 다시 렌더링하고 싶지만 ChildViews의 이벤트가 손실됩니다 (자식을 초기화하여 렌더링합니다). 1) 렌더 대신 숨기기 / 표시, 2) ChildViews 렌더의 delegateEvents 또는 3) 렌더에서 자식 만들기 또는 4) 성능에 대해 걱정하지 마십시오. 문제가 발생하기 전에 매번 ParentView를 다시 만드십시오. 흠 ....
— 폴 Hoenecke

내 경우에는 그것이 제한이라고 말해야합니다. 이 솔루션은 부모를 다시 렌더링하려고 시도하지 않으면 잘 작동합니다. :) 좋은 대답이 없지만 OP와 같은 질문이 있습니다. 여전히 적절한 '백본'방식을 찾고자합니다. 이 시점에서 나는 숨바꼭질을 생각하고 다시 렌더링하지 않는 것이 나를 위해 갈 길입니다.
— Paul Hoenecke

1
컬렉션을 어떻게 아이에게 전달 PhoneListView합니까?
— Tri Nguyen


6

나에게 어떤 종류의 깃발을 통해 뷰의 초기 설정과 후속 설정을 구별하는 것은 세계에서 최악의 생각처럼 보이지 않습니다. 이 깨끗하고 쉽게 만들려면 고유 한 뷰에 플래그를 추가하여 백본 (기본) 뷰를 확장해야합니다.

Derick과 동일 이것이 귀하의 질문에 직접 대답하는지 확실하지는 않지만 적어도이 맥락에서 언급 할 가치가 있다고 생각합니다.

참조 : 백본에서 이벤트 버스 사용


3

Kevin Peel은 훌륭한 답변을 제공합니다. 여기 내 tl; dr 버전이 있습니다.

initialize : function () {

    //parent init stuff

    this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!!

    this.child = new Child();
},

2

이러한 뷰 사이의 연결을 피하려고합니다. 내가 보통 두 가지 방법이 있습니다.

라우터 사용

기본적으로 라우터 기능을 통해 부모 및 자식보기를 초기화 할 수 있습니다. 따라서 뷰에는 서로에 대한 지식이 없지만 라우터는 모든 것을 처리합니다.

동일한 엘을 두 뷰 모두에 전달

this.parent = new Parent({el: $('.container-placeholder')});
this.child = new Child({el: $('.container-placeholder')});

둘 다 동일한 DOM에 대한 지식이 있으며 원하는대로 주문할 수 있습니다.


2

내가하는 일은 각 어린이에게 정체성을 부여하는 것입니다 (Backbone은 이미 당신을 위해 그것을했습니다 : cid)

Container가 렌더링을 수행 할 때 'cid'및 'tagName'을 사용하면 모든 자식에 대한 자리 표시자가 생성되므로 템플릿에서 자식은 Container가 어디에 배치 될지 모릅니다.

<tagName id='cid'></tagName>

당신이 사용할 수있는 것보다

Container.render()
Child.render();
this.$('#'+cid).replaceWith(child.$el);
// the rapalceWith in jquery will detach the element 
// from the dom first, so we need re-delegateEvents here
child.delegateEvents();

지정된 자리 표시자가 필요하지 않으며 컨테이너는 자식의 DOM 구조가 아닌 자리 표시 자만 생성합니다. Cotainer와 Children은 여전히 ​​고유 DOM 요소를 한 번만 생성합니다.


1

다음은 서브 뷰를 생성하고 렌더링하기위한 경량 믹스 인입니다.이 스레드의 모든 문제를 해결합니다.

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

이 플러그에 의해 취해진 접근법 은 상위 뷰가 처음 렌더링 된 후 서브 뷰를 작성하고 렌더링 하는 것입니다. 그런 다음 상위 뷰의 후속 렌더링에서 하위 뷰 요소를 $ .detach하고 상위 뷰를 다시 렌더링 한 다음 하위 뷰 요소를 적절한 위치에 삽입하고 다시 렌더링하십시오. 이런 식으로 하위 뷰 객체는 후속 렌더링에서 재사용되며 이벤트를 다시 위임 할 필요가 없습니다.

컬렉션 뷰 (컬렉션의 각 모델이 하나의 하위 뷰로 표시되는)의 경우는 상당히 다르며 자체 토론 / 솔루션이 장점이라고 생각합니다. 내가 알고있는 가장 일반적인 해결책 은 Marionette 의 CollectionView입니다 .

편집 : 컬렉션 뷰의 경우 클릭 및 / 또는 드래그 앤 드롭을 기반으로 모델을 선택 해야하는 경우이 UI 중심 구현 을 확인하고 싶을 수도 있습니다 .

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