Svelte para el desarrollador experimentado de React | Programar Plus

Esta publicación es una introducción acelerada a Svelte desde el punto de vista de alguien con una sólida experiencia con React. Proporcionaré una introducción rápida y luego cambiaré el enfoque a cosas como la administración estatal y la interoperabilidad DOM, entre otras cosas. Planeo avanzar un poco rápido, para poder cubrir muchos temas. Al final del día, principalmente espero despertar algún interés en Svelte.

Para una introducción sencilla a Svelte, ninguna publicación de blog podría superar el tutorial oficial o los documentos.

“¡Hola Mundo!” estilo esbelto

Comencemos con un recorrido rápido por el aspecto de un componente Svelte.

<script>
  let number = 0;
</script>

<style>
  h1 {
    color: blue;
  }
</style>

<h1>Value: {number}</h1>

<button on:click={() => number++}>Increment</button>
<button on:click={() => number--}>Decrement</button> 

Ese contenido va en un .svelte y es procesado por el complemento Rollup o webpack para producir un componente Svelte. Hay algunas piezas aquí. Caminemos a través de ellos.

Primero, agregamos un <script> etiqueta con cualquier estado que necesitemos.

También podemos agregar un <style> etiqueta con cualquier CSS que queramos. Estos estilos son alcance al componente de tal manera que, aquí, <h1> los elementos de este componente serán azules. Sí, los estilos con ámbito están integrados en Svelte, sin necesidad de bibliotecas externas. Con React, normalmente necesitará usar una solución de terceros para lograr un estilo de alcance, como módulos css, componentes con estilo o similares (hay docenas, si no cientos, de opciones).

Luego está el marcado HTML. Como era de esperar, hay algunos enlaces HTML que deberá aprender, como {#if}, {#each}, etc. Estas características de lenguaje específicas de dominio pueden parecer un paso atrás de React, donde todo es “solo JavaScript”. Pero hay algunas cosas que vale la pena señalar: Svelte le permite colocar JavaScript arbitrario dentro de estos enlaces. Así que algo como esto es perfectamente válido:

{#if childSubjects?.length}

Si saltó a React desde Knockout o Ember y nunca miró hacia atrás, esto podría ser una (feliz) sorpresa para usted.

Además, la forma en que Svelte procesa sus componentes es muy diferente a la de React. React vuelve a ejecutar todos los componentes cada vez que cambia cualquier estado dentro de un componente, o en cualquier parte de un ancestro (a menos que “memorice”). Esto puede volverse ineficiente, por lo que React envía cosas como useCallback y useMemo para evitar recálculos de datos innecesarios.

Svelte, por otro lado, analiza su plantilla y crea un código de actualización de DOM específico cada vez que cambia un estado relevante. En el componente anterior, Svelte verá los lugares donde number cambios y agregar código para actualizar el <h1> texto después de que se realiza la mutación. Esto significa que nunca tendrá que preocuparse por memorizar funciones u objetos. De hecho, ni siquiera tiene que preocuparse por las listas de dependencia de efectos secundarios, aunque hablaremos de eso en un momento.

Pero primero, hablemos de…

Administración del Estado

En React, cuando necesitamos administrar el estado, usamos el useState gancho. Le proporcionamos un valor inicial y devuelve una tupla con el valor actual y una función que podemos usar para establecer un nuevo valor. Se parece a esto:

import React, { useState } from "react";

export default function (props) {
  const [number, setNumber] = useState(0);
  return (
    <>
      <h1>Value: {number}</h1>
      <button onClick={() => setNumber(n => n + 1)}>Increment</button>
      <button onClick={() => setNumber(n => n - 1)}>Decrement</button>
    </>
  );
}

Nuestra setNumber La función se puede pasar donde queramos, a componentes secundarios, etc.

Las cosas son más simples en Svelte. Podemos crear una variable y actualizarla según sea necesario. La compilación anticipada de Svelte (a diferencia de la compilación justo a tiempo de React) hará el trabajo de seguimiento de dónde se actualiza y forzará una actualización del DOM. El mismo ejemplo simple de arriba podría verse así:

<script>
  let number = 0;
</script>

<h1>Value: {number}</h1>
<button on:click={() => number++}>Increment</button>
<button on:click={() => number--}>Decrement</button>

También hay que tener en cuenta aquí que Svelte no requiere un solo elemento de envoltura como lo hace JSX. Svelte no tiene equivalente del fragmento React <></> sintaxis, ya que no es necesaria.

Pero, ¿qué pasa si queremos pasar una función de actualización a un componente secundario para que pueda actualizar este estado, como podemos hacer con React? Podemos simplemente escribir la función de actualización de esta manera:

<script>
  import Component3a from "./Component3a.svelte";
        
  let number = 0;
  const setNumber = cb => number = cb(number);
</script>

<h1>Value: {number}</h1>

<button on:click={() => setNumber(val => val + 1)}>Increment</button>
<button on:click={() => setNumber(val => val - 1)}>Decrement</button>

Ahora, lo pasamos donde sea necesario, o permanecemos atentos para una solución más automatizada.

Reductores y almacenes

React también tiene la useReducer hook, que nos permite modelar estados más complejos. Proporcionamos una función reductora, y nos da el valor actual, y una función de envío que nos permite invocar el reductor con un argumento dado, activando así una actualización de estado, a lo que devuelva el reductor. Nuestro contraejemplo de arriba podría verse así:

import React, { useReducer } from "react";

function reducer(currentValue, action) {
  switch (action) {
    case "INC":
      return currentValue + 1;
    case "DEC":
      return currentValue - 1;
  }
}

export default function (props) {
  const [number, dispatch] = useReducer(reducer, 0);
  return (
    <div>
      <h1>Value: {number}</h1>
      <button onClick={() => dispatch("INC")}>Increment</button>
      <button onClick={() => dispatch("DEC")}>Decrement</button>
    </div>
  );
}

Svelte no tiene directamente algo como esto, pero lo que sí tiene se llama Tienda. El tipo de almacenamiento más simple es un almacenamiento grabable. Es un objeto que tiene un valor. Para establecer un nuevo valor, puede llamar set en la tienda y pasar el nuevo valor, o puede llamar a actualizar y pasar una función de devolución de llamada, que recibe el valor actual y devuelve el nuevo valor (exactamente como React’s useState).

Para leer el valor actual de una tienda en un momento en el tiempo, hay un get función a la que se puede llamar, que devuelve su valor actual. Las tiendas también tienen una función de suscripción, a la que podemos pasar una devolución de llamada, y que se ejecutará cada vez que cambie el valor.

Svelte siendo Svelte, hay algunos buenos atajos sintácticos para todo esto. Si está dentro de un componente, por ejemplo, puede simplemente anteponer una tienda con el signo de dólar para leer su valor, o asignarlo directamente para actualizar su valor. Aquí está el ejemplo de contador de arriba, usando una tienda, con un registro adicional de efectos secundarios, para demostrar cómo funciona la suscripción:

<script>
  import { writable, derived } from "svelte/store";
        
  let writableStore = writable(0);
  let doubleValue = derived(writableStore, $val => $val * 2);
        
  writableStore.subscribe(val => console.log("current value", val));
  doubleValue.subscribe(val => console.log("double value", val))
</script>

<h1>Value: {$writableStore}</h1>

<!-- manually use update -->
<button on:click={() => writableStore.update(val => val + 1)}>Increment</button>
<!-- use the $ shortcut -->
<button on:click={() => $writableStore--}>Decrement</button>

<br />

Double the value is {$doubleValue}

Tenga en cuenta que también agregué una tienda derivada arriba. Los documentos cubren esto en profundidad, pero brevemente, derived store le permite proyectar una tienda (o varias tiendas) a un solo valor nuevo, utilizando la misma semántica que una tienda de escritura.

Las tiendas en Svelte son increíblemente flexibles. Podemos pasarlos a componentes secundarios, modificarlos, combinarlos o incluso hacerlos de solo lectura al pasarlos por un almacén derivado; incluso podemos volver a crear algunas de las abstracciones de React que le gustarían, o incluso necesitar, si estamos convirtiendo algún código de React a Svelte.

Reaccionar API con Svelte

Con todo eso fuera del camino, volvamos a React’s useReducer gancho de antes.

Digamos que realmente nos gusta definir funciones reductoras para mantener y actualizar el estado. Veamos qué tan difícil sería aprovechar las tiendas Svelte para imitar las de React useReducer API. Básicamente queremos llamar nuestro propio useReducer, pasar una función reductora con un valor inicial y recuperar una tienda con el valor actual, así como una función de envío que invoca al reductor y actualiza nuestra tienda. Lograr esto no es tan malo en absoluto.

export function useReducer(reducer, initialState) {
  const state = writable(initialState);
  const dispatch = (action) =>
    state.update(currentState => reducer(currentState, action));
  const readableState = derived(state, ($state) => $state);

  return [readableState, dispatch];
}

El uso en Svelte es casi idéntico al de React. La única diferencia es que nuestro valor actual es una tienda, en lugar de un valor sin procesar, por lo que debemos anteponerlo con el $ para leer el valor (o llamar manualmente get o subscribe en eso).

<script>
  import { useReducer } from "./useReducer";
        
  function reducer(currentValue, action) {
    switch (action) {
      case "INC":
        return currentValue + 1;
      case "DEC":
        return currentValue - 1;
    }
  }
  const [number, dispatch] = useReducer(reducer, 0);      
</script>

<h1>Value: {$number}</h1>

<button on:click={() => dispatch("INC")}>Increment</button>
<button on:click={() => dispatch("DEC")}>Decrement</button>

Qué pasa useState?

Si realmente amas a la useState gancho en React, implementar eso es igual de sencillo. En la práctica, no he encontrado que esto sea una abstracción útil, pero es un ejercicio divertido que realmente muestra la flexibilidad de Svelte.

export function useState(initialState) {
  const state = writable(initialState);
  const update = (val) =>
    state.update(currentState =>
      typeof val === "function" ? val(currentState) : val
    );
  const readableState = derived(state, $state => $state);

  return [readableState, update];
}

¿Las ataduras bidireccionales son realmente malas?

Antes de cerrar esta sección de administración de estado, me gustaría mencionar un truco final que es específico de Svelte. Hemos visto que Svelte nos permite pasar funciones de actualización al árbol de componentes de cualquier manera que podamos con React. Con frecuencia, esto es para permitir que los componentes secundarios notifiquen a sus padres sobre los cambios de estado. Todos lo hemos hecho un millón de veces. Un componente hijo cambia de estado de alguna manera, y luego llama a una función que le pasó un padre, para que el padre pueda ser consciente de ese cambio de estado.

Además de admitir este paso de devoluciones de llamada, Svelte también permite que un componente principal se vincule de dos vías al estado de un elemento secundario. Por ejemplo, digamos que tenemos este componente:

<!-- Child.svelte -->
<script>
  export let val = 0;
</script>

<button on:click={() => val++}>
  Increment
</button>

Child: {val}

Esto crea un componente, con un val apuntalar. El export La palabra clave es cómo los componentes declaran accesorios en Svelte. Normalmente, con accesorios, los pasamos a un componente, pero aquí haremos las cosas un poco diferentes. Como podemos ver, este accesorio es modificado por el componente hijo. En React, este código sería incorrecto y con errores, pero con Svelte, un componente que represente este componente puede hacer esto:

<!-- Parent.svelte -->
<script>
  import Child from "./Child.svelte";
        
  let parentVal;
</script>

<Child bind:val={parentVal} />
Parent Val: {parentVal}

Aquí, vinculamos una variable en el componente principal, al componente secundario val apuntalar. Ahora, cuando el niño val cambios de prop, nuestro parentVal será actualizado por Svelte, automáticamente.

La unión bidireccional es controvertida para algunos. Si odias esto, entonces, por todos los medios, siéntete libre de no usarlo nunca. Pero usado con moderación, he encontrado que es una herramienta increíblemente útil para reducir la repetición.

Efectos secundarios en Svelte, sin lágrimas (o cierres rancios)

En React, manejamos los efectos secundarios con el useEffect gancho. Se parece a esto:

useEffect(() => {
  console.log("Current value of number", number);
}, [number]);

Escribimos nuestra función con la lista de dependencias al final. En cada procesamiento, React inspecciona cada elemento de la lista y, si alguno es referencialmente diferente del último procesamiento, se vuelve a ejecutar la devolución de llamada. Si deseamos realizar una limpieza después de la última ejecución, podemos devolver una función de limpieza desde el efecto.

Para cosas simples, como cambiar un número, es fácil. Pero como sabe cualquier desarrollador experimentado de React, useEffect puede ser insidiosamente difícil para casos de uso no triviales. Es sorprendentemente fácil omitir accidentalmente algo de la matriz de dependencias y terminar con un cierre obsoleto.

En Svelte, la forma más básica de manejar un efecto secundario es una declaración reactiva, que se ve así:

$: {
  console.log("number changed", number);
}

Prefijamos un bloque de código con $: y poner el código que nos gustaría ejecutar dentro de él. Svelte analiza qué dependencias se leen y, cada vez que cambian, Svelte vuelve a ejecutar nuestro bloque. No hay una forma directa de ejecutar la limpieza desde la última vez que se ejecutó el bloque reactivo, pero es bastante fácil solucionarlo si realmente lo necesitamos:

let cleanup;
$: {
  cleanup?.();
  console.log("number changed", number);
  cleanup = () => console.log("cleanup from number change");
}

No, esto no conducirá a un bucle infinito: las reasignaciones desde dentro de un bloque reactivo no volverán a activar el bloque.

Si bien esto funciona, por lo general, estos efectos de limpieza deben ejecutarse cuando se desmonta el componente, y Svelte tiene una función incorporada para esto: tiene un onMount función, que nos permite devolver una función de limpieza que se ejecuta cuando se destruye el componente, y más directamente, también tiene un onDestroy función que hace lo que cabría esperar.

Condimentar las cosas con acciones

Todo lo anterior funciona bastante bien, pero Svelte realmente brilla con acciones. Los efectos secundarios están frecuentemente vinculados a nuestros nodos DOM. Es posible que deseemos integrar un complemento jQuery antiguo (pero aún excelente) en un nodo DOM y desmantelarlo cuando ese nodo abandone el DOM. O tal vez queremos establecer un ResizeObserver para un nodo, y derribarlo cuando el nodo abandone el DOM, y así sucesivamente. Este es un requisito lo suficientemente común como para que Svelte lo incorpore con acciones. Veamos como.

{#if show}
  <div use:myAction>
    Hello                
  </div>
{/if}

Nota la use:actionName sintaxis. Aquí hemos asociado este <div> con una acción llamada myAction, que es solo una función.

function myAction(node) {
  console.log("Node added", node);
}

Esta acción se ejecuta siempre que el <div> ingresa al DOM y le pasa el nodo DOM. Esta es nuestra oportunidad de agregar nuestros complementos jQuery, configurar nuestro ResizeObserver, etc. No solo eso, sino que también podemos devolver una función de limpieza, como esta:

function myAction(node) {
  console.log("Node added", node);

  return {
    destroy() {
      console.log("Destroyed");
    }
  };
}

Ahora el destroy() la devolución de llamada se ejecutará cuando el nodo abandone el DOM. Aquí es donde derribamos nuestros complementos de jQuery, etc.

¡Pero espera hay mas!

Incluso podemos pasar argumentos a una acción, así:

<div use:myAction={number}>
  Hello                
</div>

Ese argumento se pasará como segundo argumento a nuestra función de acción:

function myAction(node, param) {
  console.log("Node added", node, param);

  return {
    destroy() {
      console.log("Destroyed");
    }
  };
}

Y si desea hacer un trabajo adicional cada vez que cambie ese argumento, puede devolver una función de actualización:

function myAction(node, param) {
  console.log("Node added", node, param);

  return {
    update(param) {
      console.log("Update", param);
    },
    destroy() {
      console.log("Destroyed");
    }
  };
}

Cuando cambie el argumento de nuestra acción, se ejecutará la función de actualización. Para pasar varios argumentos a una acción, pasamos un objeto:

<div use:myAction={{number, otherValue}}>
  Hello                
</div>

…y Svelte vuelve a ejecutar nuestra función de actualización cada vez que cambia alguna de las propiedades del objeto.

Las acciones son una de mis características favoritas de Svelte; son increíblemente poderosos.

Retazos

Svelte también incluye una serie de excelentes funciones que no tienen equivalente en React. Hay una serie de enlaces de formulario (que cubre el tutorial), así como ayudantes de CSS.

Los desarrolladores que vienen de React pueden sorprenderse al saber que Svelte también incluye soporte de animación listo para usar. En lugar de buscar en npm y esperar lo mejor, está… incorporado. Incluso incluye soporte para la física de resortes y animaciones de entrada y salida, que Svelte llama transiciones.

La respuesta de Svelte a React.Chidren son máquinas tragamonedas, que pueden tener nombre o no, y están muy bien cubiertas en los documentos de Svelte. Los he encontrado mucho más fáciles de razonar que la API de niños de React.

Por último, una de mis funciones favoritas, casi ocultas, de Svelte es que puede compilar sus componentes en componentes web reales. El svelte:options ayudante tiene un tagName propiedad que permite esto. Pero asegúrese de establecer la propiedad correspondiente en el paquete web o la configuración del resumen. Con webpack, se vería así:

{
  loader: "svelte-loader",
  options: {
    customElement: true
  }
}

¿Interesado en probar Svelte?

Cualquiera de estos elementos sería una excelente publicación de blog en sí misma. Si bien es posible que solo hayamos arañado la superficie de cosas como la gestión del estado y las acciones, vimos cómo las características de Svelte no solo coinciden bastante con React, sino que incluso pueden imitar muchas de las API de React. Y eso es antes de que mencionemos brevemente las ventajas de Svelte, como las animaciones (o transiciones) integradas y la capacidad de convertir los componentes de Svelte en componentes web auténticos.

Espero haber logrado despertar cierto interés y, si lo he hecho, no faltan documentos, tutoriales, cursos en línea, etc. que se sumerjan en estos temas (y más). ¡Déjame saber en los comentarios si tienes alguna pregunta en el camino!

(Visited 4 times, 1 visits today)