mithril.js: Recycling heuristics have false positives and false negatives

Currently, vnodes are added to a recycling pool if:

  1. they are not a component (typeof vnode.tag === "string")
  2. they are not a fragment or trusted (vnode.domSize == null)
  3. there are no hooks that can modify vnode.dom in vnode.attrs, at the time of removal

This excludes nodes that could be recycled (fragments and components), and includes nodes that may have had problematic vnode.attrs in the past.

These heuristics are only applied to vnode that are detached (those where onbeforeremove can fire). However, once a vnode has joined the pool, its children are recycled as well even if they don’t meet those criteria, thus possibly recycling DOM nodes that have been modified in the hooks.

Proposed solution:

  • Add a vnode.reuse = true field.
  • Set it to false for trusted nodes and when a problematic hook fires (by modifying initLifecycle() and friends)
  • Only add nodes that have vnode.reuse to the pool;
  • In updateNodes(), when recycling === true, verify that vnode.reuse === true as well.

AFAICT these heuristics would allow to recycle components, fragments and don’t have the holes described above.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 3
  • Comments: 24 (21 by maintainers)

Commits related to this issue

Most upvoted comments

Ok I tried to trim down my scenario to the bare bones and I was able to recreate the issue. You’ll need to put a breakpoint in the removeNode function to see it happening, since measuring it in-place with an onremove event stops the issue from happening. That could actually be a temporary workaround when problem nodes are identified.

It happens when columns in a table shift around. To see the issue, add a column, then remove the first column. Calls to redraw will hit removeNode several times despite no difference in the vnode tree. Note that “Keyed Table” is the thing I’m actually trying to draw; everything else is there to help diagnose the issue.

https://jsfiddle.net/9myxqwha/2/

Of course it’s not actually clear that mithril is recreating any DOM here, but in my situation I think it’s responsible for at least a forced reflow.

Note that we shouldn’t pool custom elements by default, since they aren’t purely host objects (they do usually execute JavaScript).