(Related: #2688)
Old proposal
### Description
Replace these idioms:
const Comp = {
oncreate(vnode) {
$(vnode.dom).modal()
if (vnode.attrs.show) {
$(vnode.dom).modal("show")
}
},
onupdate(vnode) {
if (vnode.attrs.show) {
$(vnode.dom).modal("show")
} else {
$(vnode.dom).modal("hide")
}
},
onremove(vnode) {
$(vnode.dom).modal("dispose")
},
view() {
return m("div.bs-modal")
}
}
return m("div.bs-modal", {
oncreate(vnode) {
$(vnode.dom).modal()
if (vnode.attrs.show) {
$(vnode.dom).modal("show")
}
},
onupdate(vnode) {
if (vnode.attrs.show) {
$(vnode.dom).modal("show")
} else {
$(vnode.dom).modal("hide")
}
},
onremove(vnode) {
$(vnode.dom).modal("dispose")
},
})
With this idiom:
return m("div.bs-modal",
m.access((dom, isInit) => {
if (isInit) {
$(dom).modal()
}
if (vnode.attrs.show) {
$(dom).modal("show")
} else {
$(dom).modal("hide")
}
}),
m.release(dom => {
$(dom).modal("destroy")
})
)
m.access(...) would have a tag of "+", and m.release(...) would have a tag of "-".
Why
It's simpler and more flexible, and when combined with #2688, you also get the ability to diff and patch based on attributes for free, which isn't possible with the current framework.
Oh, and it will also provide a few perf benefits - this blog post can give you an idea why for part of it, but also not accessing dynamic properties that may or may not exist can also help a lot.
I suspect a 5-10% perf increase and a significant library size decrease out of this.
Possible Implementation
- In
createNode and updateNode, if vnode.tag === "+", schedule the callback with parent and isInit set accordingly (true in createNode, false in updateNode) and treat it otherwise as equivalent to undefined.
- In
createNode and updateNode, if vnode.tag === "-", treat it as equivalent to undefined.
- In
removeNode, ignore vnode.tag === "+" and invoke the callback if vnode.tag === "-".
- Merge
vnode.dom with vnode.state in the vnode object, and use it for both element references, component state, and access/remove callbacks.
- Strip out all the lifecycle hook logic and centralize it to those two vnodes.
Mithril version:
Platform and OS:
Project:
Is this something you're interested in implementing yourself? Yes
Description
Replace these idioms:
const Comp = {
oncreate({dom, attrs}) {
$(dom).modal()
if (attrs.show) {
$(dom).modal("show")
}
},
onupdate({dom, attrs}) {
if (attrs.show) {
$(dom).modal("show")
} else {
$(dom).modal("hide")
}
},
onbeforeremove({dom, attrs}) {
return new Promise(resolve => {
if (!attrs.show) return
$(dom).modal("hide")
$(vnode.dom).one("hidden.bs.modal", resolve)
})
},
onremove({dom}) {
$(dom).modal("dispose")
},
view({vnode}) {
return m("div.bs-modal", children)
}
}
const Comp = {
view({attrs, children}) {
return m("div.bs-modal", {
oncreate({dom}) {
$(dom).modal({show: attrs.show, keyboard: false})
},
onupdate({dom}) {
if (attrs.show) {
$(dom).modal("show")
} else {
$(dom).modal("hide")
}
},
onbeforeremove({dom}) {
if (!attrs.show) return
$(dom).modal("hide")
return new Promise(resolve => {
$(dom).one("hidden.bs.modal", resolve)
})
},
onremove({dom}) {
$(dom).modal("dispose")
},
}, children)
}
}
With this idiom:
// With #2690
function Comp(ctx) {
let isInitial = true
return () => m.fragment({
afterRender([dom]) {
if (isInitial) {
$(dom).modal({show: attrs.show, keyboard: false})
} else if (ctx.attrs.show) {
$(dom).modal("show")
} else {
$(dom).modal("hide")
}
isInitial = false
},
beforeRemove([dom]) {
if (!attrs.show) return
$(dom).modal("hide")
return new Promise(resolve => {
$(dom).one("hidden.bs.modal", resolve)
})
},
afterRemove([dom]) {
$(dom).modal("dispose")
},
}, m("div.bs-modal", ctx.attrs.children))
}
// With current component API
const Comp = {
oninit() { this.isInitial = true },
view({attrs, children}) {
return m.fragment({
afterRender([dom]) {
if (this.isInitial) {
$(dom).modal({show: attrs.show, keyboard: false})
} else if (attrs.show) {
$(dom).modal("show")
} else {
$(dom).modal("hide")
}
},
beforeRemove([dom]) {
if (!attrs.show) return
$(dom).modal("hide")
return new Promise(resolve => {
$(dom).one("hidden.bs.modal", resolve)
})
},
afterRemove([dom]) {
$(dom).modal("dispose")
},
}, m("div.bs-modal", children))
}
}
m.fragment(...) would have the same tag it normally does. This would also by side effect mean m.censor just becomes m.censor = ({key, ...rest}) => rest absent user-provided keys, though we could just as easily strip key internally like React does (the smart thing to do IMHO) and not need it anymore.
The parameter of each is actually an array of DOM nodes. And while it's technically less efficient, it's likely to be minor in practice as significant DOM work is rare, and we're talking small numbers compared to a significantly more involved algorithm plus the native browser's own updating mechanisms plus all the adapting logic between JS and native just to invoke the browser APIs - a single array allocation is nothing compared to that, just slightly more GC churn (as it's retained for a decent amount of time).
I'm leaving out onbeforeupdate from this proposal as that's covered by #2688 and is being discussed separately.
Why
It's simpler and more flexible, and when combined with #2688, you also get the ability to diff and patch based on attributes for free, which isn't possible with the current framework.
Oh, and it will also provide a few perf benefits:
- If you read this blog post, you'll find that polymorphic calls are slow. This doesn't strictly eliminate that, but it does make it possible to remove that for the common case of components.
- Not accessing dynamic methods that may or may not exist for every element, fragment, and component vnode can also go a long way. This in effect centralizes that to only a specific special fragment type that is only rarely used.
I suspect a 5-10% perf increase and a mild library size decrease out of this, based on my experience with Mithril and its performance profile.
Possible Implementation
- Move this line to after this line.
- Move these lines to after this line.
- Delete these lines from
createComponent and these lines from updateComponent.
- Change
updateLifecycle to instead schedule source.afterRender with an array of vnode.domSize nodes starting from vnode.dom and continuing through elem.nextSibling.
- Remove this function and the two places it's called.
- Change these two lines of
onremove to instead invoke vnode.attrs.afterRemove and only if vnode.tag === "[" && vnode.attrs != null && typeof vnode.attrs.afterRemove === "function".
This would also make it such that vnode.state and vnode.dom are mutually exclusive, so we could merge those accordingly.
Open Questions
- Should we do this?
- Is there a better way to do it?
(Related: #2688)
Old proposal
### Description Replace these idioms:With this idiom:
m.access(...)would have a tag of"+", andm.release(...)would have a tag of"-".Why
It's simpler and more flexible, and when combined with #2688, you also get the ability to diff and patch based on attributes for free, which isn't possible with the current framework.
Oh, and it will also provide a few perf benefits - this blog post can give you an idea why for part of it, but also not accessing dynamic properties that may or may not exist can also help a lot.
I suspect a 5-10% perf increase and a significant library size decrease out of this.
Possible Implementation
createNodeandupdateNode, ifvnode.tag === "+", schedule the callback withparentandisInitset accordingly (trueincreateNode,falseinupdateNode) and treat it otherwise as equivalent toundefined.createNodeandupdateNode, ifvnode.tag === "-", treat it as equivalent toundefined.removeNode, ignorevnode.tag === "+"and invoke the callback ifvnode.tag === "-".vnode.domwithvnode.statein the vnode object, and use it for both element references, component state, and access/remove callbacks.Mithril version:
Platform and OS:
Project:
Is this something you're interested in implementing yourself? Yes
Description
Replace these idioms:
With this idiom:
m.fragment(...)would have the same tag it normally does. This would also by side effect meanm.censorjust becomesm.censor = ({key, ...rest}) => restabsent user-provided keys, though we could just as easily stripkeyinternally like React does (the smart thing to do IMHO) and not need it anymore.The parameter of each is actually an array of DOM nodes. And while it's technically less efficient, it's likely to be minor in practice as significant DOM work is rare, and we're talking small numbers compared to a significantly more involved algorithm plus the native browser's own updating mechanisms plus all the adapting logic between JS and native just to invoke the browser APIs - a single array allocation is nothing compared to that, just slightly more GC churn (as it's retained for a decent amount of time).
I'm leaving out
onbeforeupdatefrom this proposal as that's covered by #2688 and is being discussed separately.Why
It's simpler and more flexible, and when combined with #2688, you also get the ability to diff and patch based on attributes for free, which isn't possible with the current framework.
Oh, and it will also provide a few perf benefits:
I suspect a 5-10% perf increase and a mild library size decrease out of this, based on my experience with Mithril and its performance profile.
Possible Implementation
createComponentand these lines fromupdateComponent.updateLifecycleto instead schedulesource.afterRenderwith an array ofvnode.domSizenodes starting fromvnode.domand continuing throughelem.nextSibling.onremoveto instead invokevnode.attrs.afterRemoveand only ifvnode.tag === "[" && vnode.attrs != null && typeof vnode.attrs.afterRemove === "function".This would also make it such that
vnode.stateandvnode.domare mutually exclusive, so we could merge those accordingly.Open Questions