Since Meteor upgrade to 0.8.0, Template “rendered”

2019-05-13 00:01发布

问题:

I'm stuck with a problem since I upgraded to 0.8.0. The Template rendered is not being fired anymore (except the first time).

I followed the recommendations as in: https://github.com/avital/meteor-ui-new-rendered-callback/blob/master/new2/client/each.js

This didn't helped, and so I finally made this small piece of code (by modifying the new2 example).

The main difference is that the update is triggered by a Session variable change instead of a DB change.

This perfectly shows the problem, as rendered is fired only twice with this example:

client/each.js

Template.list.items = function () {
  return (Session.get('items') || 'None')
};

var renderCount = 1;
var logRender = function () {
  console.log("rendered #" + renderCount);
  renderCount++;
};

Template.list.rendered = function () {
  logRender();
};

Template.justName.rendered = function () {
  logRender();
};

setInterval(function () {
  Session.set('items', {name: Random.choice(["one", "two", "three"])});
}, 1000);

client/each.html

<body>
  {{> list}}
</body>

<template name="list">
   {{#with items}}
   {{> justName}}
   {{/with}}
</template>

<template name="justName">
  {{name}}
</template>

How can I do to get the Template.justName.rendered callback properly fired when content update is triggered by a Session.set?

Thanks,

回答1:

I do have an instant solution for you, but it probably requires a tiny bit of re-thinking your actual code. This is by the way the same problem as here:

Meteor 0.8.0 - Failed to operate DOM in rendered callback

But the question is posed in such a different context that it makes sense to answer it twice.

So why does it not trigger the rendered callback? Because it does not re-render.

Blaze treats the whole thing of "how to react on a changed dependencies" very differently, "better" one might say: it will identify the DOM node where your "one", "two" or "three" (in your case it's the template itself) was stored in and just replace the part that has changed, which is the text content "one", "two" or "three". The DOM node itself as well as the template stay completely intact. That also means, that everything you could have been doing with this DOM node won't have to be re-done in almost every practical scenario. I.e. if you animate it, change it's text color using jQuery, the color and animation will just stay on the screen, so you won't need the rendered callback to re-do that.

In your case, the problem is easily solved by just rearanging what you want to do on "rerender":

var whatever = function(){
    // whatever you want to do on data-change, in your case calling "logRender" (which needs to be renamed with Blaze, anyway..)
    logRender();
}

And then the only thing you have to do is firing it whenever your data change, either manually, like this:

setInterval(function () {
    Session.set('items', {name: Random.choice(["one", "two", "three"])});
    // calling the function when changing the data, knowing that it WON'T destroy the DOM node it affects
    whatever();
}, 1000);

or reactively, like this:

Deps.autorun(function(){
    Session.get("items"); // our dependency, just has to be there, but you can also use it
    whatever(); // will be fired whenever dependency changes
});

The core idea is to eliminate the need to re-do something you did in the rendered callback, since the DOM and the identity of its objects (and all the beautiful jQuery effects) are still intact. So all that is left to re-do is something that only would depend on the particular reactive data-change, which is why there is the Deps.autorun().

In your particular example, your "logRender" function did not have any reactive dependencies, but if you add some and put it into the Deps.autorun(), it will be reliably re-run whenever the dependency changes.

As a conclusion, Meteor 0.7.x and below drove us to make the mistake of treating the "rendered" callback function as a general purpose autorun function, which is why we are running into trouble now and have to fix our apps.



回答2:

As noted in the comments, this is a indeed a design change with Meteor.

Prior to Meteor 0.8, a template was a function that generated HTML. This function would be re-computed whenever any of its reactive dependencies changed, which resulted in a recreation of all the DOM nodes generated by the template (apart from any sub-templates or isolated nodes). Whenever this re-draw happened, the rendered callback was triggered.

This behavior creates quite a performance hit because it requires re-rendering of potentially a lot of HTML, including for identifiers and helpers depending on data that hadn't changed. Additionally, it made it difficult to use other libraries like jQuery to modify the DOM elements that were created, because Meteor basically had control of the entire process and the jQuery code would have to be carefully re-run each time.

Meteor 0.8 fixes this by only rendering the pieces of the DOM that have actually changed, down to the granularity of the identifiers in your template - it is much more fine-grained. As a result, the template's rendered callback is only triggered once when your template hits the page, and is never called again afterward. This solves a lot of performance issues and allows jQuery and other DOM manipulations to work seamlessly with Meteor, but also means that you won't get the automatic callback signalling when something has changed. You can, however, achieve this with helpers that use reactive variables for specific things that change.

For a more detailed listing of how Spacebars, the new Handlebars replacement, works, see https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md

See also the new documentation about the rendered callback: http://docs.meteor.com/#template_rendered



回答3:

So, I was doing a lot of digging yesterday to try and figure out basically the exact same issues you are having. I am still digging, but I did come across this Devshop Talk about Integrating Other Clientside JS Libraries. In it Ted Blackman describes a package he made to trigger events when a Session variable changed. It sounds like what you need. This talk was given prior to 0.8.0 so I am not sure how the package would be effected, but it might be worth a shot.

Devshop Talk - https://www.youtube.com/watch?v=NdBPY98o6eM

Session Extras - https://atmospherejs.com/package/session-extras

Event Horizon - https://atmospherejs.com/package/event-horizon