base-element
base-element copied to clipboard
combine node.render() + this.afterRender() ?
From this.afterRender():
This method needs to be called when returning a constructed virtual tree.
If the method is mandatory, wouldn't it make sense to always run it internally? What are the use cases for not running this method?
const baseElement = require('base-element')
const html = require('virtual-dom')
const el = baseElement()
// I would prefer this:
el.render(data => html(`<div></div>`))
// over always typing this:
el.render(data => this.afterRender(html(`<div></div>`)))
I'm not sure how the internals are laid out, but passed in function is being bound it might simplify the code and possibly make the interface more resilient. Thanks!
It's only necessary when you use the OO interface and override El.prototype.render. If you go functional, it's not necessary. I'd favor removing the prototype override in a major version bump and requiring everyone to pass in vtree-returning functions.
Hmh, it's kind of confusing to have two interfaces. Maybe it should be split up in two modules / converge into a single interface? Adding es6 classes to the mix complicates things even more, interface-wise.
edit: I realize that this is a bit off the point I raised in my initial point, but it might be a way to further reduce the interface surface of this module.
edit2: @bendrucker thanks for pointing it out; didn't realize it was that way ^^
I think it's confusing to let people totally break the render method and then basically offer a way to easily call the original:
https://github.com/shama/base-element/blob/981433c5b0dea71360d7b2b79b00bb9dada13605/index.js#L27-L32
I think it's reasonable to:
- Expect people to implement a particular method on the prototype that's otherwise a noop
- Allow people to monkey patch things that they want to change
afterRender is a weird mix of the two where you're monkey patching a method that takes a vtree and renders it and being encouraged to instead return a vtree and wrap it in an afterRender call.
The reason it was done that way is so one could deeply nest elements and only the top level element calling render would do the diffing. I'm totally open to removing afterRender if we're still able to detect the top of the render stack to do the diffing there. I just couldn't figure out a way without requiring afterRender to be called by the user's implementation.
Thought about this some more and it's still bugging me as a source of confusion. The functional style is presented as an alternative, but it's not really the same. el.render(callback) will always create a DOM element. You'd need to do el.render = render.
I don't think I actually understand how afterRender is supposed to work. When you this.__BaseElementSig__ not be truthy?
https://github.com/shama/base-element/blob/981433c5b0dea71360d7b2b79b00bb9dada13605/index.js#L28
this.hasOwnProperty('__BaseElementSig__') will be false whenever it is on a prototype.
The primary thing base-element solves is it allows me to create a bunch of modules that can inherit each other. Each of those modules will never need to be upgraded because of a version bump on a framework or another renderer library. They all can run standalone or together, regardless of the version of base-element or any other library.
An example is:
- I create an
class Input {}element. It only validates the value as!empty. - Later I
class PasswordInput extends Input {}which inherits the behavior, hides the input and validatesvalue.length >= 8. - Much later I
class ShowPasswordInput extends PasswordInput {}which inherits the behavior but adds a "✅Show" to toggle whether to hide/show the password value.
Each of these elements call this.afterRender(vtree) in their render() implementation.
If I do let input = new ShowPasswordInput(); input.render(), the render() call will traverse down the prototypes building up my virtual dom tree. When this.afterRender() is called on each prototype, it will not render, only return the tree.
It is only when it hits the top level render() call or the element that hasOwnProperty('__BaseElementSig__') does it do the virtual dom diffing and renders the entire tree.
Without this, each library would need to do the diffing/rendering on every render call and would very likely cause a perf hit implementing this nested architecture. It also makes each element in the nested architecture remain standalone, without needing a separate API or library.
The functional API bypasses this architecture as it isn't aware of where it lives in the render chain (and I don't know a good solution to make it work there). So it runs with every element diffing/rendering.
Which means architecturally, the class API is preferred. The functional API is really convenient to generate elements on the app level or within modules that need or can render more often.
Right, totally didn't think of inheriting prototype.render and that elements would no longer have an own property from the constructor.