aguarde...

29 de maio de 2021

Svelte para o Experienced React Dev

Svelte para o Experienced React Dev

Este post é uma introdução acelerada ao Svelte do ponto de vista de alguém com sólida experiência com React. Fornecerei uma introdução rápida e, em seguida, mudarei o foco para coisas como gerenciamento de estado e interoperabilidade de DOM, entre outras coisas. Estou pensando em me mover um pouco mais rápido, para que possa cobrir muitos tópicos. No final do dia, espero principalmente despertar algum interesse em Svelte.

Para uma introdução direta ao Svelte, nenhuma postagem no blog poderia superar o tutorial ou documentos oficiais .

“Olá Mundo!” Estilo esbelto

Vamos começar com um rápido tour pela aparência de um 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> 

Esse conteúdo vai em um .sveltearquivo e é processado pelo plugin Rollup ou webpack para produzir um componente Svelte. Existem algumas peças aqui. Vamos examiná-los.

Primeiro, adicionamos uma <script>tag com qualquer estado de que precisamos.

Também podemos adicionar uma <style>tag com qualquer CSS que quisermos. Esses estilos têm como escopo o componente de forma que, aqui, os <h1>elementos neste componente sejam azuis. Sim, os estilos com escopo são integrados ao Svelte, sem a necessidade de bibliotecas externas. Com o React, você normalmente precisa usar uma solução de terceiros para obter um estilo com escopo definido, como módulos css , componentes com estilo ou similares (existem dezenas, senão centenas, de opções).

Depois, há a marcação HTML. Como seria de esperar, existem alguns vínculos HTML que você precisa aprender, como {#if}{#each}etc. Esses recursos de linguagem específicos de domínio podem parecer um retrocesso do React, onde tudo é “apenas JavaScript” Mas há algumas coisas que vale a pena observar: Svelte permite que você coloque JavaScript arbitrário dentro dessas ligações. Portanto, algo assim é perfeitamente válido:

{#if childSubjects?.length}

Se você saltou para React from Knockout ou Ember e nunca olhou para trás, isso pode ser uma (feliz) surpresa para você.

Além disso, a forma como o Svelte processa seus componentes é muito diferente do React. O React executa novamente todos os componentes a qualquer momento, em qualquer estado de um componente ou em qualquer lugar de um ancestral (a menos que você “memorize”), seja alterado. Isso pode se tornar ineficiente, e é por isso que o React envia coisas como useCallbackuseMemopara evitar recálculos desnecessários de dados.

Svelte, por outro lado, analisa seu modelo e cria código de atualização DOM direcionado sempre que qualquer estado relevante muda. No componente acima, Svelte verá os locais onde as numberalterações e adicionará o código para atualizar o <h1>texto após a mutação ser feita. Isso significa que você nunca precisa se preocupar com memoizing funções ou objetos. Na verdade, você nem mesmo precisa se preocupar com listas de dependência de efeitos colaterais, embora cheguemos a isso em breve.

Mas primeiro, vamos falar sobre …

Gestão do estado

No React, quando precisamos gerenciar o estado, usamos o useStategancho. Fornecemos a ele um valor inicial e ele retorna uma tupla com o valor atual e uma função que podemos usar para definir um novo valor. É mais ou menos assim:

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>
    </>
  );
}

Nossa setNumberfunção pode ser passada para onde quisermos, para componentes filhos, etc.

As coisas são mais simples em Svelte. Podemos criar uma variável e atualizá-la conforme necessário. A compilação antecipada do Svelte (em oposição à compilação just-in-time do React) fará o trabalho de rastreamento de onde ele é atualizado e forçará uma atualização do DOM. O mesmo exemplo simples acima pode ter a seguinte aparência:

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

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

Também digno de nota aqui é que o Svelte não requer nenhum elemento de envolvimento único como o JSX. Svelte não tem equivalente da <></>sintaxe do fragmento React , uma vez que não é necessária.

Mas e se quisermos passar uma função de atualização para um componente filho para que ele possa atualizar essa parte do estado, como fazemos com o React? Podemos apenas escrever a função de atualização assim:

<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>

Agora, nós o aprovamos onde necessário – ou fique atento para uma solução mais automatizada.

Redutores e lojas

O React também tem o useReducergancho, que nos permite modelar estados mais complexos. Fornecemos uma função redutora e ela nos dá o valor atual e uma função de despacho que nos permite invocar o redutor com um determinado argumento, disparando assim uma atualização de estado para o que quer que o redutor retorne. Nosso contra-exemplo acima pode ter a seguinte aparência:

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 não tem algo parecido com isso diretamente , mas o que ele tem é chamado de loja . O tipo mais simples de armazenamento é um armazenamento gravável. É um objeto que tem um valor. Para definir um novo valor, você pode chamar seto armazenamento e passar o novo valor, ou você pode chamar update e passar uma função de retorno de chamada, que recebe o valor atual e retorna o novo valor (exatamente como React useState).

Para ler o valor atual de uma loja em um momento no tempo, existe uma getfunção que pode ser chamada, que retorna seu valor atual. As lojas também têm uma função de inscrição, para a qual podemos passar um retorno de chamada, e que será executada sempre que o valor mudar.

Svelte sendo Svelte, há alguns atalhos sintáticos interessantes para tudo isso. Se você estiver dentro de um componente, por exemplo, você pode apenas prefixar uma loja com o cifrão para ler seu valor, ou atribuir diretamente a ele, para atualizar seu valor. Aqui está o exemplo de contador acima, usando uma loja, com algum registro de efeito colateral extra, para demonstrar como funciona a assinatura:

<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}

Observe que também adicionei um armazenamento derivado acima. Os documentos cobrem isso em profundidade, mas resumidamente, as derivedlojas permitem que você projete uma loja (ou várias lojas) para um único valor novo, usando a mesma semântica de uma loja gravável.

As lojas em Svelte são incrivelmente flexíveis. Podemos passá-los para componentes filhos, alterá-los, combiná-los ou até mesmo torná-los somente leitura passando por um armazenamento derivado; podemos até recriar algumas das abstrações do React de que você pode gostar, ou até mesmo precisar, se estivermos convertendo algum código do React para Svelte.

React APIs com Svelte

Com tudo isso resolvido, vamos voltar ao useReducergancho do React de antes.

Digamos que realmente gostemos de definir funções de redutor para manter e atualizar o estado. Vamos ver como seria difícil aproveitar os armazenamentos Svelte para imitar a useReducerAPI do React . Basicamente, queremos chamar o nosso useReducer, passar uma função redutora com um valor inicial e receber de volta uma loja com o valor atual, bem como uma função de despacho que invoca o redutor e atualiza nossa loja. Conseguir isso não é tão ruim assim.

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];
}

O uso em Svelte é quase idêntico ao React. A única diferença é que nosso valor atual é um armazenamento, em vez de um valor bruto, portanto, precisamos prefixá-lo com o $para ler o valor (ou chamar manualmente getou subscribesobre ele).

<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>

Sobre o quê useState?

Se você realmente adora o useStategancho do React, implementá-lo é igualmente simples. Na prática, não achei essa abstração útil, mas é um exercício divertido que realmente mostra a flexibilidade 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];
}

As ligações bidirecionais são realmente más?

Antes de encerrar esta seção de gerenciamento de estado, gostaria de tocar em um truque final que é específico de Svelte. Vimos que o Svelte nos permite passar funções do atualizador para baixo na árvore de componentes de qualquer maneira que pudermos com o React. Freqüentemente, isso permite que os componentes filhos notifiquem seus pais sobre as mudanças de estado. Todos nós fizemos isso um milhão de vezes. Um componente filho muda de estado de alguma forma e, em seguida, chama uma função transmitida a ele por um pai, para que o pai possa ser informado dessa mudança de estado.

Além de oferecer suporte a essa passagem de retornos de chamada, Svelte também permite que um componente pai se vincule a um estado filho. Por exemplo, digamos que temos este componente:

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

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

Child: {val}

Isso cria um componente, com um valadereço. A exportpalavra-chave é como os componentes declaram adereços no Svelte. Normalmente, com adereços, nós os passamos para um componente, mas aqui faremos as coisas de forma um pouco diferente. Como podemos ver, este prop é modificado pelo componente filho. No React, esse código estaria errado e com bugs, mas com o Svelte, um componente que renderiza esse componente pode fazer isso:

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

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

Aqui, estamos vinculando uma variável no componente pai ao valprop da criança . Agora, quando o valadereço da criança mudar, o nosso parentValserá atualizado pelo Svelte, automaticamente.

A vinculação bidirecional é controversa para alguns. Se você odeia isso, então, por favor, sinta-se à vontade para nunca usá-lo. Mas usado com moderação, descobri que é uma ferramenta incrivelmente útil para reduzir o clichê.

Os efeitos colaterais em Svelte, sem as lágrimas (ou tampas obsoletas)

No React, gerenciamos os efeitos colaterais com o useEffectgancho. Se parece com isso:

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

Escrevemos nossa função com a lista de dependências no final. Em cada renderização, o React inspeciona cada item da lista e, se algum for referencialmente diferente da última renderização, o retorno de chamada é executado novamente. Se quisermos limpar após a última execução, podemos retornar uma função de limpeza do efeito.

Para coisas simples, como a alteração de um número, é fácil. Mas, como qualquer desenvolvedor experiente do React sabe, useEffectpode ser insidiosamente difícil para casos de uso não triviais. É surpreendentemente fácil omitir acidentalmente algo da matriz de dependência e terminar com um encerramento obsoleto.

No Svelte, a forma mais básica de lidar com um efeito colateral é uma declaração reativa, que se parece com isto:

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

Prefixamos um bloco de código com $:e colocamos o código que gostaríamos de executar dentro dele. Svelte analisa quais dependências são lidas e, sempre que elas mudam, Svelte executa novamente nosso bloco. Não há uma maneira direta de executar a limpeza desde a última vez em que o bloco reativo foi executado, mas é fácil de contornar se realmente precisarmos dela:

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

Não, isso não levará a um loop infinito: as reatribuições de dentro de um bloco reativo não dispararão o bloco novamente.

Embora isso funcione, normalmente esses efeitos de limpeza precisam ser executados quando o componente é desmontado, e Svelte tem um recurso integrado para isso: ele tem uma onMount função que nos permite retornar uma função de limpeza que é executada quando o componente é destruído e mais diretamente , também tem uma onDestroy função que faz o que você espera.

Apimentar as coisas com ações

Tudo isso funciona bem, mas Svelte realmente brilha com ações. Os efeitos colaterais são freqüentemente associados aos nossos nós DOM. Podemos querer integrar um plugin jQuery antigo (mas ainda ótimo) em um nó DOM e derrubá-lo quando esse nó deixar o DOM. Ou talvez queiramos configurar um ResizeObserverpara um nó e destruí-lo quando o nó deixar o DOM e assim por diante. Este é um requisito comum o suficiente para que Svelte o integre com ações . Vamos ver como.

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

Observe a use:actionNamesintaxe. Aqui, associamos isso <div>a uma ação chamada myAction, que é apenas uma função.

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

Esta ação é executada sempre que <div>entrar no DOM e passar o nó DOM para ele. Esta é nossa chance de adicionar nossos plug-ins jQuery, configurar nosso ResizeObserveretc. Não só isso, mas também podemos retornar uma função de limpeza a partir dele, como esta:

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

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

Agora, o destroy()retorno de chamada será executado quando o nó deixar o DOM. É aqui que derrubamos nossos plug-ins jQuery, etc.

Mas espere, tem mais!

Podemos até passar argumentos para uma ação, como este:

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

Esse argumento será passado como o segundo argumento para nossa função de ação:

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

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

E se quiser fazer um trabalho adicional sempre que esse argumento mudar, você pode retornar uma função de atualização:

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

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

Quando o argumento para nossa ação mudar, a função de atualização será executada. Para passar vários argumentos para uma ação, passamos um objeto:

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

… E Svelte executa novamente nossa função de atualização sempre que qualquer uma das propriedades do objeto muda.

Ações são um dos meus recursos favoritos do Svelte; eles são incrivelmente poderosos.

Miudezas

O Svelte também fornece uma série de excelentes recursos que não têm contrapartida no React. Há uma série de associações de formulários (que o tutorial cobre ), bem como auxiliares CSS .

Os desenvolvedores vindos do React podem se surpreender ao saber que o Svelte também fornece suporte para animação pronto para uso. Em vez de pesquisar no npm e esperar o melhor, ele está … integrado. Inclui até suporte para física de primavera e animações de entrada e saída , que Svelte chama de transições .

A resposta de Svelte para React.Chidrenos slots, que podem ser nomeados ou não, e são bem explicados nos documentos do Svelte . Eu os achei muito mais simples de raciocinar do que a API infantil do React.

Por último, um dos meus recursos favoritos, quase ocultos, do Svelte é que ele pode compilar seus componentes em componentes da web reais. O svelte:options ajudante tem uma tagNamepropriedade que permite isso. Mas certifique-se de definir a propriedade correspondente na configuração do webpack ou Rollup. Com o webpack, seria algo assim:

{
  loader: "svelte-loader",
  options: {
    customElement: true
  }
}
Postado em Blog
Escreva um comentário