Dia 1 — Como o React Native roda
O que e React Native e onde o app roda
O que é React Native
React Native não é outra linguagem. É o mesmo JavaScript — o mesmo let, o mesmo
map, o mesmo Promise — rodando dentro de um aplicativo, em vez de dentro de
uma aba do navegador. O arquivo não muda de sintaxe. Muda o que existe em volta
dele: onde o navegador oferece document e window, o React Native oferece
View, Text e StyleSheet.
| Onde | O que aparece na tela | O que existe em volta |
|---|---|---|
| Navegador | div, p, span | document, window |
| React Native | View, Text, Image | StyleSheet, useState, navegação |
É a mesma virada que o Node.js fez no 2º trimestre: a linguagem sozinha produz
efeito visível no navegador; aqui o efeito é um componente nativo desenhado pelo
sistema, e para isso existir precisa de um intermediário.
O exemplo desta página é a prova de que View é função. Ele importa os dois
nomes do módulo, imprime typeof de cada um — e o console responde function
para os dois — e só então chama View({ children: 'ola' }) e lê o que voltou.
O que volta tem duas propriedades: .type, que é o nome View de novo, e
.props, que é o objeto que foi passado. Essa forma — um objeto com tipo e
props — é o que o runtime sabe converter em tela.
O runtime no meio
O JavaScript que você escreve não é executado pela máquina virtual do Android nem
pelo motor do iOS. Existe um runtime no meio que traduz a sua chamada em
operação nativa:
- Você escreve
<View>e o Babel transforma isso numa chamada de função. - O runtime recebe a chamada e monta a árvore de componentes.
- Cada folha da árvore vira um componente nativo do sistema.
- O sistema desenha na tela.
O detalhe que costuma confundir: View é uma função do JavaScript, não uma
tag. Não existe document.createElement('view') por trás; existe uma função
chamada View que o runtime entende. E por isso View precisa estar importada —
component que não foi importado dá erro de "não está definido", o mesmo erro de
uma variável qualquer.
const { View, Text } = require('react-native'); console.log(typeof View); // function console.log(typeof Text); // function // o componente devolve um objeto com o tipo e as props, não HTML const elemento = View({ children: 'ola' }); console.log(elemento.type); // View
Com Text a leitura é a mesma, com uma camada a mais: rotulo.props.children
devolve 'ola'. A children é a prop que carrega o que foi posto dentro da
tag, e ela vale exatamente o que foi passado — texto se o conteúdo era texto,
elemento se era outro elemento. É por isso que a mesma palavra children
funciona para <Text>ola</Text> e para <View><Text>ola</Text></View>: nos dois
casos o conteúdo entrou por ali.
Android e iOS não recebem a mesma coisa
O mesmo arquivo roda nos dois sistemas, mas o resultado tem diferença de
plataforma: botão no iOS tem o comportamento do iOS, no Android tem o do Android.
Isso é proposital — o app não tenta parecer igual nos dois, ele tenta funcionar
bem em cada um.
Quando algo precisa ser específico de uma plataforma, o código separa os dois
casos em um lugar só:
import { Platform } from 'react-native'; const altura = Platform.OS === 'ios' ? 44 : 48;
Platform.OS vale 'ios' ou 'android'. Esse padrão resolve o detalhe de
espaçamento, sombra e fonte que não batem entre plataformas, sem espalhar
condicional pelo código inteiro. A moral é a mesma do ternário em qualquer
lugar: a pergunta fica escrita uma vez, perto do dado que muda, e o resto do
código lê o resultado.
O que o React Native não é
- Não é framework. É biblioteca: você escolhe como organizar o projeto, e
não há um único caminho imposto.
- Não é alternativa ao código nativo. Para aplicativo que precisa de
desempenho muito específico de hardware, Kotlin ou Swift continuam sendo a
escolha.
- Não roda no navegador. O React roda; o React Native não.
Viewnão existe
em página web.
- Não é Electron. O Electron desenha página web dentro de uma janela; o React
Native desenha componente nativo.
O aplicativo é compilado antes de ir para o aparelho
Não existe duplo clique nem recarga de página. O código JavaScript é embutido
no aplicativo e um motor JavaScript — o Hermes, no React Native atual — executa
esse código dentro do aparelho. É por isso que existe o Metro: ele assiste
seus arquivos e reconstrói o pacote a cada alteração, para o app atualizar sem
compilação completa a cada mudança.
A cadeia completa tem quatro elos, e o quarto é o que costuma ser esquecido: o
arquivo é lido, o Babel converte o JSX, o Metro empacota o resultado, e o motor
executa o pacote dentro do aparelho. Se um elo falha, nenhum dos seguintes roda —
e o sintoma é sempre "a tela não atualizou", mesmo quando o problema está no
primeiro elo. Por isso o Metro mostra o erro antes do aparelho: ele falha antes
de entregar o pacote.
Exemplo
// `View` e uma funcao do JavaScript, nao uma tag de HTML. O exemplo // mostra isso: chama a funcao e le o que ela devolve. const { View, Text } = require('react-native'); console.log('View e do tipo:', typeof View); console.log('Text e do tipo:', typeof Text); // o componente devolve um objeto com o tipo e as props const elemento = View({ children: 'ola' }); console.log('o que View devolveu:', elemento.type); console.log('as props:', elemento.props); // o mesmo com Text, e olhando o que foi posto dentro const rotulo = Text({ children: 'ola' }); console.log('o que Text devolveu:', rotulo.type); console.log('o conteudo:', rotulo.props.children);
Saída real
View e do tipo: function
Text e do tipo: function
o que View devolveu: View
as props: { children: 'ola' }
o que Text devolveu: Text
o conteudo: ola
O primeiro script: console e o terminal
O primeiro script: console e o terminal
O console.log é o que produz efeito visível fora da tela do app. Ele escreve
na saída padrão do processo, e no aparelho essa saída é lida por uma ferramenta:
o Metro, no desenvolvimento, e o monitor de logs do Android Studio ou do
Xcode. É o mesmo método que o terminal do Node já usava, e é por ele que se
descobre o que o código está fazendo quando a tela não mostra o que se esperava.
console.log('primeira saida'); console.log('varias linhas', 'no mesmo log');
Os dois argumentos não têm tratamento especial: console.log junta tudo que
receber com espaço e escreve uma linha. É por isso que console.log(a, b) é mais
fácil de ler do que console.log(a + ' ' + b) quando o valor não é texto.
O exemplo da página mostra os três casos. Um console.log com uma string imprime
só a string. O segundo recebe três argumentos e sai como uma linha só, com
varias partes no mesmo log | com pipe no meio — nada indica onde um argumento
terminou e o outro começou. E o terceiro passa o array inteiro: o console desenha
a lista de objetos com o campo de cada um, quebrando a linha quando o objeto é
longo. Esse terceiro é o mais útil em desenvolvimento, porque mostra a estrutura
inteira em vez de um campo escolhido à mão.
Os quatro métodos que importam
console.log imprime. console.error escreve no fluxo de aviso, e é ele que o
runtime usa para avisar sobre um erro — o Warning que aparece quando falta uma
key na lista vem por esse caminho. console.warn é o aviso sem erro, usado por
depreciação. console.table recebe um array de objetos ou um objeto e desenha a
tabela alinhada, que é a forma rápida de conferir uma lista de tarefas que veio
da API.
console.error('falha ao ler a lista'); console.warn('campo titulo vazio, usando o id'); console.table([{ id: 1, texto: 'estudar' }, { id: 2, texto: 'treinar' }]);
O console.table do exemplo recebe a lista de duas tarefas e desenha a tabela
com uma coluna para cada campo que aparecer em qualquer objeto: id, texto e
feita. A coluna (index) à esquerda é a posição na lista, e ela existe para
caso algum objeto esteja sem id — sem isso a linha ficaria sem nome. Campo
que não existe em todos vira célula vazia, e não erro: é o que acontece quando a
lista mistura formatos.
O detalhe que economiza tempo: **console.error e console.log saem em fluxos
diferentes**. O terminal mostra os dois, mas em lugares diferentes — o aviso vai
para o fluxo de erro. Quando a mensagem precisa aparecer junto do resto da
explicação, o par console.error mais console.log imprime a mesma coisa nos
dois lados. É exatamente o que o exemplo faz: falha ao ler a lista sai no
fluxo de erro, e a linha mensagem de erro: falha ao ler a lista repete o texto
no fluxo normal. São duas mensagens com o mesmo conteúdo em lugares diferentes,
não um erro sendo escrito duas vezes.
Comentário: o que fica fora da execução
// comenta o resto da linha. / / comenta um bloco e pode ocupar várias
linhas. Tudo entre o marcador e o fim do trecho é ignorado, e não aparece em
nenhum fluxo de saída:
// este e o nome do item na lista /* este bloco explica a regra inteira: o filtro so passa as tarefas pendentes. */
Este é o primeiro programa de qualquer material de JavaScript, e o mesmo
comentário vale para o app. Comentário não é decoração: é a resposta para a pergunta que o aluno faz
semanas depois, "por que essa linha existe". A regra prática é escrever o **por
que, não o o quê** — // soma os dois numeros não serve para nada, porque a
próxima linha já diz a soma.
A prova de que comentário não executa está no próprio exemplo: os dois comentários
explicam o item da lista e o filtro, e nenhuma linha do terminal os menciona. O
que aparece depois é o resultado do filter — um array com o objeto de id 1 e
o nome em texto, estudar, porque o segundo item estava marcado como feito. A
lista impressa sai do código que roda, não do código comentado.
Rodar o arquivo
Um script de JavaScript puro roda direto no terminal, sem apparatus — sem CLI,
sem prompt de comando, sem DevTools. É a diferença entre executar script e
depender de ferramenta:
node app.js
A entrada do app em React Native é outra: não é node app.js, é o Metro
recebendo a pasta do projeto e montando o pacote para o aparelho. O Metro é o
bundler do React Native — junta os arquivos, resolve os import e reconstrói
o pacote a cada alteração. Essa reconstrução é o fast refresh: o arquivo
salvo volta para a tela em um segundo ou dois, sem recompilar o aplicativo
inteiro.
O que muda do Node para o React Native é o alvo do console.log. No Node ele
aparece no terminal onde o comando foi digitado; no aparelho ele aparece no
monitor do Metro, no logcat do Android ou no console do Xcode. A consequência
prática é a mesma: a linha que aparece ali é o seu código, e o primeiro lugar a
olhar quando a tela não faz o que o código diz é o console.
Quando o log não aparece
Três casos, e os três têm a mesma resposta: conferir se o console.log está
depois do ponto onde o código falha. Se a exceção acontece antes, a linha nunca
chega a ser executada. E console.log dentro de uma função que ninguém chama
também não imprime — a função existe até alguém chamar, como qualquer outra.
O caso mais comum de todos é o terceiro em forma diferente: o console.log
está no fim do arquivo e não imprime nada. A explicação quase nunca é o
console.log — é que a linha está dentro de uma função ou de um componente que
o runtime ainda não chamou. No app, o componente é desenhado por quem o
chamou, e uma função solta no arquivo não é desenhada por ninguém. Por isso a
primeira coisa a tentar, quando o log some, é subir a linha um nível: colocar o
console.log direto no corpo do componente, fora de qualquer função auxiliar, e
ver se ele volta a aparecer.
Exemplo
// Os metodos de console que o aluno usa no desenvolvimento do app, e a // diferenca entre console.log e console.error: os dois vao para o terminal, // mas em fluxos separados. const tarefas = [ { id: 1, texto: 'estudar', feita: false }, { id: 2, texto: 'treinar', feita: true }, ]; // console.log aceita varios argumentos e junta com espaco console.log('primeira saida'); console.log('varias partes', 'no mesmo log', '| com pipe no meio'); console.log('array como texto:', tarefas); // console.table desenha a tabela alinhada console.table(tarefas); // console.warn e aviso sem erro console.warn('campo texto vazio, usando o id'); // console.error vai para o fluxo de aviso. O `console.log` abaixo repete a // mesma mensagem no fluxo normal, para a mensagem aparecer junto do texto. console.error('falha ao ler a lista'); console.log('mensagem de erro:', 'falha ao ler a lista'); // comentario de uma linha e de bloco: nada disso executa // este e o nome do item na lista /* este bloco explica a regra inteira: o filtro so passa as tarefas pendentes. */ const pendentes = tarefas.filter((tarefa) => !tarefa.feita); console.log('pendentes:', pendentes.map((tarefa) => tarefa.texto).join(', '));
Saída real
primeira saida
varias partes no mesmo log | com pipe no meio
array como texto: [
{ id: 1, texto: 'estudar', feita: false },
{ id: 2, texto: 'treinar', feita: true }
]
┌─────────┬────┬───────────┬───────┐
│ (index) │ id │ texto │ feita │
├─────────┼────┼───────────┼───────┤
│ 0 │ 1 │ 'estudar' │ false │
│ 1 │ 2 │ 'treinar' │ true │
└─────────┴────┴───────────┴───────┘
mensagem de erro: falha ao ler a lista
pendentes: estudar