React Native em 2019, nova arquitetura e comparações com Flutter

O React Native foi lançado em 2015 e desde lá tivemos diversas atualizações e melhorias em cima da arquitetura atual, porém, o coração da biblioteca sempre foi o mesmo, tivemos poucas alterações no comportamento da Bridge (ponte que faz comunicação entre código nativo e Javascript).

Com o avanço de sua utilização e a ascensão de plataformas concorrentes como o Flutter, a equipe do React Native acabou anunciando algumas alterações pesadas na arquitetura da ferramenta que foca em dois principais pontos: melhorar a performance e a integração do código nativo com o Javascript.

Nesse post relatarei as mudanças que farão parte do roadmap do React Native durante 2019.

O conteúdo desse post é baseado em algumas palestras que ocorreram na React Conf, ReactNext e Chain React.

Introdução

Antes de falarmos sobre termos complexos e as atualizações que tornarão o React Native mais robusto e performático em 2019, precisamos voltar um pouco no tempo e entender como os elementos se comportam dentro da DOM nos browsers.

Quando iniciamos aprendendo Javascript no início de tudo, entendemos que com ele podemos manipular tudo o que está dentro da nossa árvore de elementos, a DOM. Por exemplo, para criarmos um parágrafo de texto, podemos simplesmente utilizar algo como:

document.createElement('p')

Melhor ainda, podemos armazenar esse elemento em uma variável, e assim futuramente podemos alterar propriedades e informações diretamente através do Javascript:

var node = document.createElement('p');

// Algum tempo depois
node.innerText('Wooow, legal!')

Apesar de parecer simples, isso é fantástico, faz com que frameworks como React funcionem de maneira mágica já que temos acesso instantâneo e síncrono à referência do elemento na DOM, mas e no React Native?

A DOM do React Native?

Quando utilizamos o Javascript para criar elementos nativos no Android/iOS, todo esse funcionamento é diferente e nós perdemos a referência direta ao elemento criado na árvore, e assim, não podemos manipulá-lo diretamente através do JS.

Devido à falta dessa comunicação direta, o React Native criou a Bridge (ponte). Diferente da DOM convencional, toda comunicação feita através da ponte é serializada em uma String e repassada ao código nativo que processa as operações necessárias como criação de elementos, alterações, remoções, etc...

Apesar de no fim o funcionamento ser parecido, a grande diferença está em que o Javascript não tem noção de quando a operação no código nativo finaliza, ou seja, tudo isso é um processo assíncrono.

Problemas

Por ser assíncrono sofremos com dois grandes problemas:

  1. Não conseguimos ouvir uma resposta de nenhuma operação realizada no código nativo, nem saber se ela foi executada com sucesso, nem cancelar operações no meio;
  2. Em alguns momentos, o processo de enfileiramento e processamento pode ocasionar em lentidões na performance de renderização.

Para exemplificar melhor esse segundo item, imagine que temos uma lista muito grande no React Native. Se você der scroll muito rápido vai ver que os itens não carregam instantaneamente, ou seja, o processo de criação dos elementos nativos é assíncrono:

Perfeito, até agora, lidamos com três principais problemas da ponte do React Native: não temos referência ao elemento nativo, não temos controle da resposta das operações e lentidão na renderização em casos mais complexos.

JavaScript Interface (JSI)

Devido às deficiências apresentadas, o JSI (JavaScript Interface) foi criado para realizar a comunicação entre código Javascript e Java/ObjC. A grande diferença desse método para a Bridge convencional é que a criação de elementos nativos retornam referências e assim podemos manipular esses elementos diretamente, assim como na DOM do browser.

O ponto chave para isso ser possível é que, assim como no browser, agora temos uma camada de código em C++ que realiza a comunicação com o código nativo.

Nova comunicação da JSI com C++

Essa camada também existe no browser e podemos visualizar um exemplo acessando nosso console do navegador e digitando, por exemplo:

navigator.getUserMedia
Retorno da função getUserMedia

Veja que dentro da função temos [native code], isso significa que a implementação da função getUserMedia não está no browser, e sim dentro do código nativo feito com C++. No React Native teremos um funcionamento parecido, pense que estamos querendo buscar as fotos e vídeos do usuário:

getNativeModules('Camera').getPictures({ video: true })

O código acima acessará um recurso feito em C++ que lidará com o retorno acessando diretamente as API's nativas em Java/ObjC:

Camera:HostObject {
  Value get(propName) {
    switch (propName) {
      case 'getPictures':
       // Call Android/iOS API
       return JavaCameraManager.takePic()
    }
  }
}

Nesse momento, o Javascript está chamando diretamente a API nativa de câmera, ou seja, não é assíncrono, você tem controle direto sobre a referência da câmera.

Fabric

Agora que já falamos sobre a nova maneira para a comunicação entre Javascript e código nativo acontecer, podemos falar um pouquinho sobre o Fabric. Esse é o nome, mesmo que provisório, à nova arquitetura de UI do React Native.

Arquitetura de UI do React Native

Antes de falarmos sobre o novo funcionamento, mais uma vez precisamos entender o que acontece hoje: toda vez que um componente é renderizado e cria, modifica ou remove elementos da tela, o Reconciller do React entra em cena, é ele quem entende o que precisa ser modificado e envia através da Bridge para o código nativo as operações que precisam ser realizadas na UI.

Lá na parte nativa, quem ouve isso é o UIManagerModule que delega cada operação de acordo com seu tipo para o UIImplementation. Mesmo não possuindo uma DOM, o React Native cria "shadow nodes" desses elementos, que é uma DOM virtual invisível ao usuário final.

Além disso, essa DOM ainda é enviada ao Yoga que converte a estilização feita via flexbox em coordenadas para posicionar os elementos em tela.

O que muda?

Com a vinda do JSI explicado no tópico anterior, conseguimos diminuir o número de passos para que os elementos sejam criados e manipulados na interface nativa. As funções não precisam mais passar pela Bridge e agora são chamadas diretamente através do código Javascript do React Native.

Na imagem acima você pode visualizar o que vai deixar de existir pela marcação em vermelho.

Isso faz com que a performance para criação desses elementos seja bem superior ao modelo anterior.


Existem alguns outros conceitos que estão surgindo por volta dessa reestruturação da arquitetura do React Native, como o TurboModules e CodeGen, como não são extremamente importantes não vou abordar no post, mas você pode dar uma olhada aqui: http://blog.nparashuram.com/2019/01/react-natives-new-architecture-glossary.html

Flutter vs React Native

O Flutter, outra tecnologia para desenvolvimento multiplataforma de aplicações mobile com interface nativa, por sua vez, utiliza o Dart como linguagem de programação, que por mais que tenha uma sintaxe parecida com Javascript possui alguns pontos negativos na minha opinião principalmente envolvendo a parte estrutural e de estilização que no React é feita com JSX e Flexbox.

Um dos pontos positivos da ferramenta em relação ao React Native até então era a performance já que a comunicação do Dart com o código nativo já é feita de maneira síncrona e a troca de mensagens não acontece via uma Bridge como é até então no React.

Com a vinda do JSI, o React Native não fica mais atrás nesse quesito, melhorando a performance do processo de comunicação entre o código nativo e o Javascript e mantendo todos benefícios de utilizar o Javascript como linguagem principal.

Na minha visão, o React Native ainda tem mais espaço que o Flutter em diversos pontos, mas posso destacar os principais pontos que colocam a ferramenta do Facebook à frente da do Google:

  • Background de empresas utilizando como NuBank, Uber, Instagram, Discord, Pipefy, etc;
  • Sintaxe do código, principalmente na parte estrutural e estilização (JSX e Flexbox);
  • Maturidade da ferramenta e comunidade muito maior em número de pessoas utilizando, testando e criando;
  • Mercado e quantidade de vagas. Encontrar uma vaga hoje com Flutter é praticamente impossível enquanto que o React Native já foi adotado por muitas empresas;
  • Hoje, o React Native possui 1866 contribuidores no Github e em média 600 issues abertas, enquanto o Flutter possui 322 contribuidores e mais de 4000 issues abertas;
  • Ter o Facebook por trás do React Native investindo forte em todo ecossistema, não só na ferramenta em si é um ponto ímpar, por outro lado ainda temos medo do Google pela sua reputação de encerrar produtos pela metade ou não dar a devida atenção aos projetos.

Concluindo

O React Native está em constante evolução, o que me deixa extremamente empolgado em estudar e ensinar sobre essa ferramenta, poder reaproveitar grande base de conhecimento com o front-end e acompanhar todo crescimento e adoção do React em geral é fantástico.

O Flutter ainda é muito recente e imaturo, na minha opinião apostar nessa ferramenta agora não é o melhor caminho. Aqui na Rocketseat nós jamais deixaríamos de apoiar alguma tecnologia se ela fosse realmente mais relevante que outra que já utilizamos, por isso, se em algum momento entendermos que o Flutter for melhor que o RN por algum motivo, seremos os primeiros a nos dedicar nele.

É isso aí dev, não esquece de deixar um comentário com sua opinião sobre tudo isso que conversamos aqui durante esse post!