Skip to content
En esta página

Las rarezas de Svelte

Como vimos, los builds de Svelte son muchísimo más livianos que los de Vue o React. Esto es sin duda una gran ventaja. Pero Svelte también tiene sus problemas.

SvelteScript

En este tweet Evan You, el creador de Vue, le dice a Rich Harris, el creador de Svelte, que en realidad Svelte debería llamarse SvelteScript 😆️

sveltescript tweet

¿Por qué dice esto? Porque Svelte difiere tanto del comportamiento y la sintaxis normal de JavaScript que es casi como si fuese un lenguaje aparte. Y algo de razón tiene. Por ejemplo, en cómo Svelte maneja la reactividad.

Reactividad por asignación

Mientras que en Vue la reactividad funciona por referencias o mutaciones de objetos (que es la forma normal en que funcionan los objetos en JavaScript) en Svelte la reactividad funciona por asignación.

¿Qué quiere decir esto? Que en Svelte cualquier variable, con sólo asignarle un valor (let variable = valor) ya se convierte en reactiva, como por arte de magia.

Por ejemplo, para que la variable framework sea reactiva, simplemente hacemos así:

html
<button on:click={() => framework = 'Svelte'}>
	Hola {framework}!
</button>

<script>
	let framework = 'Vue'
</script>

Y al cliquear en el botón el valor de la variable cambia y el cambio se ve reflejado en la vista.

Reactividad en Vue:

Para hacer esto en Vue 2 usaríamos el objeto data:

html
<template>
  <button @click="framework = 'Vue'">
    Hola {{ framework }}!
  </button>
</template> 

<script>

export default {

  data: () => ({
    framework: 'Svelte'
  })
}

</script>

Y el resultado sería:

En esto no hay tanta "magia", lo que hace Vue internamente es usar el método Object.defineProperty() para interceptar los cambios en el objeto data.

Con Vue 3

Y si lo hacemos en Vue 3 (usando la Composition API) sería así:

html
<template>
  <button @click="framework = 'Vue'">
    Hola {{ framework }}!
  </button>
</template> 

<script setup>

import { ref } from 'vue'

const framework = ref('Svelte')

</script>

Esto se parece bastante a Svelte, pero a diferencia de Svelte estamos usando expresamente una función (ref) para indicar que la variable es reactiva. Si no usamos ref (o reactive) la variable no es reactiva:

html
<template>
  <button @click="framework = 'Vue'">
    Hola {{ framework }}!
  </button>
</template> 

<script setup>

let framework = 'Svelte'

</script>

Al cliquear en el botón no pasaría nada:

Pero puede haber casos en que este comportamiento sea el deseado. Por ejemplo, si queremos diferenciar entre variables (o constantes) que no deben ser reactivas porque no cambian y variables que deben serlo. En Svelte todas las variables son reactivas 🤷‍♂️️

Reactividad en React:

Para hacer esto mismo en React usaríamos el hook useState():

js
import { useState } from 'react'

const ButtonExample = () => {
  const [framework, setFramework] = useState('Svelte')

  return (
    <>
      <button 
        onClick={() => setFramework('React')}
      >{framework}</button>
    </>
  )
}
export default ButtonExample

React es aún más explícito que Vue: además de estar usando expresamente un hook para generar reactividad (useState) estamos distinguiendo entre el estado (en este caso, la propiedad framework) y el setter que genera un cambio de estado (en este caso, setFramework).

Esta forma de hacerlo es mucho más cercana al funcionamiento normal de JavaScript que en Vue, y muchísimo más cercana comparado con Svelte.

💡️

La forma en que funciona la reactividad en Svelte cambia radicalmente el funcionamiento normal de JavaScript. Svelte puede permitirse cambiar las reglas de JS porque funciona como un compilador. Al igual que TypeScript, Svelte puede inventar nuevas funcionalidades para JS y luego el compilador las transforma en JavaScript nativo.

Reactividad en Arrays y Objetos

El ejemplo de arriba es bastante simple, porque la variable sólo contiene un string. Pero, ¿qué pasa con los arrays y objetos? Su comportamiento en Svelte es muy raro...

Supongamos que tenemos un array que contiene un string que se muestra dentro de un botón:

html
<button on:click={handleClick}>
	{array[0]}
</button>

<script>

	let array = ['un string']
	
	function handleClick() {
		array.splice(0, 1, 'otro string')
	}

</script>

El método splice toma 3 parámetros: el index donde debe ocurrir el cambio, la cantidad de elementos a cambiar, y el nuevo valor. Por lo tanto, array.splice(0, 1, 'otro string') provoca que el array cambie a array = ['otro string'].

Entonces, el contenido del botón ({array[0]}) debería cambiar. Sin embargo, cliqueamos en el botón y no pasa nada...

Esto es porque en Svelte la reactividad funciona por asignación (let algo = 'otro valor') y aunque splice produce una mutación en el array ese cambio no se ve reflejado en la vista porque no hubo asignación.

Entonces, ¿cómo hacemos en Svelte para generar reactividad en arrays que cambian? Así:

html
<button on:click={handleClick}>
	{array[0]}
</button>

<script>

	let array = ['un string']
	
	function handleClick() {
		array.splice(0, 1, 'otro string')
        array = array // 🤔️ raro, raro... 
	}

</script>

Ahora, al cliquear en el botón el texto cambia. Pero la forma de provocar el cambio (array = array) es muy rara y se aleja demasiado del comportamiento normal de JS.

En Vue esto funcionaría de una forma más comprensible:

html
<template>

  <button
    @click="array.splice(0, 1, 'otro string')"
  >{{ array[0] }}</button>

</template>

<script>

export default {

  data: () => ({ 
    array: ['un string']
  }),
}

</script>

Declaraciones reactivas

En Svelte hay algo llamado reactive declarations que se parecen mucho a las computed de Vue:

html
<p>Numero: {numero}</p>

<p>El doble de {numero} es: {doble}</p>

<script>

  let numero = 3
	
  $: doble = numero * 2 // $? Estamos en JQuery? 🤨️

</script>

De esta forma se obtiene un nuevo valor (el doble) a partir del estado (el número) pero sin mutar el estado.

El problema es que la forma de hacerlo es muy rara. En Vue esto tiene un nombre entendible: computed quiere decir que un nuevo valor es computado a partir del valor original.

Pero, ¿qué es eso de $: doble? ¿por qué $, un signo tan ambiguo que tiene tantos significados distintos? En JQuery, por ejemplo, donde el signo $ es muy usado, es un selector del DOM...

Además, ¿qué es eso de asignarle un valor a una propiedad en el ámbito global, por fuera de un objeto?

Ahí empezamos a entender por qué a Svelte se lo llama, irónicamente, SvelteScript...

Exportar para importar

Otra de las rarezas de Svelte es la forma en que se declaran las props, es decir, las propiedades que se pasan del componente padre al componente hijo.

En Vue es bastante fácil de entender:

html
<template>

  <ComponenteHijo :prop="someData"

</template>

<script>

import ComponenteHijo from './components/ComponenteHijo.vue'

export default {

  components: {
    ComponenteHijo
  },

  data: () => ({
    someData: 'data de componente padre a hijo'
  })
}

</script>

Y en el componente hijo:

html
<template>
  <h1>{{ prop }}</h1>
</template>  

<script>

export default {

  props: {
    prop: String
  }
}

</script>

Y, ¿cómo funciona esto en Svelte? Con export let.

Y uno podría pensar: claro, tiene sentido, exporto una prop desde el componente padre y la importo en el hijo. Pero no, es al revés...

html
<ComponenteHijo prop={someData}/>

<script>

	import ComponenteHijo from './ComponenteHijo.svelte'

    let someData = 'data de componente padre a hijo'

</script>

Y en el componente hijo:

html
<h1>{{ prop }}</h1>

<script>

export let prop // Qué??? 🤔️

</script>

O sea, para que el componente hijo pueda importar la prop del componente padre tiene que exportarla 😖️

Iterate like it's 2010

Otra de las cosas raras que tiene Svelte es la forma en que se generan iteraciones de bloques de HTML. En Vue esto es bastante simple, usando la directiva v-for, que no es más que un atributo dentro del elemento HTML que se quiere repetir:

html
<ul>
  <li v-for="item in items" :key="item.id">
    {{ item.name }}
  </li>
</ul>

Esto se parece bastante a cómo funciona Vanilla JavaScript:

js
for (let item of items) {
  let li = document.createElement('li')
  li.innerText = item.name
  document.querySelector('ul').append(li)
}

En Svelte, en cambio, es así:

html
<ul>
	{#each items as item}
		<li>
			{item.name}
		</li>
	{/each}
</ul>

Una sintaxis que se aleja demasiado de HTML y es muy parecida a la usada por los template engines para Server-Side Rendering en PHP, Python y Node.js de hace más de 10 años.

Para empezar a aprender, mejor Vue

Es por esto que pienso que para alguien que recién está empezando a aprender a usar frameworks de frontend lo mejor es empezar por Vue, no por Svelte.

En cuanto a React, la carga cognitiva para alguien que recién comienza es tan grande que puede resultar frustrante (o por lo menos así lo fue para mí 🤷‍♂️️).

Y en el caso de Svelte, aunque es relativamente fácil de aprender, si es el primero que abordamos, luego, al intentar aprender otro framework, nos va a resultar mucho más difícil, por todas las rarezas y particularidades que tiene.

Además, como vimos, Svelte se aleja demasiado de JavaScript nativo. Es cierto que todos los frameworks modifican en cierta medida la forma normal en que funciona JS, pero en el caso de Svelte esto es bastante extremo...