What if... Virtual DOM... but by hand?
aha ha, just kidding...
unless.. ?
Microview is a tiny library for writing efficient data-driven DOM rendering logic by hand. DOM writes are driven by a pure data model. Using Microview, you can freely "bash the dom". Writes will be batched — they'll only happen once per animationframe, and only if the data model changes.
Here's a simple click counter example, written using Microview.
import {creator, renderer} from './microview.js'
const createClicker = creator((clicks, handle) => {
const el = document.createElement('div')
el.innerText = `Clicks: ${clicks}`
el.addEventListener('click', handle)
return el
})
const renderClicker = renderer((el, prev, curr, handle) => {
el.innerText = `Clicks: ${curr}`
})
let clicks = 0
const handleClick = event => {
event.preventDefault()
clicks = clicks + 1
renderClicker(clicker, clicks)
}
const clicker = createClicker(clicks, handleClick)
document.querySelector('body').appendChild(clicker)
Let's take a quick look at the key functions:
creator
takes a factory function of (model, handle) => element
, and transforms it so that it will associate the model with the element after it is created. handle
is an extra parameter for passing in event listeners, etc.
renderer
is where most of the magic happens. It takes a write function of (el, prev, curr, handle) => null
and transforms it. When you call the resulting function with an element and a new state, it retrieves the already associated model, and checks to see if the previous and current model disagree. If they do, it calls the write function on the next animation frame, passing in prev
and curr
, which are the previous and current versions of the model. You can call the render function as many times as you like during a single frame. Only the last write within a frame will be scheduled, and the write will only occur if the model has actually changed.
Microview is deliberately unopinionated about how state should be updated. Its job is only to compare a prev
and curr
model, and schedule renders accordingly. How you choose to manage model updates is up to you. This means you can use Microview with things like Redux, the Elm app architecture pattern, or whatever.
Microview is composable. You can nest create
and write
calls inside creators and renderers to build up nested DOM components that will only update when the corresponding sub-component of the model has updated. (Note that while top-level writers may use renderer
, nested writers should use writer
to perform immediate writes during the same frame.)
The full suite of view functions:
creator(createf)
: decorates a create function so it associates the model.create(writef, model, handle)
: same ascreator
, but allows you to pass in an undecorated create function.upgrader(writef)
: decorates a first-write function so that it will associate the model after performing a first write. You can use this instead ofcreator
to define "upgrade" functions for existing elements.upgrade(writef, el, model, handle)
: same asupgrader
but allows you to pass in an undecorated write function.writer(writef)
: decorates a update-write function so that it will retreive the previously associated model. Same asrenderer
, but performs an immediate write, instead of waiting for next frame.write(writef, el, model, handle)
same aswriter
but allows you to pass in an undecorated write function.renderer(writef)
: decorates an update-write function so that it will retreive the previously associated model, and makes sure to only schedule a single write per frame.render(writef, el, model, handle)
: same asrenderer
, but allows you to pass in an undecorated write function.isDirty(el, model)
: check if an element is out of sync with a given model. You shouldn't often need to use this directly.
I like it. I had planned to build something almost identical a ways back when working out this framework free TodoMVC -- https://github.com/tantaman/fk-your-frameworks-todomvc
--
Unrelated -- love the ideas on subconscious and subtext. Docs definitely have to be broken apart at the block level to enable any sort of re-use of their contents in other contexts.
I think I've read every post you've written on substack more than once 😅