Backbone.js에서 서브 뷰 초기화 및 렌더링을 처리하는 방법은 무엇입니까?
뷰와 하위 뷰를 초기화하고 렌더링하는 세 가지 방법이 있으며 각 뷰마다 다른 문제가 있습니다. 모든 문제를 해결하는 더 좋은 방법이 있는지 궁금합니다.
시나리오 하나 :
부모의 초기화 함수에서 자식을 초기화하십시오. 이런 식으로 모든 것이 렌더링에 걸리는 것은 아니므로 렌더링에 대한 차단이 줄어 듭니다.
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();
}
문제 :
이것은 렌더링 기능이 이제 모든 초기화 로직과 연결되어야한다는 것을 의미합니다.
자식 뷰 중 하나의 상태를 편집 한 다음 부모에서 render를 호출하면 완전히 새로운 자식이 만들어지고 현재 상태가 모두 손실됩니다. 또한 메모리 누수로 인해 문제가 발생할 수 있습니다.
당신의 사람들이 이것에 걸릴 정말 궁금합니다. 어떤 시나리오를 사용 하시겠습니까? 아니면이 모든 문제를 해결하는 네 번째 마법이 있습니까?
뷰의 렌더링 상태를 추적 한 적이 있습니까? 발언권 renderedBefore
플래그를? 정말 고약한 것 같습니다.
이것은 좋은 질문입니다. 백본은 가정이 부족하기 때문에 훌륭하지만, 이와 같은 것을 직접 구현해야하는 방법을 결정해야합니다. 내 자신의 것들을 살펴본 후, 나는 (종류의) 시나리오 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이 다이어그램과 어떻게 일치하는지 분명히 알 수 있습니다.
The ParentView
holds 2 child views, InfoView
and PhoneListView
as well as a few extra divs, one of which, #name
, needs to be set at some point. PhoneListView
holds child views of its own, an array of PhoneView
entries.
So on to your actual question. I handle initialization and rendering differently based on the view type. I break my views into two types, Parent
views and Child
views.
The difference between them is simple, Parent
views hold child views while Child
views do not. So in my example, ParentView
and PhoneListView
are Parent
views, while InfoView
and the PhoneView
entries are Child
views.
Like I mentioned before, the biggest difference between these two categories is when they're allowed to render. In a perfect world, I want Parent
views to only ever render once. It is up to their child views to handle any re-rendering when the model(s) change. Child
views, on the other hand, I allow to re-render anytime they need since they don't have any other views relying upon them.
In a little more detail, for Parent
views I like my initialize
functions to do a few things:
- Initialize my own view
- Render my own view
- Create and initialize any child views.
- Assign each child view an element within my view (e.g. the
InfoView
would be assigned#info
).
Step 1 is pretty self explanatory.
Step 2, the rendering, is done so that any elements the child views rely on already exist before I try to assign them. By doing this, I know all child events
will be correctly set, and I can re-render their blocks as many times as I want without worrying about having to re-delegate anything. I do not actually render
any child views here, I allow them to do that within their own initialization
.
Steps 3 and 4 are actually handled at the same time as I pass el
in while creating the child view. I like to pass an element in here as I feel the parent should determine where in its own view the child is allowed to put its content.
For rendering, I try to keep it pretty simple for Parent
views. I want the render
function to do nothing more than render the parent view. No event delegation, no rendering of child views, nothing. Just a simple render.
Sometimes this doesn't always work though. For instance in my example above, the #name
element will need to be updated any time the name within the model changes. However, this block is part of the ParentView
template and not handled by a dedicated Child
view, so I work around that. I will create some sort of subRender
function that only replaces the content of the #name
element, and not have to trash the whole #parent
element. This may seem like a hack, but I've really found it works better than having to worry about re-rendering the whole DOM and reattaching elements and such. If I really wanted to make it clean, I'd create a new Child
view (similar to the InfoView
) that would handle the #name
block.
Now for Child
views, the initialization
is pretty similar to Parent
views, just without the creation of any further Child
views. So:
- Initialize my view
- Setup binds listening for any changes to the model I care about
- Render my view
Child
view rendering is also very simple, just render and set the content of my el
. Again, no messing with delegation or anything like that.
Here is some example code of what my ParentView
may look like:
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);
}
});
You can see my implementation of subRender
here. By having changes bound to subRender
instead of render
, I don't have to worry about blasting away and rebuilding the whole block.
Here's example code for the InfoView
block:
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());
}
});
The binds are the important part here. By binding to my model, I never have to worry about manually calling render
myself. If the model changes, this block will re-render itself without affecting any other views.
The PhoneListView
will be similar to the ParentView
, you'll just need a little more logic in both your initialization
and render
functions to handle collections. How you handle the collection is really up to you, but you'll at least need to be listening to the collection events and deciding how you want to render (append/remove, or just re-render the whole block). I personally like to append new views and remove old ones, not re-render the whole view.
The PhoneView
will be almost identical to the InfoView
, only listening to the model changes it cares about.
Hopefully this has helped a little, please let me know if anything is confusing or not detailed enough.
I'm not sure if this directly answers your question, but I think it's relevant:
http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/
The context in which I set up this article is different, of course, but I think the two solutions I offer, along with the pros and cons of each, should get you moving in the right direction.
To me it does not seem like the worst idea in the world to differentiate between the intital setup and subsequent setups of your views via some sort of flag. To make this clean and easy the flag should be added to your very own View which should extend the Backbone (Base) View.
Same as Derick I am not completely sure if this directly answers your question but I think it might be at least worth mentioning in this context.
Kevin Peel gives a great answer - here's my tl;dr version:
initialize : function () {
//parent init stuff
this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!!
this.child = new Child();
},
I'm trying to avoid coupling between views like these. There are two ways I usually do:
Use a router
Basically, you let your router function initialize parent and child view. So the view has no knowledge of each other, but the router handles it all.
Passing the same el to both views
this.parent = new Parent({el: $('.container-placeholder')});
this.child = new Child({el: $('.container-placeholder')});
Both have knowledge of the same DOM, and you can order them anyway you want.
What I do is giving each children an identity (which Backbone has already done that for you: cid)
When Container does the rendering, using the 'cid' and 'tagName' generate a placeholder for every child, so in template the children has no idea about where it will be put by the Container.
<tagName id='cid'></tagName>
than you can using
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();
no specified placeholder is needed, and Container only generate the placeholder rather than the children's DOM structure. Cotainer and Children are still generating own DOM elements and only once.
Here is a light weight mixin for creating and rendering subviews, which I think addresses all the issues in this thread:
https://github.com/rotundasoftware/backbone.subviews
The approach taken by this plug is create and render subviews after the first time the parent view is rendered. Then, on subsequent renders of the parent view, $.detach the subview elements, re-render the parent, then insert the subview elements in the appropriate places and re-render them. This way subviews objects are reused on subsequent renders, and there is no need to re-delegate events.
Note that the case of a collection view (where each model in the collection is represented with one subview) is quite different and merits its own discussion / solution I think. Best general solution I am aware of to that case is the CollectionView in Marionette.
편집 : 컬렉션보기 사례의 경우 클릭 및 / 또는 드래그 앤 드롭을 기반으로 모델을 선택 해야하는 경우이 UI 중심 구현 을 확인하고 싶을 수도 있습니다 .
'Programming' 카테고리의 다른 글
Ansible에 sudo 비밀번호를 지정하십시오. (0) | 2020.05.09 |
---|---|
Java : List를 Map으로 변환하는 방법 (0) | 2020.05.09 |
Promise.all에서 오류 처리 (0) | 2020.05.09 |
자식은 파일을 어떻게 저장합니까? (0) | 2020.05.09 |
go get으로 설치된 패키지 제거 (0) | 2020.05.09 |