Html.Lazy
If you are creating an HTML file, you would write HTML directly like this:
You can think of this as producing some DOM data structure behind the scenes:
The black boxes represent heavy-weight DOM objects with hundreds of attributes. And when any of them change, it can trigger expensive renders and reflows of page content.
What is Virtual DOM?
If you are creating an Elm file, you would use elm/html
to write something like this:
You can think of viewChairAlts ["seiza","chabudai"]
as producing some “Virtual DOM” data structure behind the scenes:
The white boxes represent light-weight JavaScript objects. They only have the attributes you specify. Their creation can never cause renders or reflows. Point is, compared to DOM nodes, these are much cheaper to allocate!
If we are always working with these virtual nodes in Elm, how does it get converted to the DOM we see on screen? When an Elm program starts, it goes like this:
- Call
view
to get the initial virtual nodes.
Now that we have virtual nodes, we make an exact replica in the real DOM:
Great! But what about when things change? Redoing the whole DOM on every frame does not work, so what do we do instead?
Diffing
Once we have the initial DOM, we switch to working primarily with virtual nodes instead. Whenever the Model
changes, we run view
again. From there, we “diff” the resulting virtual nodes to figure out how to touch the DOM as little as possible.
So imagine our Model
gets a new chair alternative, and we want to add a new node for it. Behind the scenes, Elm diffs the current virtual nodes and the next virtual nodes to detect any changes:
It noticed that a third li
was added. I marked it in green. Elm now knows exactly how to modify the real DOM to make it match. Just insert that new li
:
This diffing process makes it possible to touch the DOM as little as possible. And if no differences are found, we do not need to touch the DOM at all! So this process helps minimize the renders and reflows that need to happen.
But can we do even less work?
The Html.Lazy
module makes it possible to not even build the virtual nodes! The core idea is the lazy
function:
Going back to our chair example, we called viewChairAlts ["seiza","chabudai"]
, but we could just as easily have called lazy viewChairAlts ["seiza","chabudai"]
instead. The lazy version allocates a single “lazy” node like this:
The node just keeps a reference to the function and arguments. Elm can put the function and arguments together to generate the whole structure if needed, but it is not always needed!
One of the cool things about Elm is the “same input, same output” guarantee for functions. So whenever we run into two “lazy” nodes while diffing virtual nodes, we ask is the function the same? Are the arguments the same? If they are all the same, we know the resulting virtual nodes are the same as well! So we can skip building the virtual nodes entirely! If any of them have changed, we can build the virtual nodes and do a normal diff.
Usage
The ideal place to put a lazy node is at the root of your application. Many applications are set up to have distinct visual regions like headers, sidebars, search results, etc. And when people are messing with one, they are very rarely messing with the others. This creates really natural lines for lazy
calls!
For example, in my TodoMVC implementation, the view
is defined like this:
Notice that the text input, entries, and controls are all in separate lazy nodes. So I can type however many characters I want in the input without ever building virtual nodes for the entries or controls. They are not changing! So the first tip is try to use lazy nodes at the root of your application.
It can also be useful to use lazy in long lists of items. In the TodoMVC app, it is all about adding entries to your todo list. You could conceivably have hundreds of entries, but they change very infrequently. This is a great candidate for laziness! By switching viewEntry entry
to lazy viewEntry entry
we can skip a bunch of allocation that is very rarely useful. So the second tip is try to use lazy nodes on repeated structures where each individual item changes infrequently.
Touching the DOM is way more expensive than anything that happens in a normal user interface. Based on my benchmarking, you can do whatever you want with fancy data structures, but in the end it only matters how much you successfully use lazy
.
On the next page, we will learn a technique to use lazy
even more!