Memoization
Act doesn't dictate how you should avoid rerenders. Nevertheless, it encourages
the usage os memoization. If you're used to React's componentShouldUpdate
,
you may read the comparison here.
Memoization is an age old technique - specially in functional programming - to cache pure functions. Basically it wraps a given function and stores all given inputs and outputs. If some newly passed inputs match some stored inputs it simply returns the stored value, without calling the original function.
This technique is not a panacea, and will only help your performance if the
cost of storing and searching elements in a hash tabe is better than the actual
amount of time your function takes to execute. And this is specially
problematic in JavaScript, given the difficulty of comparing deeply nested
data. There's no simple rule of thumb to know when to use it, so I encourage
you to use benchmark
and see for yourself.
I've done some benchmarks in my machine (a MacBook, 2.6 GHz Intel Core i5, with 8 GB of RAM) and apparently memoize starts being useful when a function doesn't do more than 1.5M operations per second. But again, this is a dummy rule, since its usefuless may depend on much more things, like the function's complexity.
Here's a naive implementation and usage example of memoize:
const memoize = (fn) => {
const cache = []
return (...args) => {
const cachedReturn = cache[JSON.stringify(args)]
if (cachedReturn) return cachedReturn
return cache[JSON.stringify(args)] = fn(...args)
}
}
const sum = (x, y) => {
console.log(`called with x=${x} y=${y}`)
return x + y
}
const memoizedSum = memoize(sum)
memoizedSum(1, 1) // called with x=1 y=1 => 2
memoizedSum(1, 1) // => 2
memoizedSum(2, 1) // called with x=2 y=1 => 3
memoizedSum(2, 1) // => 3
Luckily there's a not-so-naive implementation of memoize on Ramda library. So you can simply:
import memoize from 'ramda/src/memoize'
const memoizedFn = memoize(fn)
Since Act's views are simply JSON – even if it contains some functions there shouldn't be any cyclic object value on them –, Ramda's memoize is supposed to work fine. All you have to do is:
// before
const view = (model) => (
['div', someOp(model)]
)
// after
const view = memoize((model) => (
['div', someOp(model)]
))
This, of course, assuming the view
function is pure – so therefore someOp
must also be pure.
A final note is that memoization can be used for any pure function, so you can use it not only for views, but also for processes (again, if pure) and other interesting things.