A biblioteca que transforma o React.js no Solid.js
O Problema
Uma das maiores críticas ao React é que ironicamente apesar do nome, a ferramenta não é realmente reativa e a forma que ela usa para simular uma reatividade é re-executar todo o componente toda vez que um estado ou prop é alterado
Isso infelizmente acaba levando a muitos outros problemas como por exemplo:
- Os rules of hooks que limitam como e onde um hook deve ser usado para que ele sempre seja invocado de forma consistente durante os renders
- A necessidade de se usar context para distribuir estados de forma eficiente entre multiplos componentes mas sujeitando todos os componentes que invocam o contexto a novos renders toda vez que qualquer estado que está sendo distribuído pelo contexto é modificado
- A necessidade de se usar o HOC memo() para evitar que um filho sofra um re-render junto com o pai
- A recomendação de não se modificar estados dentro do useEffect já que este é um hook feito para ser usado fora do ciclo de render
- A recomendação de não se ler ou modificar refs durante o ciclo de render já que elas não são limitadas pela imutabilidade dos renders e por não desencadearem novos renders quando modificados
- A necessidade de se usar useMemo e useCallback para memoização de valores e funções com alto custo para serem gerados em todo render
- Etc...
É claro que o próprio framework possui ferramentas internas para evitar renders desnecessários, como por exemplo:
- Comparação de v-dom para checar se a mudança de estado resultou em alguma mudança visual no DOM
- Comparação de referência de estados e props para determinar se houve uma mudança que exija um novo render
E até mesmo está sendo considerado uma etapa de compilação durante o build para auto memoização assim como o Vue já faz
Mas conforme os apps vão ficando mais e mais complexos mesmo todas essas soluções não são o suficiente e o framework vai se tornando um gargalo na performance do app
Os competidores
Para solucionar esse problema ao longo do tempo foram surgindo competidores como Solid.js e Svelte.js (na minha opinião o melhor framework javascript de todos) que usam uma etapa durante o build para analizar o código e compilar uma versão em vanilla javascript em que somente as partes especificas que necessitam de reatividade reagiriam a uma mudança de estado.
O que acaba gerando apps que:
- Não precisam de V-DOM e conseguem manipular o DOM diretamente com muito mais performance
- Não precisam enviar um runtime especial para o cliente, sendo assim muito menores e mais rápidos
- Tem soluções muito mais eficientes para gerenciamento de estado tanto local quanto global
As soluções da comunidade
Para tentar solucionar o problema de gerenciamento de estado e reatividade dentro do react foram também surgindo ao longo do tempo diversas bibliotecas de terceiros como por exemplo:
- Redux
- MobX
- Recoil
- Akita
Estes são alguns dos que se tornaram muito populares mas que já não recomendo em comparação a por exemplo:
- React-Query
- X-State
- Jotai
- Valtio
- Zustand
Mas o que praticamente todos tem em comum é a necessidade de algum tipo de seletor para evitar renders desnecessários e forçar renders quando necessário
Até que no ano passado surgiu uma solução que prometia acabar com a dependencia de renders para reatividade
Signals
Signals é o nome da biblioteca mas também é o nome do método utilizado (o mesmo que o Solid.js utiliza), que consiste em criar um ponto alto gerenciado e que se alto atualiza quando há uma mudança
A biblioteca foi criada pela mesma equipe por trás do Preact.js que é uma alternativa leve e performática ao React mantendo as mesmas APIs
Ao criar um estado usando a função signal() da biblioteca você recebe de volta um signal, um objeto com estabilidade referencial (o que exclui a necessidade de se usar o HOC memo e os renders do context) e com a propriedade value e o método peek
A propriedade .value é onde o valor do signal fica guardado e onde você deve modificar-lo, as mudanças no valor do signal não irão forçar novos renders no componente desde que o value não seja invocado durante o render
O método .peek() permite ler o valor do signal mesmo durante o render sem forçar novos renders quando o valor do signal é modificado
Além disso o signal pode ser utilizado diretamente no corpo do retorno JSX e em input bindings para atualizar a UI sem a necessidade de novos renders
A biblioteca também disponibiliza a função computed() que permite criar signals reativos a outros signals e estados, a função effect() que permite criar lógica reativa sem depender de renders ou useEffect e batch() que permite fazer atualizações de valores em fornada
Os signals podem ser criados tanto de dentro quanto de fora de componentes ou de hooks o que permite o compartilhamento de estado apenas exportando o signal em um arquivo e importando onde necessário, mas signal e computed possuem as versões hooks useSignal e useHook para serem usados para criar signals dentro de componentes e hooks
Exemplo
No exemplo a seguir eu criei um app com 2 instancias do mesmo componente counter, um recebendo o valor a partir de um signal e o outro a partir de um useState
Quando qualquer um dos contadores passa de 10 o valor é resetado para 0
Utilizando a extensão do react para chrome podemos ver um highlight ao redor do componente toda vez que ele sofre um novo render
(Abra a imagem em uma nova guia para ver o gif)
Mas perceba que enquanto o counter feito com useState força renders em todo o app toda vez que é atualizado o counter feito com signals não gera nenhum re-render, seja com a mudança de estado no evento ou no effect
Mais do que isso, o quando o counter com useEffect é resetado é forçado 2 re-renders, pois como o useEffect é executado fora do ciclo de render ele precisa forçar um novo render para atualizar o valor
Profiler do counter com Signal durante reset
Profiler do counter com useState
Conclusão
Signals é uma biblioteca revolucionária que permite criar apps complexos e performáticos com facilidade sem a necessidade da maior parte dos hooks do prórprio react