mithril.js
mithril.js copied to clipboard
Hooks timing
Hi all, here follows some brain storming about the order in which the hooks fire (this may be a dupe, because some of these ideas have been brewing for a while... Kudos to @nraibaud for a previous discussion on the topic).
Currently, the following statements are true:
For a single component, the following sequences run:
-
when created:
oninitview- the DOM node is appended
- (
oninitandviewfor children, and corresponding DOM additions)
- (
oncreate(first for the current nodes then its children)
-
when updated:
onbeforeupdateview- the DOM is mutated
- (
onbeforeupdateandviewfor children, and corresponding DOM mutations)
- (
onupdate(first for the current nodes then its children)
-
when detached:
onbeforeremoveonremove, first for the node then its children.- the DOM node is detached.
However, nothing guarantees that the hooks of a list of nodes will fire in source order. Actually, for keyed diff, the relative order is all over the place.
In other words, the hooks all fire on the descending edge of a depth-first traversal, with no order specified in a given nodes list. In a keyed list, going from ABC to ADC, B.onbeforeremove could fire before or after D.oninit (I know it sometimes happens after).
It may be nice to
- have the hooks fire in source order, even in keyed mode,
- have the post-view hooks fire on the ascending edge of the traversal (
oncreate/onupdatefiring on the children before they do on the parent). - guarantee that
B.onbeforeremovefire beforeD.oncreate. Not sure where a synchornousB.onremovewould go relative toD.oninit,D.viewand theDDOM creation...
Some of that will probably come at a cost, perf-wise and/or size-wise.
would be good to show what specific userland problem this solves (or link to prior discussion for context)
For 1) I've had a case where I needed to put the focus on the first invalid field in a list. It was thankfully not a keyed list, ensuring that the nodes were traversed in order, so I could just use { invalidField && ...{onupdate: focus.setIfNeeded} } in the attrs. focus was a stateful helper like this:
const focus = {
needed: <boolean> false,
setIfNeeded: function setIfNeeded({dom}: VdomHTML<any, any>) {
if (focus.needed) {
setTimeout(() => dom.focus(), 100) // that timeout isn't needed anymore
focus.needed = false
}
}
}
Had it been a keyed list I would have had to make two passes to determine where to put the focus.
For 2) you may want to get the dimensions of an element whose children mess with the DOM directly. So the post-view hooks must fire after the ones of the children (and one must sync the computed properties first, off course). I don't have a scenario where the opposite order would be useful.
For 3) One may expect the old node to be removed before the new one is created, that was my expectation at some early point, at least. I don't remember why I wanted to rely on it though.
For my part i did a hook named phase. This can be registred on "up" or "down" phase.
@pygy So what's the status on this in light of #2156?