-
-
Notifications
You must be signed in to change notification settings - Fork 929
Description
(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
createNodeandupdateNode, ifvnode.tag === "+", schedule the callback withparentandisInitset accordingly (trueincreateNode,falseinupdateNode) and treat it otherwise as equivalent toundefined. - In
createNodeandupdateNode, ifvnode.tag === "-", treat it as equivalent toundefined. - In
removeNode, ignorevnode.tag === "+"and invoke the callback ifvnode.tag === "-". - Merge
vnode.domwithvnode.statein 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
createComponentand these lines fromupdateComponent. - Change
updateLifecycleto instead schedulesource.afterRenderwith an array ofvnode.domSizenodes starting fromvnode.domand continuing throughelem.nextSibling. - Remove this function and the two places it's called.
- Change these two lines of
onremoveto instead invokevnode.attrs.afterRemoveand only ifvnode.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?
Metadata
Metadata
Assignees
Labels
Type
Projects
Status