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보기는 그렇지 않습니다. 그래서 내 예에서 ParentViewPhoneListView있는 Parent동안, 뷰 InfoViewPhoneView항목은 다음과 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, 당신은 모두 당신에 좀 더 논리가 필요합니다 initializationrender핸들 컬렉션에 기능을. 컬렉션을 처리하는 방법은 실제로 당신에게 달려 있지만 최소한 컬렉션 이벤트를 듣고 렌더링하려는 방법을 결정해야합니다 (추가 / 제거 또는 전체 블록을 다시 렌더링). 개인적으로 새로운 뷰를 추가하고 이전 뷰를 제거하고 전체 뷰를 다시 렌더링하지 않습니다.

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하고 상위 뷰를 다시 렌더링 한 다음 하위 뷰 요소를 적절한 위치에 삽입하고 다시 렌더링하십시오. 이런 식으로 하위 뷰 객체는 후속 렌더링에서 재사용되며 이벤트를 다시 위임 할 필요가 없습니다.

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

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

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