Executando verificação de segurança...
7

Signals, a nova moda no gerenciamento de estados de componentes

Signals é um conceito antigo da programação que vem ganhando bastante atenção nos últimos tempos após ser novamente popularizado pelo Solid.js e depois adotado pelo Preact.js, Qwik.js e recentemente anunciado para uma nova versão do Angular, e não sem motivo já que é uma grande evolução no modelo criado pelo React

O modelo React

O React trouxe um modelo em que um componente só consegue reagir a mudanças em estados que ele é dono, e então re-executa todo o componente para que esteja atualizado com o novo estado, mas isso acaba trazendo alguns problemas como por exemplo:

  • Um estado ter de ser criado dentro de um componente mesmo que ele precise ser compartilhado com toda a aplicação (criando a necessidade de inúmeros providers)
  • Ter que escrever a lógica de um componente de forma ela consiga fugir do ciclo de render quando necessário (quase sempre)
  • Criar clojures nos componentes usando memo, useMemo e useCallback para evitar problemas de performance
  • Necessidade de useEffect para conseguir sincronizar com estado externo
  • Necessidade de um V-DOM rodando em paralelo para saber quais partes do DOM precisam ser atualizadas

O modelo Solid

Pra quem não conhece um signal é basicamente um observable, um estado capaz de atualizar suas dependências por conta própria

Mas com o Solid.js vem ganhando um novo sentido, signals se tornaram estados independentes que conseguem atualizar suas dependências incluindo o DOM, com isso todo o gerenciamento de estado de uma aplicação é realizado pelo estado em si e o framework tem a obrigação apenas de inicializar os componentes e adicionar as partes do DOM que um signal influencia como uma dependência do mesmo, cada componente executa apenas uma vez e o signal modifica diretamente a parte do DOM que tem como dependência sem a necessidade de um V-DOM

Isso é uma evolução gigantesca do modelo do React mas também traz seus defeitos, como por exemplo:

  • Não poder desestruturar as props de um componente que recebe um signal como uma de suas props
  • Por não poder desestruturar as props é necessário uma função auxiliar para adicionar valores padrão às props
  • Ter de dizer explicitamente ao framework quais valores e expressões serão reativas
  • Ter de dizer explicitamente ao framework quais signals devem ter seus valores usados mas não adicionados como uma dependência
  • Apesar de os componentes serem executados apenas uma vez todo o gerenciamento de dependências de um estado deve ser feito em tempo de execução
  • Como o signal deve fazer o gerenciamento de suas próprias dependências eles também tem um tamanho mínimo razoável
  • Como o componente não tem mais posse sobre os estados ele também não possui mais ciclo de vida de atualizações

O modelo Svelte

Mas antes mesmo do Solid trazer esta alternativa ao React teve um outro framework que na minha opinião era uma solução ainda melhor, o Svelte.js

O Svelte assim como o Solid executa seus componentes apenas uma vez e não necessita de V-DOM mas ao invés de gerenciar as partes do DOM e do script que tem um estado como dependência durante o runtime ele faz isso durante o build com uma etapa de compilação que analisa o código
Para conseguir fazer isso ele separa estado local que o componente possui criado com a palavra chave let de estado compartilhado/combinável criado usando stores, que são basicamente qualquer observable com um método .subscribe que retorna uma função de desinscrição
Além disso ele oferece uma sintaxe especial para que seja possível acessar o valor de uma store reativamente com a mesma facilidade de um estado local fazendo com que o compilador crie um estado local e o atualize usando uma inscrição, tendo assim assim as mesmas vantagens de otimização de dependências dentro de um componente em um estado compartilhado que em um estado local, resolvendo assim os mesmos problemas que o Solid.js mas ainda por cima:

  • Podendo desestruturar props e dar valores padrão com facilidade
  • Por a análise de dependências ser feita durante o build há menos trabalho para ser feito durante o runtime e o app é mais leve
  • Mais facilidade de separar estados e expressões reativas de não reativas e suas dependências
  • O componente possui seu estado, tornando possível executar ações durante atualizações no ciclo de vida como por exemplo animações
  • Possibilidade do uso de qualquer API de gerenciamento de estado para estado compartilhado/combinável, incluíndo zustand, valtio, xstate e até mesmo o próprio signals do solid
Carregando publicação patrocinada...
1

Queria ver como era implementado antigamente o conceito.
Já que o mesmo não é novo.

Embaixo de toneladas de libs não quero ver não, gostaria de ver em JS puro e limpo

1
1

Não confudna store com estado dos componentes, São coisas diferentes.

Stores são usadas para gerenciamento/compartilhamento de estado na aplicação inteira, se a store mudar, todos os componentes que dependem dessa store vão renderizar denovo.

O estado do componente é so literalmente uma variavel do javascript normal. Não requer nenhum boilerplate para ser reativo, em todos os lugares que você usar essa variavel o svelte vai perceber que depende daquela variavel e se você setar a variavel com operador =, --, ++ e etc, ele já atualiza a interface.

Se você estiver proucurando algo como useEffect para sincronizar o estado com outras coisas fora do sistema do svelte vc pode colocar um statement ou bloco no topo do escopo na tag script e colocar um $: no começo, o svelte vai rodar essa linha denovo se alguma dependencia for mudada.

1

Sim, por isso que eu disse que o svelte

separa estado local que o componente possui criado com a palavra chave let de estado compartilhado/combinável criado usando stores

Só que assim como no React os componentes não conseguem reagir a mudanças numa store, apenas a mudanças em estados locais
Então para fazer um componente reagir a mudanças em uma store é necessário criar um estado local e sincronizar esse estado com a store usando o método .subscribe (por isso o contrato de store exige esse método) para que então o componente reaja ao estado local
Basicamente quando você escreve isso:

<script lang="ts">
    import {writable} from 'svelte/store'
    
    const count = writable(0)
</script>

<button on:click={() => {$count++}}>Count is: {$count}</button>

O compilador transforma nisso:

<script lang="ts">
    import {writable} from 'svelte/store'
    import {onDestroy} from 'svelte'
    
    const count = writable(0)
    let $count
    
    const unsubscribe = count.subscribe((value) => {
        $count = value
    })
    
    onDestroy(() => {
        unsubscribe()
    })
</script>

<button on:click={() => {count.update((value) => value + 1)}}>Count is: {$count}</button>

No fim para reagir a estados externos o svelte precisa fazer algo parecido com o hook useExternalAsyncStore do react (só que 1000x mais poderoso e versátil)
A diferença dos signals no Solid é que os componentes conseguem reagir diretamente aos estados compartilhaveis/combináveis que são os signals, sem precisar (e sem poder) usar estado local no componente