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