Mas e aí, qual é o melhor: VueJS ou React?

Veja aqui como é trabalhar com dois dos frameworks de componentização Web mais usados do ecossistema Javascript atual.

Mas e aí, qual é o melhor: VueJS ou React?

Esse post terá a prova definitiva e incontestável de qual desses dois frameworks é melhor!

Mentira, não vai ter isso. Até porque "melhor" é um conceito muito relativo. É o melhor pra trabalhar? É o que gera melhor resultado final? É o que tem mais performance ou que tem melhor experiência de desenvolvimento? São muitas variáveis que são difícieis ou impossíveis de serem levadas em conta em uma única postagem.

Tá, mas então pra que serve esse post?

Eu vou mostrar o que sinto usando os dois, a experiência de desenvolvimento. O que eu acho sobre eles e tentar explicar meu ponto de vista. A ideia aqui é te dar um norte, caso precise. E vou me focar apenas nesses dois pois são os que já trabalhei.

E como isso vai funcionar?

Primeiro eu preciso explicar o meu contexto.

Eu venho do Design Gráfico. Entrei no mundo da programação pela porta do design de interfaces e quando comecei eu não mexia com Javascript. Me limitava a escrever HTML e CSS. E eu gosto muito de escrever HTML e CSS. E isso vai ser importante em breve.

E, dentre outras coisas, eu acho que componentização (independente da estratégia pra se fazer isso) é imprescindível nos dias de hoje. Dos projetos mais simples aos mais complexos.

Dito tudo isso, podemos começar a falar sobre a experiência de se escrever componentes nesses dois frameworks.

Como funciona no React?

Vou começar pelo queridinho da galera. O React.

Tudo nele é javascript. Seu componente é uma função ou classe. E o 'html' na verdade é um modo de representação do mesmo por meio do javascript (famoso JSX).

E isso é importante de ser registrado porque isso faz com que o ecosistema do React gire em torne especialmente do JS.

Eu não tenho problemas nenhum com JSX e na verdade quando descobri que antes se escrevia funções pra cada tag html eu dei graças a Deus que o JSX foi inventado. Mas eu tenho um problema com as soluções para o CSS.

Tem 3 grandes soluções, ao menos que eu conheça.

  • 1 - Escrever os estilos como se nada tivesse mudado
  • 2 - Escrever estilos usando CSS Modules
  • 3 - Escrever estilos usando alguma estratégia de CCS-in-JS

Tá mas o que isso significa:

O primeiro caso tem o problema da dificuldade de conversar com a componentização. Porque isso leva a vários estilos globais mesmo para componentes que não estejam em tela. Não me entenda mal, o problema aqui não é com o CSS. Mas apenas o fato dele não conversar diretamente com os componentes React.

/* styles.css */
.loginButton {
    color: #fff;
    border: none;
    font-size: 16pt;
    background: #d9173b;
}
// LoginButton.js
const LoginButton = () => (
    <button className="loginButton">Entrar</button>
)

No segundo caso você tem que referenciar cada classe na tag jsx como uma chave de um objeto de estilos. A vantagem aqui é que como parte da responsabilidade do estilo é passada para o javascript, você consegue acoplar melhor cada estilo a um componente. Ajuda também a corrigir o problema de colisão de classes que coisas como o BEM naming chegam pra resolver no CSS puro.

/* styles.css */
.loginButton {
    color: #fff;
    border: none;
    font-size: 16pt;
    background: #d9173b;
}
// LoginButton.js
import styles from "./styles.css";
// Ou direto: import { loginButton } from "./styles.css";

const LoginButton = () => (
    <button className={styles.loginButton}>Entrar</button>
)

O terceiro caso eu acredito que seja o melhor dentre esses os três (mesmo que não seja perfeito). JS ainda tem muita participação. E você ainda pode fazer qualquer coisa que o CSS faz. Só que ele conversa diretamente com a componentização, uma vez que por padrão você irá estar criando componentes pra cada bloco de estilo que quiser definir. Isso facilita o desenvolvimento e dá mais liberdade dentro de um código React. A biblioteca que costumo usar, styled-components, ainda realiza o tree-shaking automático (um estilo que não está sendo usado na página não é carregado, diminuindo o tamanho de CSS que o browser precisa carregar a cada página navegada).

/* styles.js */
import styled from 'styled-components'

export const LoginButton = styled.button`
    color: #fff;
    border: none;
    font-size: 16pt;
    background: #d9173b;
`
// LoginButton.js
import  * as S from "./styles.js";

const LoginButton = () => (
    <S.LoginButton>Entrar</S.LoginButton>
)

Mas é ruim tudo ser feito em javascript?

Acredito que não. Isso facilita aos montes diversas integrações que a IDE ou editor de texto podem ter para te ajudar.

Eu gosto muito de escrever meus códigos em Typescript, por exemplo, e a integração entre React e Typescript no Visual Studio Code é maravilhosa. Tudo funciona perfeitamente e sem muitos esforços específicos criados pela união das três coisas.

Entretanto, leva ao problema da possível dificuldade que isso pode gerar para iniciantes.

Antes de começar em qualquer framework aprenda primeiro HTML, CSS e Javascript. Isso é importantíssimo.

Só que se você não souber muito sobre os ES Modules, entender sobre classes ou algumas coisas que não são tão básicas ou triviais das versões mais novas do Javascript: Pode ser que você tenha problemas em começar a usar esse framework.

Você falou classes, mas ouvi dizer que não se usa mais

Desde 2019 o uso de Classes do Javascript pra construir componentes tem estado menos em voga graças a criação dos React Hooks.

Esses Hooks são funções que dão poderes especiais a outras funções ou variáveis (estados) que você crie.

Você chama uma função, como useState, e o valor que é retornado agora faz parte da reatividade do React e está propenso a ser re-renderizado em tela de acordo com o uso.

const [contagem, setContagem] = useState(0)

console.log(contagem) // Pega o valor do estado

setContagem(10) // Define um novo valor

// A variável retornada pelo estado é imutável, 
// logo, é sempre necessário usar a função `set` pra trocar seu valor

O exemplo clássico do botão que ao ser clicado aumenta mais 1 a uma contagem.

Eu gosto da ideia dos Hooks. Mas odeio trabalhar com eles. Não porque sejam mal feitos ou tenham problemas em si. Mas porque o React precisa que você tome diversos cuidados com eles para que não ocorra nenhum problema.

Pra isso foram definidas diversas regrinhas passadas ao ESLint (que recomendo você usar em seus projetos) que te impede de gerar código potencialmente quebrado.

A regra que mais vejo é a do react-hooks/exhaustive-deps que é quando você está usando o hook como o useEffect e deixa de incluir alguma variável reativa, que está em uso, no array de dependências.

Esse array de dependências serve para que o React saiba o que ele precisa ou não executar quando uma renderização da tela ocorre. No exemplo do botão contador: ele sempre irá chamar a função de um useEffect que tenha o estado responsável pela contagem quando o valor dessa contagem mudar (como em um clique do botão).

Isso tudo porque o React não é bem "reativo". Ele não espera as mudanças ocorrerem. Na verdade, ele está constantemente verificando para realizar o render novamente ou não. A parte "reativa" é porque ele tem seus meios de saber o que mudou e precisa ser alterado ou não a cada um desses processos de verificação da tela (o chamado Virtual DOM). Mesmo que nada mude na tela ou no estado, o react tá trabalhando por debaixo dos panos.

Sempre que busco ajuda sobra alguma dessas regras vejo recomendações em post de blogs ou em respostas do Stack Overflow para que desligue a regra. Bom, você pode fazer isso. Mas eu prefiro seguir o que os criadores da biblioteca recomendam. Eles sabem muito mais sobre ela do que eu.

E uma dica que custou bastante tempo pra eu enteder. O array de dependências não serve para que você diga ao React quando um Hook deve agir. O array de dependências serve para que o React saiba quando deve agir. Coloque nele o que o React precisa e não o que você quer.

Outro detalhe é quando o hook envolve async/await. Por padrão os hooks que recebem funções, como useEffect e useCallback, não te deixam definir a keyword async diretamente. Ou você cria uma função async e chama ele ou você escreve o código dentro de um IIFE (Immediately Invoked Function Expression ou Expressão de Função Imediatamente Invocada).

useEffect(() => {
    const funcaoAssincrona = async () => {
        const response = await fetch("...")
        // ...
    }

    funcaoAssincrona()
}, [])

//  ou...

useEffect(() => {
    (async () => {
        const response = await fetch("...")
        // ...
    })()
}, [])

O que não é bem um problema gigante, mas sempre tem que ser levado em conta quando se está criando um componente que envolve algo assíncrono junto aos Hooks de função (como useMemo, useCallback e useEffect).

Resumindo

Trabalhar com React não é em si ruim. Mas, dependendo do seu estilo de trabalho ou conhecimento de Javascript, pode-se ter algumas dores de cabeça.

Eu atualmente uso o React mais pelas vantagens do Next.js do que pelo framework em si.

Geralmente é assim que escrevo componentes React:

// /Contador/index.js
import React, { useState, useEffect } from 'react'

import * as S from "./styles"

const Contador = () => {
    const [contagem, setContagem] = useState(0)

    useEffect(() => {
        console.log(contagem)
    }, [contagem])

    const incrementar = (event) => {
        event.preventDefault()

        setContagem(contagem + 1)
    }

    return (
        <div>
            <S.ContagemTexto>Contagem: {contagem}</S.ContagemTexto>
            
            <S.ContagemBotao onClick={incrementar}>
                Incrementar contagem
            </S.ContagemBotao>
        </div>
    )
}
// /Contador/styles.js
import styled from 'styled-components'

export const ContagemTexto = styled.strong`
    color: #000;
    font-size: 16pt;
`

export const ContagemBotao = styled.button`
    color: #fff;
    border: none;
    font-size: 16pt;
    background: #d9173b;
`

Como funciona no VueJS

Diferente do React, o VueJS já tenta ao máximo separar as três linguagens da Web. Mesmo que não 100%.

Em vez de usar JSX, ele vai pro lado do Template. Basicamente um html com superpoderes. E pra ser sincero. Não vejo diferença prática, fora a sintáxe, entre eles. Ambos não são html 100%, mas pertos o suficiente pra não te fazer ficar completamente por fora.

As coisas começam a mudar mesmo na hora do CSS. Aqui aquelas duas primeiras estratégias existem e podem ser usadas. Mas o Vue apresenta uma palavra que acredito ser mágica que é a scoped.

Cada componente Vue tem uma tag style associada. E apesar de estar contida no arquivo de um componente, ela é por padrão "global". Mais ou menos na verdade. Os estilos não vão afetar componentes pais, mas vão afetar componentes filhos. Quando se usa a keyword scoped, você faz com que aqueles estilos só digam respeito as tags do componente em si. Se eventualmente duas classes de mesmo nome forem usadas em componentes distindos, nenhum problema vai acontecer, uma vez que ambas estarão em seus respectivos escopos.

O que resolve, pra mim, todos os problemas.

No fim o scoped é um jeito de escrever CSS como você já está acostumado (escreve as classes, vai no html e define a classe a uma tag). Mas resolvendo o problema de conflito de especificidade e criando pequenos escopos de estilos diretamente vinculados a um componente. É o melhor dos 3 mundos citados (CSS padrão, CSS Modules e CSS-in-JS).

O VueJS também te permite escrever o seu CSS passando ele por pré-processadores como less, sass e o meu preferido postcss.

Detalhe importante: estes pequenos escopos de estilo não são pacotes de código a serem importados. Por padrão todos os estilos, scoped ou não, são sempre importados para o projeto. O estilo "scoped" apenas vai gerar um hash e vincular a classe/seletor a esse hash.

Isso:

<h2 class="subtitulo">
    Subtítulo do Post
</h2>

<style scoped>
.subtitulo {
    font-size: 32px;
}
</style>

Vai virar isso:

<h2 data-v-40699cf9 class="subtitulo">
    Subtítulo do Post
</h2>

<style>
.subtitulo[data-v-40699cf9] {
    font-size: 32px;
}
</style>

Sendo assim, duas classes com o mesmo nome subtitulo não irão conflitar.

Mas isso não significa que você não tem como criar esses pacotes de código (não só de CSS). Isso porque o VueJS facilita muito o import assíncrono de componentes.

Se você usar o Vue Router junto do WebPack, você já deve ter tornado uma rota inteira assíncrona passando uma função import em vez de fazer o import e passar a referência direta ao componente:

const Index = () => import('./Index.vue')
const Sobre = () => import('./Sobre.vue')
const Cadastro = () => import('./Cadastro.vue')

Mas você tembém pode escolher quais componentes internos de uma rota podem ser assíncronos se preferir. Isso já era possível na versão 2, mas na versão 3 fica ainda mais fácil. Você irá novamente passar uma função import em vez de passar a referência direta, só que englobando dentro de uma função do Vue chamada defineAsyncComponent:

{
    // ...
    components: {
        NomeDoComponente: defineAsyncComponent(() => import("./Componente.vue"))
    }
}

Como é o jeito VueJS de escrever componentes?

Bom, é basicamente um objeto com funcionalidades bem definidas que você precisa seguir. O que deixa a barra de entrada muito baixa.

Você precisa de um método? Escreve uma função dentro da chave methods. Preciso de um estado? Escreve um valor dentro da chave data. E tudo simplesmente se comunica sem que você precise fazer algo.

export default defineComponent({
    data() {
        return {
            contagem: 0
        }
    },
    methods: {
        incrementar() {
            this.contagem = this.contagem++
        }
    }
})

Isso é uma faca de dois gumes, as vezes. Em componentes muito grandes pode se tornar difícil de escrever ou manter, já que cada uma das funcionalidades dele pode estar divida em três ou mais blocos de código distindos e separados (por exemplo você ter um dado que passa por um método e que é chamado em um lifecycle method)

Pensando nisso, o time do VueJS, em sua versão 3, lançou o que eles chamaram de Composition API bastante inspirado nos React Hooks.

E até aí você pode pensar "ah, mas então é melhor usar o original do que usar a cópia". E claro, você pode querer fazer isso. Pode também não usar e se manter ao método que citei mais cedo que continua existindo e tendo suporte sem nenhum problema.

Porém a Composition API tem uma coisa que os React Hooks não tem: uma reatividade melhor definida.

A Reatividade do VueJS é bem mais inteligente e faz automaticamente várias otimizações que o React coloca na sua mão durante o desenvolvimento.

Lembra da questão com async/await? Aqui não existe.

watchEffect(async () => {
    const response = await fetch("...")
    // ...
})

Lembra da regrinha dos react-hooks/exhaustive-deps? Aqui não existe. Perceba como o watchEffect acima não recebe nenhuma dependência. Isso porque, o VueJS vai definir as dependências automaticamente de acordo com o conteúdo do hook. Você até tem um hook que recebe um array (ou valor único) de dependências, o que é muito útil quando você quer ter maior controle de quando algo vai ser chamado. E aqui ele funciona como você espera (e ao contrário do React), é você dizendo ao Vue quando a função deve ser executada.

watch(contagem, (valorNovoDaContagem) => {
    console.log(valorNovoDaContagem)
})

Te peguei! Você é um fanboy de VueJS

Bom, ele tem seus problemas também.

Como ele não é 100% javascript. E pra evitar você precisar 3 arquivos para cada novo componente. O framework oferece o que eles chamam de Single File Component (ou, Componente de Arquivo Único).

Um arquivo .vue com uma tag <template> (html), uma tag <script> (javascript) e uma tag <style> (css).

Os SFC por padrão são muito bons. Mas podem levar a alguns problemas com relação ao uso deles em uma IDE ou Editor de Texto.

Por exemplo: Typescript já era possível na versão 2, porém muito limitado. Na versão 3, o time do Vue melhorou e muito a integração tornando possível escrever Typescript sem muito problemas ou dificuldades. O próprio código do framework agora é em Typescript.

O problema é que o arquivos .vue precisam de um plugin especial para serem entendidos pelo editor de texto. No Visual Studio Code, que utilizo, tem duas. A mais famosa chamada Vetur e uma recente chamada Volar.

A Vetur tem um problema de entender quando você usa na tipagem de uma Prop uma interface ou type alias. Ele ainda não é capaz de entender o que está por trás daquele nome e apresenta uma erro na tela. Isso porque a parte de interpolação de Typescript, responsável por entender a tipagem das props, é recente e ainda marcada como experimental.

Mas mesmo se você passar o tipo direto para a Prop, ainda tem seus problemas.

Por exemplo: Digamos que temos um botão que tem dois tamanhos grande e pequeno. Você tem um prop chamada tamanho e você só quer que seja possível receber uma string grande ou uma string pequeno. A tipagem poderia ser algo como:

// MeuBotao.vue
{
    // ...
    props: {
        tamanho: {
            type: String as PropType<'grande' | 'pequeno'>, 
            required: true
        }
    }
}

Essa tipagem serve pra avisar quem for usar o componente e passar um valor para tamanho saber que somenta esses dois valores são aceitos. E que enviar gigante no lugar não vai fazer sentido.

O Vetur vai te avisar que existe uma prop chamada tamanho se você apertar ctrl + espaço. Mas, atualmente, para ter acesso a informação da tipagem sem precisar abrir o arquivo do componente você precisa parar o mouse em cima da prop e esperar o Vs Code abri um popup com o texto completo da prop (exatamente como escrevi no trecho de código acima).

O auto-complete não vai te avisar nada ou auxiliar na escrita, como o Typescript faz normalmente (que acredito ser uma das forças e motivo pelo qual uso ele).

Esses problemas, até onde vi, não existem na extensão Volar, uma extensão mais recente e sem filiação direta ao time do Vue. Em que o foco é justamente o Typescript. O problema dela é o CSS. Como disse, meu pré-processador favorito é o postcss e usando em conjunto com o TailwindCSS, significa ter uma folha de estilos cheia de diretivas @apply que o CSS normal não entende.

O Volar só está preparado para entender CSS puro. E vai ficar apontando diversos erros durante o CSS (o que pode ser contornado se você colocar o CSS em um arquivo separado e importar ele usando @import dentro da tag <style> do seu componente) ou aceitar os erros que o IDE vai te dar e ignorar (já que o código vai estar funcionando normalmente).

Atualmente eu mantenho as duas instaladas e vou trocando de acordo com a necessidade. Vetur para trabalhar dentro de um SFC onde estarei criando os estilos ou Volar pra quando estou usando ou re-usando componentes e preciso da interpolação do Typescritp na tag <template>.

Resumindo

Trabalhar com VueJS não é em si ruim. Mesmo sendo uma de suas vantagens, o SFC é um problema em potencial. A depender das coisas que o VueJS for passar a ofertar no futuro, pode ser que os editores de texto ou IDE's demorem a conseguirem entender e se integrar 100%.

Uma vez que as coisas estão definidas e funcionando, o projeto consegue rodar e ser escrito de maneira bem flúida.

Geralmente é assim que escrevo componentes VueJS:

Usando a Options API:

// Contador.vue
<template>
    <div>
        <strong>Contagem: {{contagem}}</strong>
        
        <button @click.prevent="incrementar">
            Incrementar contagem
        </button>
    </div>
</template>

<script>
import { defineComponent } from 'vue'

export default defineComponent({
    data() {
        return {
            contagem: 0
        }
    },
    methods: {
        incrementar() {
            this.contagem = this.contagem++
        }
    }
})
</script>

<style scoped>
strong {
    color: #000;
    font-size: 16pt;
}

button {
    color: #fff;
    border: none;
    font-size: 16pt;
    background: #d9173b;
}
</style>

Usando a Composition API:

// Contador.vue
<template>
    <div>
        <strong>Contagem: {{contagem}}</strong>
        
        <button @click.prevent="incrementar">
            Incrementar contagem
        </button>
    </div>
</template>

<script>
import { defineComponent, ref, watch } from 'vue'

export default defineComponent({
    setup() {
        const contagem = ref(0)

        const incrementar = () => {
            contagem.value = contagem.value++
        }

        watch(contagem, () => {
            console.log(contagem.value)
        })

        return { contagem, incrementar }
    }
})
</script>

<style scoped>
strong {
    color: #000;
    font-size: 16pt;
}

button {
    color: #fff;
    border: none;
    font-size: 16pt;
    background: #d9173b;
}
</style>

Conclusão

Não sei se os pontos que levantei te ajudaram a tomar sua decisão. Ou mesmo se o que você procura em um framework é o mesmo que eu.

Mas o meu processo de escolha atual tem sido:

O projeto pode ser uma SPA normal? O projeto tem uma interface ou Design System proprietário a ser seguido (em vez de usar algum template ou biblioteca de componentes pronta)?

VueJS com TailwindCSS.

Agora se o projeto depender de SEO. Ou seja, precisar que os arquivos do build ou no momento da requisição retornem um html com conteúdo pré-pronto.

Aí eu escolho React por meio do Next.js e seu ISR (incremental static regeneration).

O ecossistema VueJS tem uma alternativa, o Nuxt, que fornece SSR ou SSG normalmente e sem muitos problemas. Mas eu vejo a estratégia de ISR como um diferencial positivo a contrapartida React.

E um ponto que pode ser importante. Nas pesquisas que já fiz sobre a diferença entre ambos citavam muito a comunidade e davam a vitória ao React por ter maior comunidade. Mas acredito que hoje em dia essa diferença seja muito menor. Ambas as comunidades são gigantes e vão ter muitas estratégias, bibliotecas, etc. pra te oferecer. As opções só não vão ser equivalentes devido as diferenças inerentes entre ambos os frameworks.

Posts Recomendados em seguida