Building a Simple Virtual DOM from Scratch

Jason Yu on December 05, 2018

I gave a live-coding talk last week at the Manchester Web Meetup #4. I built a virtual DOM from scratch in less than an hour during the talk. ... [Read Full]
markdown guide
 

This is the kind of article that makes me feel so Lucky to be alive at this moment and be part of this code side of the world which is filled with extremely passionate and knowledgeable people who really make efforts to share their knowledge with others.

It's because of engineers like you that it's actually approachable to not only learn to code, but learn to make amazing things.

Forgive me for saying so much, I just happen to really appreciate this, even if I'm not thinking of building a Vdom right now.. Knowing how things work behind the wheel feels very good.

Thank you.

 

It's because of readers like you that make me feel motivated to keep writing! Your comment almost made me teared 😭! Thank you so much!!!! β™₯️β™₯️β™₯️

 
 

Jason, I'm out of words to express my admiration for this tutorial! You've taken a modern concept that is on fire and considered out of reach of mere mortals, and made it accessible. I used to think that the virtual DOM was some kind of evil magic I'd never understand, but now I can confidently say that I do. That said, this was a really heavy read and tripped me up several times, but I'm really glad I pushed through. I do have a question (or two) bothering me; can I post it as a separate comment here?

Once again, thank you so much for the amazing work! πŸ™πŸ˜‡πŸ€©

 

Thank you so much for your kind words! I am very very glad to see you enjoying the post!

I used to think that the virtual DOM was some kind of evil magic I'd never understand, but now I can confidently say that I do.

This is one of the main reasons why I did this topic!! I am glad this presentation achieved its goal!

Please ask the questions! Would be nice if I can clear things further!

 

Cool! So, I see the use of recursion in the code (diff() calls diffChildren(), which calls diff()), and once I started looking for the worst case, my head started to spin. Now, the logic reaches the point you marked (A) when the tag names are same, which makes me wonder what if we're stuck with a structure that's all same-name tags all the way? Let's say something like this:

<!-- HTML for first virtual DOM -->
<div class="tree1-level0">
  <div class="tree1-level1">
    <div class="tree1-level2">
      <div class="tree1-level3">
        "Whee!"
      </div>
    </div>
  </div>
</div>

<!-- HTML for second virtual DOM -->
<div class="tree2-level0">
  <div class="tree2-level1">
    <div class="tree2-level2">
      <div class="tree2-level3">
        "Whoa!"
      </div>
    </div>
  </div>
</div>

So the first time we call diff(), it will call diffChildren(), which will call diff(), which will call diffChildren() down the chain, and so on. At this point, my brain circuits get fried and I can't figure out where this is going. πŸ˜›

Care to explain a little? πŸ™‚

Sure. You have stepped into the common trap of thinking recursion recursively. Your brain will stack-overflow first before the computer does haha.

The reason why you are so confused is because you are lacking the "leap of faith" for recursion. You try to figure out what is happening, then you look into the function; it calls itself, and u look into the function again... Then you are lost.

All you need is faith!

The first thing is to define what diff and diffChildren do. I made it very clear for diff.

Imagine we have a function diff (oldVTree, newVTree) which calculate the differences between the two virtual trees; return a patch($tree) function that takes in the real DOM of oldVTree and perform appropriate operations to the real DOM to make the real DOM looks like newVTree.

So the idea is, you know diff will somehow call itself again at some point. And when this happens, all you need is the "leap of faith"; believe that diff will work as you wish! Don't look into diff again to try to figure things out! Just think about what diff should do and return. By our definition, it will return a patch! So just assume the recursive calls to diff will work and read on.

Teaching recursion using this example is a bit hard, have a look at this article which I explained very clearly how you could obtain faith in recursion.

The leap of faith takes some practice to get use to it. If you want some exercise, I am happy to give you some challenge and guide you through them. Feel free to DM me on twitter: @ycmjason

First off, five points for the funny poster! πŸ€­πŸ€—

Now, I do have some experience with recursion, and I understand that one shouldn't try to trace it all the way down the rabbit hole, but this is just not done:

Don't look into diff again to try to figure things out! Just think about what diff should do and return. By our definition, it will return a patch!

The example you linked to didn't help me too much, for some reason. Can you please point out what the base case in this diff function is? πŸ€”

I am sorry that my explanation didn't help. :(

diff has two base cases:

  1. If the new node is undefined
  2. If the new and old nodes are of different types. This could be either of the cases below:
    1. one of the node is a TextNode while the other one is an ElementNode
    2. Both are ElementNode but with different tag.

In fact, all my base cases are defined in a guard clause. This means that all the return statement before the last return can be considered as base case.

Oops, I just realised there is one more, which is when there is no children in the node. But I didn't explicitly deal with that case as it will be automatically dealt with in the for loop in diffChildren

I am sorry that my explanation didn't help. :(

That's because the (I forget the name of the linked example . . . flatten()?) the second example is just a single function calling itself, whereas in the vDOM case, we have diff calling diffChildren calling diff, and so on, so it's harder to reason about.

This means that all the return statement before the last return can be considered as base case.

I see that, and what I did this time was write out a two-depth example and trace it by hand. My head still went for a spin, but I kind of see how this is supposed to work. I think I'll leave it at that for now. πŸ˜‡

Now, on to a really important question: did you write this code simply based on the "leap of faith"? Is this how algorithms like merge sort and quick were written? On leaps of faith? I'm having a really hard time believing that! Is leap of faith good enough for serious professional/interview problems? πŸ€”

All said and done, this exercise has reminded me how badly I need to do a course in algorithms, which I'm going to do over the next few days. So, for that, too, thanks a lot! πŸ˜‡πŸ˜‡

did you write this code simply based on the "leap of faith"?

Yes. Totally based on the leap of faith. It always work! It's the very important thing you need when dealing with recursion.

Is this how algorithms like merge sort and quick were written?

Well, merge sort and quick sort if written in a recursive way, can be reasoned about using the "leap of faith" for sure. Whether or not the original Author has the leap of faith there is noway to find out. πŸ˜‚πŸ˜‚

Is leap of faith good enough for serious professional/interview problems?

Leap of faith will definitely work in professional and interview problems. It's just a mindset you should have when writing recursive solutions, not really a method. Once you do more recursion, you will become confident enough to hold that faith all time.

 

Hey, really great write up. I missed the talk at Manchester Web Meetup, but wanted to see this talk, when I first learned React a few years I looked at how the Virtual DOM was put together and did a similar createElement/mount function pair, but I think the real power on the Virtual DOM, especially when used in React is the diffing mechanism. I didnt attempt that at the time, but I think what you have put above is great because I think it helps show why/how frameworks like React update themselves in reaction to changes and you do it with understandable and clean code, awesome :D

 

Thank you so much! Writing clean and elegant code is my passion. I tried to keep things clean so that people can understand it.

 

I'm the same, I think there is a lot to be gained from clean code, not just for yourself, but for other developers that do (or will) work with it too :) Doing it in example/tutorial code even more so as it improves the value and quality of the article, like you have here :)

I am so glad I found someone who appreciate this. ❀❀

 

Very good job. Thank you for a thorough guide!
Actually, there is a typo in your code within the post.

  const additionalPatches = [];
  for (const additionalVChild of newVChildren.slice(oldVChildren.length)) {
    additionalPatches.push($node => {
      $node.appendChild(render(newVChildren));
      return $node;
    });
  }

Inside the function that we put into additionalPatches we should render not the newVChildren but the additionalVChild.
This code appears in the first example in the explanation of diffChildren function.

 

You are a hero! Thanks for pointing that out! πŸŽ‰πŸŽ‰πŸŽ‰ I'll change it as soon as possible!

 

Hi,

Thanks a lot for the article, very nicely explained.
How would you recommend to handle events on the virtual nodes (e.g. add a addEventListener on a node that will influence another node rendering)?

 

since many people asked. i might write an eps 2 that could cover this.

 

Please do, if you haven't already. I enjoyed your article btw, pinned on reading list.

Currently, I have the following somewhat working:

render.ts
if (events) {
events.map(([type, event]) => {
$element.addEventListener(type, event)
})
}

index.ts
attrs: {...},
events: [['click',() => {console.log('event handled')}]]

It seems to work and not to propagate upwards. Not sure about dynamically rendering nested elements or w/e. Thoughts?

 

I could attend to the talk and it was perfect. I really appreciate and value the ability to share knowledge on such an interesting and cutting-edge topic as this. Thanks mate ;)

 

❀❀❀ thank you so much!!!!

 

share the post if you liked it! spread the knowledge! :)

 

Halfway through the article but you are real genius .Great contribution .

 

Thank you!!! Thanks for taking the time to read through this. This is a very very long article I know. Should have made this a series of articles really haha.

 

Any way to get in touch with you Jason .Would love to learn things from you .

 

really appreciate your sharing, I want to translate this article to Chinese and share to other people in China, I wonder if you agree me to do that, thank you so much。

 

That's a great idea. As long as you credit me, feel free! Please let me proof read before you post it! :)

 
code of conduct - report abuse