HomeAbout Me

Svelte for React Developers

By Dan Orlando
January 23, 2022
8 min read

There are some distinct differences with Svelte that make it a particularly interesting front-end UI framework that I’d like to share as I’ve been going through it. Since I am primarily a React developer, I initially felt it was fitting to do a side-by-side comparison with the most popular front end framework. However, as I fleshed out the article, it started to lean in the direction of - “Svelte is so much better than React because x, y, z” instead of a direct side-by-side compare/contrast. To be perfectly honest, it is difficult to do a side-by-side comparison of Svelte against any of the popular UI frameworks or libraries without it starting to sound that way. Svelte is just ridiculously easy to work with and beats frameworks like React, Vue, and Angular in every single category from the underlying architecture to the developer experience. For this reason, my prediction is that we’ll more than likely be seeing a similar level of adoption with Svelte as we do now with React in about the next 3 years, so what better time than now to start investigating it.

Motivation Behind Svelte

In this article from Rich Harris, which was published with Svelte’s first major release in 2016, there is a fundamental flaw with frameworks like React and Vue and it is that they must run in the browser, which can require a lot of complex processing to run behind the scenes. The purpose of these frameworks is to manage code complexity for the developer by moving these complex tasks like virtual DOM diffing out of the way so you can ship code faster. However, there is a high cost that comes with this luxury and your bundle size is paying for it. Its not just the size of the bundle though. All of this background processing has to be done at runtime, and that adds up quickly for highly interactive static web apps. It also stands to reason that these frameworks are perhaps over-engineering in some regards as well. At least it begins to feel that way once you start messing around with Svelte.

DOM Manipulation and Updates

When React determines if it needs to update the DOM, it uses “virtual DOM diffing”. When a state change occurs, React updates the virtual DOM, then compares the previous and current versions to determine if the real DOM needs updating. If the previous doesn’t match the current after the state change, a batch update is sent to the real DOM to update the UI. The promise was that this method is extremely efficient and ultra fast, when in reality that hasn’t turned out to be entirely true. However, in consideration of the alternatives that were available in 2013, I think we can all agree that virtual DOM diffing was an extraordinary solution to dealing with slow DOM operations. It is important to consider that virtual DOM operations must happen in addition to the eventual operations that occur on the real DOM though. While it is faster than older frameworks like JQuery and Knockout, it is of course still much slower than if you were to run vanilla JS in the browser.

The promise of React and other virtual DOM diff-ing libraries was that you could re-render your app on every state change without having to worry about performance. In practice however, that’s not entirely accurate. If it were, we wouldn’t need features like startTransition, which allow you to (/use-starttransition-for-concurrent-rendering-in-react/)[specify updates that are non-urgent for better application responsiveness].

A common misconception is that the virtual DOM is a feature when it is merely a means for declarative, state-driven UI development. It allows us to build apps without thinking about state transitions and performance that is usually good enough.

Svelte does not use a virtual DOM at all. In fact, it shifts that work to a compile step when building your app, converting your code to pure vanilla JavaScript. Svelte statically analyzes the component template and compiles all of your application code into tiny standalone JavaScript modules so the browser actually does very little work at runtime. This eliminates the layers of abstraction betwen your app and the browser, with the final result being highly-optimized vanilla Javascript. This results in smaller applications and significantly higher performance. In fact, Svelte demolishes all of the major UI frameworks not only in runtime speed, but in startup and memory tests as well.

Ecosystem and Community Support

It goes without saying that the svelte community is only a fraction of the size as that of React, although it has seen significant growth as of late with support from people like Scott Tolinksi, who just rewrote the level-up tutorials site with it and is talks about it on the Syntax podcast. Moreover, Vercel is backing the framework after it was voted the most loved web framework with the most satisfied developers on Stackoverflow.

It also goes without saying that the React ecosystem is absolutely massive. There is at least 10 libraries for anything you could possibly want to do, whether it be managing state, routing, animated transitions, or just implementing a simple date-picker.

I think it is worth considering the question of why there are so many of these libraries though, and why so many packages from all these different sources must be added as dependencies for your application. The simple answer is that its because React doesn’t have any of that stuff baked in. While perhaps a datepicker component is not something that you’d want baked into your UI framework, stuff like routing, state management, and transitions is. While the intention of React was to be able to pick your solutions ala-carte and have the rendering library be impervious to do these decisions, with Svelte you don’t have to worry about picking a third-party package for these things. This makes it easy to get up and running quickly with a front end application that is both scalable and easier to maintain. It also allows for a shallow learning curve to getting started.

Coding with Svelte

Svelte works by extending HTML to allow JavaScript expressions in markup and provides directives for conditions and loops. If you’ve ever worked with handlebars, this may sounds familiar. It then extends JavaScript to achieve reactivity by reiniterpreting specific directives.

I have found that with Svelte, you generally have to write less code to accomplish most things than you would with React. When Scott Tolinski rewrote the level-up-tuts site with Svelte, he said the application went from 300,000 lines of code with React, to 130,000 lines with Svelte. That is a mind-blowing 57% reduction. For large applications, reducing your application’s code by 57% means the overhead involved with maintaining the codebase will also be reduced by about that much, allowing you to iterate that much faster.

Component Structure

Component’s in Svelte use a .svelte extension and an individual svelte component may contain HTML, JavaScript, and CSS in a single file. JavaScript in enclosed in a <script/> tag and css is enclosed in a<style/> tag. There isn’t much else to know about the structure of a svelte component. You don’t have to export the component because Svelte already has it and it doesn’t have to be wrapped in a function or class. There are usually a lot less imports that have to be done for each component as well.

Styling

Svelte extends CSS by adding a scoping mechanism that allows the component to define it’s own styles without clashing with the styling of other components. For example, you can style an h1 tag for a component without having to give it its own class name and not worry about every h1 in your app now having those styles (you can of course use global styles if that is what you want though).

Svelte has built-in CSS variable support for themeing, similar to SASS and LESS. For example you could set a high level theme color:

/* global.css */
html {
  --theme-color: green;
}

…and use it at the component level:

<style>
    button {
        background-color: var(--theme-color);
    }
</style>

Routing

Routing is stupid-simple. When you initialize a new Svelte project, it contains a routes folder where you place each route for the application. There’s no fancy routing mechanism or library necessary. Then you just use anchor tags to link to them as you would with HTML.

Updating Local State

React updates components according to a schedule. You use this.setState or the useState() hook to accomplish this. Svelte does something similar, but it updates the DOM on a component state change only when triggered by assignments.

Assignments

Until an assignment triggers a DOM update, all changes are batched together similar to React. Here’s an example of an array assignment that will update the value displayed in the body:

<script>
    let myArray = [];

    function assignItemToArray() {
        myArray = [...myArray, myArray.length]
    }
</script>
<main>
    <p>{myArray}</p>
    <button on:click={assignItemToArray}>Assign</button>
</main>

Whenever the Assign button is clicked, the DOM will be updated with the new value and the array will show the next number added with each click. Mutating the array with array.push() will not have the same effect however. This is because the array.push() mutates the array rather than assigning a new value to it. This becomes clear when looking at the code for the assignItemToArray function generated by Svelte from the above example:

function assignItemToArray() {
  $$invalidate(0, (myArray = [...myArray, myArray.length]))
}

You can see that using the assignment results in a call to the $$invalidate method, which will force a re-render.

Reactive Declarations

Reactive declarations trigger changes to other variables when the initial variable is changed and recompute logic automatically on each update through assignments. This is all accomplished by simply adding a dollar sign (yes, it is that easy). Let’s look at an example:

<script>
    let myArray = [];

     function assignItemToArray() {
        myArray = [...myArray, myArray.length]
    }

    $: nextValue = myArray.length + 1
</script>
<main>
    <p>Array: {myArray}</p>
    <p>Next Value: {nextValue}</p>
    <button on:click={assignItemToArray}>Assign</button>
</main>

The $: marks the nextValue statement as reactive.

Conditional Blocks

Using conditional blocks to update state looks like this:

{#if query.isError}
   <p>There was an error processing the request.</p>
{:else if query.isSuccess}
    <FabulousComponent data={query.data}/>
{:else}
    <LoadingSpinner />

Await Blocks

Using Await blocks looks very similar to the conditional syntax:

{#await promise}
    <LoadingSpinner />
{:then result}
    <FabulousComponent data={result.data} />
{:catch error}
    <p>There was an error: {error.message}</p>
{/await}

You can also iterate over lists using an {#each} block:

<ItemTable>
    {#each items as items}
        <ItemRow item={item}/>
    {/each}
</ItemTable>

State Management

Svelte has a couple options for state management, namely a Context API and Svelte Stores, both of which are baked-in.

Context API

The Context API is the thing to use when it comes to communicating across components without passing props all over the place. The Context API consists of two functions: getContext and setContext. Using setContext, you can make a value or an object available anywhere in the application by giving it a key:

<script>
  import {setContext} from 'svelte' const myObject = {}
  setContext('myKey', myObject)
</script>

Using the key, you can grab it from anywhere with getContext:

<script>
  import {getContext} from 'svelte' const myObject = getContext('myKey')
</script>

One thing I find particularly useful is that Svelte automatically injects a a let declaration for you in the generated code if it comes across a statement with an assignment to an undeclared variable.

The limitation of the Context API is that it only shares state with descendants in the same component tree. For values that must be communicated across components from separate component trees, we use Svelte Stores.

Svelte Stores

Svelte stores come in two flavors: writable, and readable. Using writable stores, we can make a value or object store writable like so:

<script>
import writable from 'svelte/stores/writable'

export const myValue = writable('26');

…then change it with the set method, which Svelte attaches to the object:

<script>import {myValue} from 'age.js' myValue.set('34')</script>

The update() method will run a callback that passes current value as an argument. (eg. myValue.update(existing => newValue))

You can also create side effects to state changes by using subscribe() and passing a callback:

<script>
import {myValue} from './age.js'

const look = myValue.subscribe(value => {
    console.log(value);
});
</script>

Readable stores are used for handling data that needs to be immutable. That being said, there is no set() or update() method on these stores.

The value is set once and then used anywhere, like so:

<script>
  import {readable} from 'svelte/store'; export const counter = readable(0, set
  =>{' '}
  {setTimeout(() => {
    set(1)
  }, 1000)}
  )
</script>

The counter is then readable from anywhere in the application:

<script>
  import { counter } from './store.js'
</script>

<p>You've been here for: {$counter} seconds.</p>

I suspect we’ll see a third solution added in the not-too-distant future that allows for a deeper level of state management.

Svelte directives

Directives control an element or component’s behavior. Svelte provides the following directives out of the box:

  • on:event
    • To listen for DOM events on elements (eg. on:click={handler})
    • Used with createEventDispatcher for components rather than elements
  • bind:property
    • For binding element properties from child to parent
    • eg. <input type="text" bind:value={name}>
  • bind:this
    • Gives a reference to a DOM node for binding
  • class:name
    • Shorthand way to toggle classes of an element
    • eg. <div class:active class:inactive={!active}>...</div>
  • style:property
    • Shorthand way for setting multiple styles on an element
    • eg. <div style:color={myColor}> or <div style:color="red">
    • can be combined with style attributes, but the directives will take precedence
  • use:action
    • Actions are functions called when an element is mounted to the DOM
    • Extremely useful
    • May include a parameter which will call the action’s update() method if the value is changed.
  • transition:fn
    • Used for bidirectional transitions when a node enters or leaves the DOM
    • Can use custom functions
  • in:fn and out:fn
    • Like transition: but specific to elements entering and leaving the DOM
    • not bidirectional
  • animate:fn
    • For animating elements in a keyed each block when reordered
    • can also contain parameters and custom animation functions

Conclusion

There are some things that I wasn’t able to get into for this article, namely Svelte tags and the component lifecycle, but I plan to in an upcoming post.

The bottom line is that Svelte is a straight-forward, easy to pick up front-end UI framework that speeds up development considerably and allows you to accomplish the same thing with a lot less code to maintain when compared with libraries like React. Plus you don’t have to spend so much time trying to keep up with a massive ecosystem with a lot of churn. With all of this extra time, you can learn how to surf or scuba-dive, or even pick up a new skill!


Tags

Sveltebeginner
Previous Article
Automatic State Batching in React 18
Dan Orlando

Dan Orlando

Web Developer

© 2022, All Rights Reserved.

Quick Links

PortfolioAbout

Social Media