MithrilJS / mithril.js

A JavaScript Framework for Building Brilliant Applications
https://mithril.js.org
MIT License
13.99k stars 926 forks source link

Multiple apps in same page cause multiple redraws of the first mounted app #674

Closed altunyurt closed 9 years ago

altunyurt commented 9 years ago

A simple demonstration is at https://jsfiddle.net/t42co8ks/2/

Simply, binding the other modules to page causes the first module to redraw, even if they are not even related.

Is this a known behavior? How can i prevent the first module from redrawing?

lhorie commented 9 years ago

Yes, it's known behavior. Multiple tenancy is currently not supported

elektronik2k5 commented 9 years ago

I actually rely exactly on this behavior. @altunyurt, why do you care about redraws when you've got a virtual DOM?

altunyurt commented 9 years ago

@elektronik2k5 I'm firing up some requests when the elements are created, and in this case, multiple requests are made, for the element is created multiple times.

How do you rely on this behavior?

@lhorie does this behavior only cause firing the first component twice, or may any other unexpected things happen too?

barneycarroll commented 9 years ago

@altunyurt there are 2 issues here:

  1. Mithril redraws are global, so there is no way to tell only a specific component to redraw.
  2. When you mount several components directly into the DOM immediately, there will be 2 draw loops.

Why is issue 2 occurring? As Leo says, Mithril redraws are global, so whenever Mithril redraws, all views are re-computed. Another feature of redraws is that normally they are 'batched', or 'queued', meaning that if many redraws are triggered one after the other within a very short time, they will result in only a single redraw. There are some exceptions though: the very first time Mithril draws, it happens immediately. So in your situation what happens is this:

  1. The first component is mounted
  2. Mithril draws it immediately
  3. The second component is mounted, and queues a redraw
  4. The third component is mounted, and since a redraw is already queued, we stick with that one
  5. Mithril draws all three components

To avoid the immediate first draw, you can use m.startComputation before your initialisation code, and m.endComputation immediately afterwards. This will tell Mithril to pause and resume redraws. As you can see here, this will mean each component renders once during application initialisation.

may any other unexpected things happen too?

It depends upon expectation :) The Mithril philosophy is that rather than the traditional model of the programmer having to explicitly state when and how each individual part of the application redraws, you can just let Mithril compute all views automatically and update only the bits that have changed. So when component 1 draws a second time in your example, nothing is actually happening to its DOM. So there is actually nothing to worry about with this approach — if this were a real application, nothing bad would have happened. This becomes extremely useful when you have large models that can effect any number of views, because components are no longer responsible for explicitly stating when the model they represent may have changed — I can decide to add a new feature to my application like long-polling (making regular HTTP requests to get up to date data) and I wouldn't need to change any of my components: redraw is automatic and totally economic if no change to the view occurs.

lhorie commented 9 years ago

@altunyurt as others mentioned, redraws are global because all code is considered part of a single tenant application. Consider this:

var things = m.request(...)

m.mount(foo, {
  view: function() {
    return m(".things", things().map(function(thing) {
      return m("div", thing.name)
    }))
  }
})

m.mount(bar, {
  view: function() {
    return m(".things", things().map(function(thing) {
      return m("div", thing.id)
    }))
  }
})

In this case, a single model entity is consumed by two different "islands" in the page, so when the data source updates, you logically would expect that both islands update simultaneously.

"Multi tenancy" is where you explicitly have two different, independent apps mounted to the same page, but where one does not affect the other in any way.

altunyurt commented 9 years ago

Thank you all for the detailed explanations.

dejayc commented 7 years ago

@lhorie I'd like to know a little bit more about this philosophy. What do you propose as a solution for multi-tenant views where each view is complex and unrelated? For example, a source code repository browser might have a complex, computed dependency graph rendered on the top, and a user-input form at the bottom that supports real-time conversion of text to markdown. Independently, each part of the app might require considerable computation power to calculate the view. Forcing them to be single-tenant means updating the computed dependency graph each and every time a user enters a character into the text field, which seems like an expensive proposition.

dead-claudia commented 7 years ago

@dejayc See #1907. Although it's for a different concern (subtree rendering), it does effectively this, and you're probably looking for subtree rendering, anyways.

You may also want to see this batch of utilities I made, which includes something meant for m.mount-independent rendering, and it was pretty much designed with that use case in mind (both libraries and complex views). It still redraws on global m.redraws, but you can avoid that by defining onbeforeupdate: function (vnode, old) { return vnode === old }.

dejayc commented 7 years ago

@isiahmeadows thanks for the feedback. I'm not looking to do plentiful customization of mithril at this point, so I'll probably return if performance actually becomes a problem. Is #1907 something that I could implement now without modifying mithril at all?

dead-claudia commented 7 years ago

Not really without a lot of boilerplate (especially if you want proper batching), but my self-sufficient utility is probably what you're looking for. (That particular proposal only covers rendering individual roots, not full detachment.)