Truly reactive programming with Svelte 3.0

May 21, 2019 0 Comments

Truly reactive programming with Svelte 3.0



That title is a bit dramatic, but then again, so is Svelte and the idea behind it. If you don’t know what Svelte is yet, then strap in — you’re about to witness a revolution, and it’s going to be a ride (no pressure on the Svelte team 😅).

Note that this is not a tutorial on how to get started with Svelte. There is already a great step-by-step interactive tutorial by the Svelte team that eases you into the world of reactive programming.

Disclaimers are in order: I’m not a programming rockstar, and I don’t know everything. I’m just very enthusiastic about the innovations being made every day, and I like to talk about them when I can — hence, this article. Take everything I say with a pinch of salt, and please let me know if I say something ridiculous.

Alright, let’s get into it!

Before I get into why I think Svelte is so disruptive, let’s take a look at this tweet from the man, Dan, from a while back and dissect what it actually means:

Uhh, why is it called React then? 🤔

Yet another disclaimer: This article is not meant to bash React in any way. I simply decided to use React as a case study because most people who read this article will have used it at one point or another. It’s just the best example to contrast Svelte against.

What did Dan mean, and what effect does this have on the way we currently write code? To answer this question, let me give you a simplified view of how React works behind the scenes.

When you render a React app, React keeps a copy of the DOM in something called the virtual DOM. The virtual DOM acts as a middleman of sorts between your React code and what your browser paints to the DOM.

Then, when your data changes (maybe you called this.setState, useState), React does a bit of work to determine how to repaint your UI on the screen.

It compares the virtual DOM against the real DOM to determine what changed due to this data update. It then repaints only the parts of the DOM that do not match the new copy in the virtual DOM, eliminating the need to repaint the whole DOM every time something changes.

Now, this is very fast because updating the virtual DOM is a lot cheaper than updating the real DOM, and React only updates the bits and pieces of the real DOM that need to be updated. This article does a much better job of explaining this process.

But there’s something you might have noticed with this implementation. If you don’t tell React that your data has changed (i.e., by calling this.setState or the Hooks equivalent), your virtual DOM will not change, and React will not react (ba dum tss! 🤓).

This is what Dan meant when he said React isn’t fully reactive. React is relying on you to track your app’s data and to tell it when it changes, which is often more work for you.

Svelte is a whole new way of building UI in a blazingly fast, efficient, and truly reactive manner, all without using a virtual DOM in fewer lines of code than you’d write with any other framework or library.

That sounds all nice and good, but how is it different from the myriad of other JavaScript libraries and frameworks out there, you ask? I’ll tell you.

Svelte is not a library. Svelte is not a framework. Rather, Svelte is a compiler that takes in your code and spits out native JavaScript that interacts with your DOM directly with no need for an intermediary.

Wait, what? A compiler? Yes — a compiler. It’s such a bloody good idea that I don’t know why it wasn’t so obvious until now, and I’ll tell you why I think it’s so cool.

Here’s a quote from Rich Harris’ talk at the YGLF 2019 conference:

Svelte 3.0 moves reactivity out of the component API and into the language.

What does that mean? Well, we’ve seen how React (and most other frontend frameworks) requires you to use an API to tell it that data changed (again by calling this.setState or using useState) before it knows to update its virtual DOM.

The need to call this.setState in React (and most other UI frameworks and libraries) means that the reactivity of your app is now tied to a specific API, without which it would be completely unaware of data changes.

Svelte takes another approach to this.

It has taken inspiration from Observable in the way it runs your code. Instead of running your code top to bottom, it runs it in topological order. Look at the snippet of code below, and we’ll go through what it means to run it in topological order.

1. (() => {
2. const square => number => number * number;
4. const secondNumber = square(firstNumber);
5. const firstNumber = 42;
7. console.log(secondNumber);
8. })();

Now, if you run this code top to bottom, you’ll get an error at line 4 because secondNumber is relying on firstNumber, which hasn’t been initialized at that point.

If you run that same code in topological order, you wouldn’t get any errors. How come? The compiler wouldn’t run this code from top to bottom; instead, it would take a look at all the variables and generate a dependency graph (i.e., who needs who first).

In our case, this is a ridiculously simplified look at how a compiler would compile this code topologically.

1. Does this new variable 'square' depend on any other variable?
- it doesn't, so I'll initialize it
2. Does this new variable 'secondNumber' depend on any other variable?
- it depends on 'square' and 'firstNumber'. I already initialized 'square', but I haven't initialized 'firstNumber', which I will do now.
3. OK, I've initialized 'firstNumber'. Now I can initialize 'secondNumber' using 'square' and 'firstNumber'
- Do I have all the variables required to run this console.log statement?
- Yes, so I'll run it.

At first glance, it seems the code is running top to bottom, but take a closer look and you’ll discover that it actually does some jumping around.

When it gets to line 4, the compiler discovers that it doesn’t have firstNumber, so it pauses the execution there and looks through your code to see if you defined it anywhere. Well, we did exactly that on line 5, so it runs line 5 first before going back to line 4 to execute it.

TL;DR: If statement A depends on statement B, then statement B will run first regardless of the order of declaration.

So how does this apply to the way Svelte implements true reactivity? Well, you can label a statement with an identifier in JavaScript, and that operation looks like this: $: foo = bar. All that does is add an identifier called $to the foo = bar statement (an operation that would fail in strict mode if foo wasn’t defined earlier).

So in this case, when Svelte sees any statement prefixed with $:, it knows that the variable on the left derives its value from the variable on the right. We now have a way of binding one variable’s value to another’s.

Reactivity! This means that we’re now using a core part of JavaScript’s API to achieve true reactivity with no need to fiddle around with third-party APIs like this.setState.

This is how it looks in practice:

1. // vanilla js
2. let foo = 10;
3. let bar = foo + 10; // bar is now 20
4. foo = bar // bar is still 20 (no reactivity)
5. bar = foo + 10 // now bar becomes 30

6. // svelte js
7. let foo = 10;
8. $: bar = foo + 10; // bar is now 20
9. foo = 15 // bar is now 25 because it is bound to the value of foo

Notice how in the code above, we didn’t need to reassign bar to the new value of foo — either by doing it directly via bar = foo + 10; or by calling an API method like this.setState({ bar = foo + 10 });. It is handled automatically for us.

This means that when you change foo to equal 15, bar is updated to be 25 automatically, and you don’t have to call an API to update it for you. Svelte already knows.

The compiled version of the Svelte code above looks something like this:

1. ... omitted for brevity ...
2. function instance($$self, $$props, $$invalidate) {
3. let foo = 10; // bar is now 20
4.   $$invalidate('foo', foo = 15) // bar is now 25 because it is bound to the value of foo
5.   let bar;
6.   $$self.$$.update = ($$dirty = { foo: 1 }) => {
7. if ($$ { $$invalidate('bar', bar = foo + 19); }
8. };
9.   return { bar };
10. }
11. ... omitted for brevity ...

Take your time to really study this piece of code above. Really take your time.

Do you see how the update on foo happened before bar was even defined? That’s because the compiler is parsing the Svelte code in topological order instead of a strictly top-down order.

Svelte is reacting on its own to data changes. It doesn’t want you to worry about tracking what changed and when; it knows automatically.

Note: On line 4, bar’s value is not updated until after the next Event Loop, leaving everything nice and tidy.

This allows you to stop worrying about manually updating your state whenever your data changes. You can focus on your logic all day while Svelte helps you to reconcile your UI with your latest state.

Remember how I said Svelte allows you to do so much with fewer lines of code written? I meant it. I’ll show you a simple component in React and its equivalent in Svelte, and you judge for yourself:

17 vs. 29 lines of code

These two apps are completely identical in functionality, but you can see how much more code we had to write in React.js — and don’t even get me started on Angular 😂.

I’m the oldest dev alive 😎

Apart from the Svelte code being more pleasing to the eye, it is also much easier to reason about as there are less moving parts than in the React code. We didn’t need an event handler to update the value of the input element — simply binding the value was enough.

Imagine you were just starting out learning web development. Which code would have confused you more? The one on the left, or the one on the right?

While this might seem like a trivial point to make, it quickly becomes clear how useful it is to write fewer lines of code when you start to build bigger and more complex apps. I’ve personally found myself spending hours trying to understand how a large React component my teammate wrote works.

I honestly believe that Svelte’s simplified API will allow us to read and understand code much faster, improving our overall productivity.

OK, we’ve seen that Svelte is truly reactive and allows you to do more with less. What about performance? What is the user experience like with apps written entirely in Svelte?

One of the reasons React has been so powerful is because of how it uses the virtual DOM to update only bits and pieces of your app’s UI, eliminating the need to repaint the whole DOM every time something changes (which is really expensive).

A downside to this approach, though, is that if a component’s data changes, React will re-render that component and all its children whether the children need to re-render or not. This is why React has APIs like shouldComponentUpdate, useMemo, React.PureComponent, etc.

This is a problem that will always exist if a virtual DOM is used to paint the UI on state change.

Svelte doesn’t use the virtual DOM, so how does it tackle the issue of repainting the DOM to match your app’s state? Well, let me quote Rich Harris again from his wonderful YGLF talk:

Frameworks are not tools for organizing your code. They are tools for organizing your mind.

The quote above is what led Rich to the idea that a framework could be something that runs in a build step, eliminating the need for your code to have an intermediary at runtime. This idea is why Svelte is a compiler and not a framework.

That simple idea is why Svelte is really fast. Svelte compiles your code down to an efficient, low-level code that interacts with the DOM directly. This is all well and good, but how does Svelte solve the issue of repainting the whole DOM when data changes?

The difference lies in the way a framework like React knows what changed versus how Svelte does the same thing. We’ve seen how React relies on you to call an API method to tell it when your data changes, but with Svelte, simply using the assignment operator = is enough for it.

If a state variable — let’s say foo — is updated using the = operator, Svelte will only update the other variables that depend on foo, like we saw earlier. This allows Svelte to only repaint the parts of the DOM that derive their value in one way or another from foo.

I’m going to leave out the actual implementation of how this works because this article is already long enough. You can watch Rich Harris himself explain that.

Svelte 3.0 is one of the best things to happen to software development in a while. Some may say that that is an exaggeration, but I disagree. The concept behind Svelte and its execution will enable us to do more while shipping less boilerplate JS to the browser.

This, in turn, will allow for apps that are more performant, more lightweight, and produce code that is easier to read. Now, will Svelte be replacing React, Angular, or any of the other established frontend frameworks anytime soon?

For now, I can say the answer is no. Svelte is relatively new compared to those, so it needs time to grow, mature, and sort out some kinks we might not even know exist yet.

Just like React changed software development when it came out, Svelte, too, has the potential to change how we think about frameworks and what is possible when we create new boxes to think in.

Happy coding!

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Try it for free.

Tag cloud