Thunks allow the user to take control of the diff'ing process for a specific dom tree, usually to avoid doing calculations you know are unneeded, such as diff'ing a tree you know hasn't changed.
A Thunk needs to be an object with two keys
type
must be the string "Thunk"
render
Function that returns a VNode, Widget, or VText.
// Boilerplate Thunk
var Thunk = function (){}
Thunk.prototype.type = "Thunk"
Thunk.prototype.render = function(previous){}
When diff is run, the render method is passed a single argument.
previous
The previous VNode, Thunk, Widget, or VText that the Thunk is being diffed against
When render
is called by diff
, it will create a cache of whatever it returns in the key vnode
. When implementing the Thunk interface, don't define a vnode
key, as it will be overwritten! You should use the vnode
property when render is provided with the previous
argument, and you'd like to return the cached copy to prevent a Thunk from re-rendering. We give an example of this in the ConstantlyThunk implementation below.
Here we implement a simple Thunk called ConstantlyThunk. Any instance of ConstantlyThunk that gets diffed with another instance of ConstantlyThunk will return the value of the previous ConstantlyThunk, preventing a patch from being generated.
// Only the first instance of this Thunk will be shown.
// Once its been rendered, any other instances of ConstantlyThunk that
// diff with it will return a reference to the cached value that is automatically
// assigned to the "vnode" property
var ConstantlyThunk = function(greeting){
this.greeting = greeting
}
ConstantlyThunk.prototype.type = "Thunk"
ConstantlyThunk.prototype.render = function(previous) {
if (previous && previous.vnode) {
return previous.vnode
} else {
return h('div', ["Constantly "+ this.greeting])
}
}
Thunk1 = new ConstantlyThunk("Thunk!")
Thunk2 = new ConstantlyThunk("I won't be rendered!")
thunkElem = createElement(Thunk1)
document.body.appendChild(thunkElem)
// No new patches are generated
patches = diff(Thunk1, Thunk2)
// Nothing will happen
patch(thunkElem, patches)
Here we implement GenericThunk, a simplified version of Raynos' immutable-thunk
It takes a rendering function, a comparison function, and a state. When it's being diffed vs. another instance of GenericThunk, it will use the comparison function to look at the new state and old state, and decide if it's ok to update.
var diff = require("virtual-dom").diff
var patch = require("virtual-dom").patch
var h = require("virtual-dom").h
var createElement = require("virtual-dom").create
// Our GenericThunk will take 3 arguments
// renderFn is the function that will generate the VNode
// cmpFn is the function that will be used to compare state to see if an update is necessary.
// returns true if the update should re-render, and false if it should use the previous render
// state is a value that holds the information cmpFn will use to decide whether we should
// update the Thunk or not
var GenericThunk = function(renderFn, cmpFn, state) {
this.renderFn = renderFn
this.cmpFn = cmpFn
this.state = state
}
GenericThunk.prototype.type = "Thunk"
GenericThunk.prototype.render = function(previous) {
// The first time the Thunk renders, there will be no previous state
var previousState = previous ? previous.state : null
// We run the comparison function to see if the state has changed enough
// for us to re-render. If it returns truthy, then we call the render
// function to give us a new VNode
if ((!previousState || !this.state) || this.cmpFn(previousState, this.state)) {
return this.renderFn(previous, this)
} else {
// vnode will be set automatically when a thunk has been created
// it contains the VNode, VText, Thunk, or Widget generated by
// our render function.
return previous.vnode
}
}
// The function we'll pass to GenericThunk to see if the color has changed
// We return a true value if the colors are different
var titleCompare = function(previousState, currentState) {
return previousState.color !== currentState.color
}
// The function that builds our title when we detect that
// the color has changed
var titleRender = function(previousThunk, currentThunk) {
var currentColor = currentThunk.state.color
return h("h1", { style: {color: currentColor}}, ["Hello, I'm a title colored " + currentColor])
}
var GreenColoredThunk = new GenericThunk(titleRender, titleCompare, { color: "green"})
var BlueColoredThunk = new GenericThunk(titleRender, titleCompare, { color: "blue"})
var currentNode = GreenColoredThunk
var rootNode = createElement(currentNode)
// A simple function to diff your thunks, and patch the dom
var update = function(nextNode) {
var patches = diff(currentNode, nextNode)
rootNode = patch(rootNode, patches)
currentNode = nextNode
}
document.body.appendChild(rootNode)
// We schedule a couple updates
// Our first update will see that our color hasn't changed, and will stop comparing at that point,
// instead returning a reference to GreenColoredThunk.vnode
setTimeout(function() {
update(new GenericThunk(titleRender, titleCompare, { color: "green" }))
},
1000)
// In our second update, BlueColoredThunk will see that state.color has changed,
// and will return a new VNode, generating a patch
setTimeout(function() {
update(BlueColoredThunk)
},
2000)
Raynos has created a library for making Thunks at vdom-thunk.