React: o framework JavaScript criado pelo Facebook (atual Meta) desperta paixões. Ele é usado para criar interfaces de usuário em aplicativos web e traz uma série de vantagens para o desenvolvimento, como facilitar a componentização, oferecer uma performance eficiente, melhorar o SEO do aplicativo, simplificar a criação de Single Page Applications (SPA) e permitir o desenvolvimento de aplicativos móveis compartilhando a lógica de negócio e a maioria do código com a versão web.
Por outro lado, o React também é apontado como uma solução com uma curva de aprendizado íngreme, que pode impactar no tempo de desenvolvimento de projetos, ao invés de agilizar. Além disso, o React e suas dependências podem resultar em um pacote maior do que outras bibliotecas mais leves. Essas mesmas dependências de várias bibliotecas podem aumentar a complexidade do projeto e tornar o gerenciamento mais desafiador. O framework também exige atenção para se manter a compatibilidade com versões anteriores.
EMNudge é um desenvolvedor web que passou pelo Lyft e agora se dedica a evangelizar sobre os meandros do JavaScript. Em um artigo publicado na internet, ele explica como ele ao mesmo tempo ama e odeia o React e não consegue abrir mão do framework.
Com sua autorização, traduzimos e reproduzimos o artigo na íntegra:
“Parece que este artigo teria sido um sacrilégio apenas alguns anos atrás. Sob a proteção dessa nova tendência encontrada no desagrado do React, gostaria de finalmente dizer minha opinião.
Eu não ligo muito para React. E, francamente, eu diria que o mesmo é verdade para a maioria. Mesmo que você ainda não tenha percebido seu ressentimento.
Bom, já uso React há bastante tempo para dizer que prefiro a companhia de outro. Quer dizer, sou praticamente um “Desenvolvedor React” pelo amor de Deus. Minha última empresa, esta empresa, provavelmente minha próxima empresa. Não consigo evitar. Você pensaria que eu pararia de me importar tanto depois de um tempo, mas basta olhar para a alternativa para se perguntar por que você ficou.
Eu vou te contar. React não é tudo o que parece ser.
Quero dizer, foi. Obviamente. Quando o React entrou em cena, tudo parecia tão elegante. Código de componente com foco em localidade de comportamento. Seu DOM foi embutido – sentou-se ao lado de seus manipuladores de eventos. Seu código parecia modular. Foi legal.
class MyComponent extends React.Component { constructor() { super(); this.setState({ num: 0 }); } handleClick = () => this.setState({ num: this.state.num + 1}); render() { return ( <button onClick={this.handleClick}> Clicked {this.state.num} times! </button> ) } }
Tutoriais nasceram, livros foram escritos, gurus foram criados. O React ergueu um império mais rápido do que eu já tinha visto antes. A experiência do desenvolvedor foi incomparável, mas melhor do que isso: era óbvio. Os primeiros a experimentar foram os participantes da conferência, os thinkfluencers, os mais nerds de nós. A biblioteca se espalhou, como não? Era sintaticamente simples.
Então todos nós pulamos nesse barco.
E então fomos presenteados com hooks! Viva! Para ser honesto, eles eram um pouco estranhos no começo. Mas a clareza! A elegância recém-descoberta! Mais rápido para digitar, mais rápido para iterar, mais fácil de ler. React foi a escolha correta.
const MyComponent = () => { const [num, setNum] = useState(0); const handleClick = () => setNum(num + 1); return ( <button onClick={handleClick}> Clicked {num} times! </button> ) }
Mas aí os anos passam e você começa a perceber a riqueza de alternativas. Todo mês um novo nome. Nós zombamos desses novos projetos de fim de semana! Eles nunca vão entender! React é testado em batalha! O React se moldou perfeitamente no buraco que encontramos anteriormente! Não há mais espaço para um novo tipo de massa corrida.
E então as pessoas começam a falar muito positivamente. Muito consistentemente. Parece que todo mundo está feliz com suas coisas novas e menos feliz com as coisas antigas. Mas são apenas modismos passageiros! Nada como o React resistiu ao teste do tempo desde seu lançamento em 2015… e iteração em 2019… e evolução constante.
Você sabe, o React não parece mais tão estável. Quero dizer, talvez seja a melhor escolha, mas quem realmente sabe? Achei que tínhamos terminado de iterar, mas continuo sendo arrastado por algumas novas mudanças. Talvez haja uma maneira melhor que eu ainda não vi! Não pode doer olhar.
E ainda assim.
React & Hooks
“Hooks” não é React, mas honestamente pode ser. “React Hooks” é diferente do próprio React – afinal, você ainda pode usar componentes de classe. No entanto, ele assumiu o controle do cenário do React a ponto de os Hooks parecerem inerentes ao desenvolvimento do React. Quando me refiro ao modelo de coisas do React neste artigo, estou me referindo a “React Hooks”.
Recentemente, li o espanto de alguém sobre como foi suave a transição do ecossistema React para Hooks – como todos concordaram unilateralmente sobre seu benefício. Este não é o passado que eu me lembro. Lembro-me de um pouco de discórdia. Particularmente no site laranja, mas não exclusivo dele.
Não estou necessariamente do lado da multidão anti-Hook, mas acho que muitas de suas preocupações eram justificadas. Os React Hooks existiam em um ambiente controlado por componentes de classe. Para permitir essa transição, o React precisava ser totalmente compatível com o código da classe. E foi!
Essa compatibilidade combinada com as melhorias de capacidade de composição e legibilidade levou o setor como um todo a adotar os Hooks mais rapidamente do que se poderia prever.
Eu realmente acho que Hooks trouxe essas duas melhorias. Embora não seja universalmente aceito, estou no campo firme de “composição sobre herança” e acho que compartilhar comportamento por meio de funções é uma melhoria drástica em relação à herança de classe. Quanto à legibilidade, embora o comprimento do seu código possa não estar diretamente correlacionado com a legibilidade (consulte Regex, code golfing), React Hooks melhora a “Localidade do comportamento“.
Seus olhos escaneiam menos em componentes menores. Event listeners, transformações de estado e saída renderizada podem fazer seus olhos saltarem para cima e para baixo. Os React Hooks melhoram isso. Acho os componentes de função mais rápidos de escrever e mais fáceis de ler.
Legibilidade vs. Complexidade
Mas a legibilidade (pelo menos no sentido imediato) não anda de mãos dadas com a complexidade. Os hooks diminuíram a complexidade por meio da localização do comportamento, mas a aumentaram por meio das abstrações que precisavam fazer.
Eu penso muito sobre essa citação fora de contexto de Amos:
Ou melhor, é uma meia-verdade que encobre convenientemente o fato de que, ao fazer algo simples, você transfere a complexidade para outro lugar.
Amos (Simple is a Lie)
Quando abstraímos sobre sistemas complexos, não eliminamos a complexidade, nós a movemos. No nosso caso, o sistema complexo não é o Front-End Development, mas o React.
Hooks move nosso modelo mental para pensar sobre transformações de estado e sincronização em vez de ciclos de vida. Ou, pelo menos, tenta.
componentDidMount → useEffect(func, []) componentWillUnmount → useEffect(() => func, []) componentDidUpdate → useEffect(func, [props])
Houve alguns sacrifícios para o desempenho trazidos por esse movimento – suas ataduras visíveis nas forma dos Hooks useMemo e useCallback. Não pretendo sugerir que a “memoização” não é anterior ao Hooks in React. Sim (React.memo()). Estou dizendo que agora temos que “memoizar” a inicialização e as transformações do estado devido às melhorias que fizemos na localização do comportamento.
Há uma conversa comum na comunidade sobre “memoização” no React. Muito mais do que outros frameworks. O cache de valor é importante em todas as estruturas, mas os Hooks forçam muito essa tomada de decisão no autor do componente, não na biblioteca principal.
Veremos mais sobre isso mais tarde. Mas antes de continuarmos, gostaria de dedicar um momento para discutir nosso modelo mental.
Existe o modelo sobre o qual você costuma ler nos documentos do React ou nos vídeos do YouTube e o que realmente está acontecendo. Ou pelo menos existe um modelo mental mais fiel ao comportamento real e que considero importante revisar.
Um modelo mental melhor
É raro ver uma conversa sobre o próprio React sem ver o termo “VDOM” espalhado. Dan Abramov não parece ser fã disso. Eu concordo com ele aqui. O React VDOM não deve ser nosso foco.
React não é seu VDOM. O VDOM é uma consequência do React, não a causa dele. Embora ao discutir as diferenças imediatas, seja algo fácil de apontar.
Em vez disso, devemos nos concentrar em como os componentes do React devem ser “puros”.
Esse termo parece imediatamente deslocado quando lembramos que os componentes têm estado. O estado parece diretamente contrário à ideia de uma função pura – algo que produz a mesma saída para um determinado conjunto de entradas, independentemente da quantidade de vezes ou das formas em que é chamado.
// função pura const getPlusOne = (num) => num + 1; // função impura const getDateNow = () => Date.now();
O truque é entender que o estado no React não é armazenado no componente.
Estado é mais uma entrada.
Na terra do React, chamadas para useState são outra forma de receber entradas. State vive no React VDOM/state-tree. Os componentes são chamados de maneira muito ordenada e o useState retirará as entradas de uma pilha fornecida.
const Component = ({ color }) => { const [num1] = useState(0); // receive next state argument const [num2] = useState(0); // receive next state argument const [num3] = useState(0); // receive next state argument return <div>{num1} + {num2}</div> }
Ambos state e props são tipos de inputs. Chamadas para setState são sinais internos do React, não mutações diretas.
Esses sinais, por sua vez, atualizarão sua pilha de estado do componente e executarão novamente um componente. Esse componente produzirá uma certa saída dada essa nova entrada.
Os componentes do React também podem ser uma caixa preta para o React.
Seu comportamento interno não é visível. Podemos pensar nos próprios componentes como objetos reativos em vez de partes individuais do estado.
Isso é geralmente o que as pessoas querem dizer quando descrevem o modelo de reatividade do React como não sendo “refinado”.
Por esse motivo, o React precisa de uma maneira de não reescrever todo o DOM a cada atualização. Portanto, ele passa por um processo de diferenciação na nova atualização para decidir qual nó DOM precisa ser atualizado.
Talvez nenhum. Talvez todos. Não podemos saber sem verificar.
Isso é React. Essa relação entre o renderizador e o reconciliador. Esse comportamento de “componente puro”. Essa falta de conexão direta entre atualizações de estado e DOM.
No React, os componentes são reais, o DOM não.
Talvez seja por isso que o React é uma escolha tão boa para renderizadores não-web. Podemos usar o React para descrever a interface do usuário e as atualizações, mas trocar o processo pelo qual essas novas atualizações são aplicadas à nossa interface do usuário.
Como desenvolvedor da Web, essa não é uma vantagem grande o suficiente.
Encontrando arapucas
O obstáculo mais rápido que você encontrará como alguém novo no React será algo assim:
function MyComponent() { const [num, setNumber] = useState(42); // infinite loop setNumber(n => n + 1); return <div>{num}</div> }
Tentar fazer atualizações de estado no nível superior de um componente resultará em um loop infinito. Atualizações de estado re-executam componentes. Isso não significa uma atualização do DOM, mas significa outra atualização de estado que acionará outra re-execução que acionará uma atualização de estado que acionará uma re-execução e assim por diante.
Você provavelmente encontrará esse bug rapidamente. Loops infinitos como este não são muito difíceis de detectar.
As coisas ficam mais complicadas quando você começa a usar o React Context e começa a sinalizar atualizações em um componente pai. As cascatas de renderização. Talvez um componente busque alguns dados, algum componente remonte e você execute sua atualização de estado novamente, com atraso de alguns segundos.
Esse padrão é comum o suficiente para merecer um artigo próprio, mas este não é um artigo sobre como corrigir seus problemas de React; é um desabafo.
Componentes como objetos reativos
Vamos continuar a discussão sobre componentes existentes como objetos reativos, não como estado. Existem algumas consequências desse padrão.
const MyForm = () => { const [text1, setText1] = useState(''); const [text2, setText2] = useState(''); const [text3, setText3] = useState(''); return <form> <input type="text" value={text1} onInput={e => setText1(e.currentTarget.value)} /> <input type="text" value={text2} onInput={e => setText2(e.currentTarget.value)} /> <input type="text" value={text3} onInput={e => setText3(e.currentTarget.value)} /> </form>; }
Eu simplifiquei demais esse componente para não causar muita dor de cabeça, mas qualquer pessoa que já trabalhou com formulários no React sabe que geralmente eles são muito mais complexos.
Vejo regularmente componentes de formulário com mais de 300 linhas.
Estão envolvidas transformações de estado, validações e exibições de erro. Muito disso é inerente aos formulários, não apenas ao React. React tende a complicar as coisas, no entanto.
Lembre-se, os componentes são reativos, não de estado. Ao trabalhar com entradas controladas, estamos causando uma “re-renderização” a cada pressionamento de tecla em nossas entradas. Isso significa que estamos potencialmente executando um código de computação de estado, independentemente de qualquer um desses estados ser tocado.
‘Mas o VDOM conserta tudo isso!’
Isso parece ser uma falácia anti-anti-VDOM predominante. O VDOM impede atualizações estranhas do DOM, não cálculos de estado.
Seu componente é uma função que está literalmente sendo executada novamente toda vez que precisamos verificar se há atualizações. Embora o próprio DOM possa não ser tocado, o código em execução não precisa ser executado.
Imagine o seguinte componente:
const MyInput = ({ label, value, onInput, isError, errorText }) => { const labelText = label ? toTitleCase(label) : 'Text Input'; return <> <label> <span>{labelText}</span> <input value={value} onInput={onInput} /> </label> {isError && <div className="error">{errorText}</div>} <>; }
Um exemplo mais realista, eu acho. Decidimos corrigir as entradas de rótulo fornecidas a nós, transformando-as em “Title Case”.
Por enquanto, está bom. Decidi não memorizar nada porque o cálculo parece bastante simples.
Mas e se as coisas mudassem?
E se toTitleCase crescesse em complexidade? Talvez, com o tempo, tenhamos adicionado recursos lentamente para criar o Title Caser™️ definitivo!
const MyForm = () => { const [text1, setText1] = useState(''); const [text2, setText2] = useState(''); const [text3, setText3] = useState(''); return <form> <MyInput value={text1} onInput={e => setText1(e.currentTarget.value)} /> <MyInput value={text2} onInput={e => setText2(e.currentTarget.value)} /> <MyInput value={text3} onInput={e => setText3(e.currentTarget.value)} /> </form>; }
Em cada pressionamento de tecla, agora executamos novamente o toTitleCase em todos os componentes. Nosso uso de useState tornou todo o nosso componente de formulário reativo a mudanças em qualquer um de seus estados!
Oh não!
Ou… quero dizer, isso é um problema? Os navegadores são muito rápidos. O hardware é bastante rápido. Talvez não seja um problema.
Bem, não é até que seja.
A adição incremental de cálculos em lugares diferentes não causará muito dano. Mas continue fazendo isso e, eventualmente, você criará uma experiência lenta. Agora você deve enfrentar o problema de que não existe uma fonte única de problemas de desempenho – está em toda parte. Consertar isso requer muito mais trabalho do que você gostaria de gastar.
‘Você não está esquecendo do useMemo?’ Ah sim. Isso…
“Memoização”
Eu certamente gostaria que houvesse um consenso confiante sobre isso. Para cada artigo pró “memoização”, há outro contra.
Você vê, a “memoização” tem um custo de desempenho:
Dan Abramov apontou repetidamente que a “memoização” ainda incorre no custo de comparar props e que há muitos casos em que a verificação de “memoização” nunca pode impedir a re-renderização porque o componente sempre recebe novos props. Como exemplo, veja esta thread do Twitter de Dan.
Mark Erikson (A (Mostly) Complete Guide to React Rendering Behavior)
Esse comentário foi uma referência a React.memo(), que é uma forma ligeiramente diferente de “memoização” em React.
const MyInputMemoized = React.memo(MyInput);
“Memoizar” componentes inteiros impede que a cascata de uma renderização precise verificar seus filhos. Isso parece um padrão sensato, mas a equipe do React parece pensar que o custo de desempenho de comparação de props supera os custos médios de desempenho de permitir que uma cascata de renderização massiva ocorra.
Acho que isso provavelmente está errado. Mark parece concordar.
Também faz o tipo parecer ainda mais feio. A maioria das bases de código que examinei tende a evitar React.memo() até ter certeza absoluta de que criará uma melhoria significativa no desempenho.
Outro argumento contra a “memoização” é que é fácil para um React.memo() ser ineficaz quando o código pai não está escrito corretamente.
// "Memoizando" para prevenir re-renderizações const Child = React.memo(({ user }) => <div>{user.name}</div>); function Parent2() { const user = { name: 'John' }; // re-renders anyway return <Child user={user} />; }
Estamos comparando props da maneira mais rápida possível – igualdade superficial. Isso aparece como um novo suporte em cada re-renderização. Como as re-renderizações são comuns, precisamos estar cientes disso.
Componentes sendo os “primitivos” reativos aqui, podemos corrigir alguns problemas de “memoização” movendo o estado através dos componentes.
Eu particularmente não gosto desse tipo de discussão quando estou tentando criar um produto.
“Sim, eu disse useMemo(), não React.memo()”
Justo. Vamos falar um pouco sobre isso.
Caímos nas mesmas considerações de desempenho com useMemo(). Temos um custo de comparar “dependências” agora, em vez de props.
const value = useMemo(() => { const items = dataList .map(item => [item, placeMap.get(item)]) .filter(([item, place]) => itemSet.has(place)); return pickItem(items, randomizer); }, [dataList, placeMap, itemSet, pickItem, randomizer]);
Não gaste muito tempo lendo isso. São apenas bobagens para fins de demonstração.
Mas você notou algo estranho? Existem 2 transformações de estado discretas. Uma é uma operação de lista e a outra chama alguma função nos dados resultantes.
Sem querer, “memoizamos” demais! O que acontece se o randomizer mudar? Reexecutamos toda a função! Devíamos ter escrito isto:
const items = useMemo(() => { return dataList .map(item => [item, placeMap.get(item)]) .filter(([item, place]) => itemSet.has(place)) }, [dataList, placeMap, itemSet]); const value = useMemo(() => { return pickItem(items, randomizer) }, [items, pickItem, randomizer]);
Agora nossos valores são mais específicos. As alterações no randomizer não executarão novamente nosso .map e .filter, apenas a chamada pickItem.
O dia está salvo! Talvez?
Costumo “memoizar” dados automaticamente quando vejo uma operação de lista. Esse é o qualificador? Não sei. Eu apenas faço isso.
O problema mais notável com essa “memoização” é que ela é feia. Não sei se chamaria isso de “código fedido” (como li antes), mas definitivamente pode tornar o código mais difícil de ler.
A “memoização” pode ajudar às vezes, mas apenas se tivermos cuidado com o uso e a composição do componente.
O armazenamento em cache não é um campo de complexidade exclusivo do React, mas somos forçados a lidar com isso manualmente com muito mais frequência do que precisaríamos.
A “memoização” resolve problemas, mas pode ser frustrante pensar sobre quando e onde “memoizar”. A ergonomia é ruim.
Sobre Pedagogia
E é nisso que eu adoraria focar. Fiz um hobby de pesquisar a pedagogia da programação ao longo dos anos. Tenho me concentrado bastante na questão de “Como você comunica os conceitos de programação de maneira mais eficaz?”.
Acho que ainda não tenho uma resposta, mas sei como você faz o contrário.
O React tem sido tradicionalmente ensinado como um sistema de componentes simples onde o estado é conectado à interface do usuário e atualizado ao longo do tempo.
Tive o prazer de ensinar React a várias pessoas. Pessoas relativamente novas em frameworks, React ou codificação em geral. React não é fácil. E isso se torna ainda mais difícil devido à ofuscação dos materiais de ensino.
Esses conceitos, esses modelos mentais que estamos examinando – eles podem parecer triviais para você se você estiver trabalhando com o React por tempo suficiente. Não é para a maioria das pessoas.
Não é óbvio que seu componente seja renderizado novamente nas atualizações de estado.
Como isso funcionaria? Não há nome para acompanhar o uso de cada estado. Como ele se lembra?
Sim, claro, o estado é mantido em uma pilha no VDOM em certo sentido e é por isso que a ordem é importante e os estados também são entradas para um componente e as mutações de estado estão sinalizando para uma árvore que chama a função novamente para diferenciar a saída, mas você sabia disso?
Você descobriu isso com o tempo? Talvez você tenha lido um artigo, assistido a um vídeo. Ou talvez você seja significativamente mais inteligente do que eu. Eu não imagino que estabeleci um padrão alto.
O React, quando comparado com suas alternativas contemporâneas, apresenta a complexidade das atualizações de estado como um obstáculo ativo no desenvolvimento.
E esses materiais, necessários para facilitar o desenvolvimento, são ensinados principalmente como tópicos complementares ou avançados.
Imagino que os novos documentos do React tentarão mudar isso. Espero que sim. Também espero que as pessoas percebam quantos iniciantes preferem obter informações de vídeos em vez de tutoriais longos.
Consertando o React
Mas eu quero revisitar essa discussão sobre formulários.
A dor foi sentida por tempo suficiente para que houvesse alguma mudança nas melhores práticas de formulário. Entradas descontroladas estão na moda hoje em dia.
As atualizações de componentes vêm de atualizações de estado. As entradas controladas forçam uma atualização de estado em cada interação do formulário. Se apenas deixarmos o formulário fazer o que quer que seja, só precisamos atualizar as etapas de envio e validação.
Esse padrão foi popularizado com bibliotecas de formulários como Formik e react-hook-form. Nós podemos transformar isso:
const [firstName, setFirstName] = useState(''); const onSubmit = data => console.log(data); return <form onSubmit={handleSubmit(onSubmit)}> <Input name="firstName" value={firstName} onInput={e => setFirstName(e.currentTarget.value)} /> </form>
Em:
const { control, handleSubmit } = useForm({ defaultValues: { firstName: '' } }); const onSubmit = data => console.log(data); return <form onSubmit={handleSubmit(onSubmit)}> <Controller name="firstName" control={control} render={({ field }) => <Input {...field} />} /> </form>
Sim, adicionamos alguma complexidade, mas ajudamos com atualizações de estado que afetam mais componentes do que gostaríamos.
No entanto, isso traz um ponto interessante. Quando olhamos para o ecossistema React, encontramos muitas bibliotecas que existem com o propósito expresso de corrigir as deficiências do React.
Quando você vê uma biblioteca anunciada como uma melhoria de velocidade e ergonomia 100x, o que eles estão fazendo é evitar o React.
O que, para constar, não sou contra. É simplesmente engraçado observar o ecossistema de um renderizador de interface do usuário funcionar tão incansavelmente para continuar a usá-lo, evitando todas as partes dele.
E na discussão sobre estados – temos alguns amigos se juntando a nós! Temos react-redux, @xstate/react, Zustand, Jotai, Recoil e muito mais!
A discussão do estado em geral tende a ficar deprimente porque eles geralmente estão encobrindo alguma forma de React Context. Devemos obedecer às regras do React para acionar as atualizações da interface do usuário, portanto, há alguma forma de efeito de renderização em cascata para todas as bibliotecas mencionadas.
Os componentes do React não podem compartilhar o estado diretamente. Como o estado mora na árvore e só temos acesso indireto a essa árvore, devemos subir e descer a árvore em vez de pular de galho em galho. Quando fazemos esse tipo de escalada, podemos tocar em coisas que não deveríamos.
const countAtom = atom(0); const doubleCountAtom = atom(get => get(countAtom) * 2); const MyComponent = () => { const [count, setCount] = useAtom(countAtom); const doubleCount = useAtomValue(doubleCountAtom); return <button onClick={() => setCount(count + 1)}> {count} x 2 = {doubleCountAtom} </button>; }
Habilmente configuramos algum estado derivado usando Jotai, mas conectá-lo ao React significa que voltamos à reatividade baseada em componentes.
Você pode adicionar sistemas reativos “refinados” ao React sem consertar muito.
Ele precisa ser integrado no nível da estrutura.
Reatividade Refinada
Como seria a reatividade de granulação fina integrada ao framework? Provavelmente algo como Solid.js:
function Counter() { const [count, setCount] = createSignal(0); setInterval(() => setCount(count() + 1), 1000); return <div>Count: {count()}</div>; }
O Solid é divertido de abordar nas discussões do React porque sua API é bastante semelhante ao React. A principal exceção é que não precisamos agrupar código como este em um useEffect.
No React, esse tipo de código resultaria em um bug desagradável em que criamos uma nova chamada para setInterval a cada segundo.
Para frameworks que não possuem reatividade baseada em componentes, a distinção entre os componentes desaparece. Eles são úteis para configuração e geração de interface do usuário. O estado é tudo o que realmente importa durante o tempo de vida do seu aplicativo.
Frameworks como Preact, Vue, Angular, Marko, Solid e Svelte adotaram alguma forma de reatividade refinada. Eles são chamados de sinais, armazenamentos ou observáveis. As diferenças semânticas podem ser importantes, mas vou me referir ao conceito como um sinal.
const [headerEl, divEl, spanEl] = getEls(); const nameSignal = signal('John'); nameSignal.subscribe(name => headerEl.textContent = `Name: ${name}`); nameSignal.subscribe(name => divEl.textContent = name); nameSignal.subscribe(name => spanEl.textContent = `"${name}"`); // somewhere in our application nameSignal.set('Jane')
Neste exemplo, temos sinais – pedaços de estado que estão cientes de seus “assinantes”. Quando alteramos o valor desse estado, o sinal irá “informar” seus assinantes sobre uma atualização por meio da função passada.
Não precisamos consultar alguma árvore de estado suprema para diferenciar as saídas da interface do usuário antes de realizar as atualizações. Podemos conectar diretamente o estado às alterações da interface do usuário.
Os sinais também podem informar outros sinais. Nossas máquinas de estado computadas ainda podem existir, apenas com uma ergonomia extraordinariamente melhor.
Você poderia construir seu próprio framework em uma hora usando primitivos reativos como base e ter um código consideravelmente melhor do que usaria outro modelo de reatividade.
const num1 = signal(0), num2 = signal(0); const total = computed(() => num1.value + num2.value); const inputEl1 = create('input').bind('value', num1); const inputEl2 = create('input').bind('value', num2); const outputEl = create('input').bind('textContent', total); get('body').append(inputEl1, ' + ', inputEl2, ' = ', outputEl);
No mesmo sentido dos “Rustáceos” argumentando contra qualquer nova linguagem sem segurança de memória, eu me oporia a qualquer nova estrutura sem sinais.
Estamos encontrando lutas semelhantes nas guerras WASM. O Yew foi lançado e ainda permanece como a estrutura de front-end mais proeminente do Rust, mas conta com uma abordagem semelhante à do React. Ele mal supera o React em desempenho, enquanto os frameworks Rust baseados em sinal, como Leptos e Sycamore, deixam para trás Angular e Svelte.
Conclusões sobre os problemas
Apesar do último parágrafo, não acho que apenas olhar para benchmarks de framework seja suficiente.
React sofre de ergonomia pobre.
É muito mais fácil errar no React do que no Svelte. Claro, o React hiperotimizado é apenas marginalmente pior do que qualquer outro framework, mas eu não escrevo código hiperotimizado. Portanto, na prática, o código React que vejo tende a ter cerca de uma dúzia de problemas de desempenho por arquivo, ignorados por uma questão de sanidade.
O React foi ótimo quando foi lançado! Mas agora existem opções melhores; quase objetivamente. Embora melhorias sejam feitas ao longo do tempo, não vejo o React mudando tão fundamentalmente como funciona para se tornar tolerável novamente.
Então, por que ainda estamos usando o React?
- É testado em batalha
- Grandes empresas provaram que pode ser usado de forma produtiva.
- É mais fácil tomar uma decisão quando você vê produtos de sucesso usando uma tecnologia específica.
- Ecossistema evoluído
- Tecnicamente verdade, mas metade do ecossistema existe como wrappers React para bibliotecas vanilla ou como pacotes React.
- Um modelo de reatividade diferente significa que geralmente é mais fácil conectar bibliotecas de terceiros fora do React.
- Maior força de trabalho
- Difícil discordar dessa. Se você quer um emprego, sua melhor aposta é o React. Se você quer contratar, sua melhor aposta é o React.
- Embora eu ache mais fácil ensinar outros frameworks, isso só faz sentido se você tiver tempo e largura de banda para treinar seus engenheiros.
- Está evoluindo
- É difícil mudar quando a “correção” está chegando.
- As evoluções existem principalmente no espaço “Fullstack”, mas cada novo produto é apresentado como a solução para todos os males do React.
- É difícil partir
- Os custos de migração não valem o benefício percebido.
- Seu modelo de reatividade é único o suficiente para que a migração para outra estrutura leve muito tempo para o que não é imediatamente óbvio como uma melhoria.
E então meu trabalho atual é React. Meu próximo trabalho será React. O seguinte também pode ser React.”
Publicado originalmente como “React Is Holding Me Hostage“, em 22 de fevereiro de 2023. Traduzido e republicado com autorização do autor.