React Native: jogo de adivinhação (parte 1)
Comece um jogo de adivinhação de números com React Native e Expo, criando componentes próprios (Cabecalho, Cartao, Entrada), telas, estilos com sombra e elevação e cores centralizadas.
1. Visão geral
Neste material iremos desenvolver uma nova aplicação, ilustrando o uso de novos componentes prontos do React, a criação de novos componentes personalizados, o uso mais avançado de recursos para estilizar componentes entre outras coisas. A aplicação que criaremos é um jogo de adivinhação muito conhecido. A aplicação irá gerar um número aleatório e pedir que o usuário tente adivinhar que número é. Quando adivinhar, o jogo acaba. Veja sua tela inicial na Figura 1.1.

Figura 1.1: Tela inicial do jogo.
O que você vai aprender
- Organizar a aplicação em componentes (pasta
components) e telas (pastatelas) - Receber dados via
propse conteúdo viaprops.children - Estilizar com
StyleSheet, Flexbox, sombras (iOS) eelevation(Android) - Mesclar estilos próprios com estilos recebidos via props usando o operador spread
- Centralizar as cores da aplicação em um único arquivo
- Repassar propriedades de um componente para o
TextInputque ele encapsula
O que você vai precisar
- Node.js e o Expo CLI instalados (comando
expo) - VS Code
- Um celular com o Expo Go ou um emulador
2. Criando o projeto
Criando um novo projeto
Abra um terminal e navegue até o seu workspace, que é a pasta que abriga todos os seus projetos React. Lembre-se que o workspace não é uma pasta específica de nenhum projeto. Use o seguinte comando para criar um novo projeto
Terminal
expo init nome_do_seu_app
Abrindo o VS Code
Navegue até o diretório nome_do_seu_app e abra uma instância do VS Code vinculada a ele
Terminal
cd nome_do_seu_app
code .
Iniciando o projeto
Coloque o projeto em execução com o comando
Terminal
npm start
que pode ser digitado a partir do terminal comum ou mesmo do terminal interno do VS Code.
Apagando parte do conteúdo inicial
Comece fazendo uma limpeza no código entregue automaticamente pelo assistente do Expo, existente no arquivo App.js. Não precisaremos do componente textual e do objeto que define alguns estilos. Apagando esses trechos, o resultado deve ser parecido com o que mostra a Listagem 2.4.1.
App.js · Listagem 2.4.1
import React from 'react';
import { StyleSheet, View } from 'react-native';
export default function App() {
return (
<View>
</View>
);
}
const estilos = StyleSheet.create({
});
3. O componente Cabecalho
Como aprendemos, é fortemente recomendável escrever aplicações compostas por diversos componentes, cada qual com uma pequena responsabilidade, ao invés de escrever uma aplicação com um único componente que resolve todos os problemas. Isso nos leva a uma solução de maior nível de reusabilidade e que permite que manutenções futuras sejam feitas com maior facilidade.
Comece criando uma pasta chamada components (o nome pode ser qualquer um) lado a lado com o arquivo App.js. A seguir, crie um arquivo chamado Cabecalho.js dentro da pasta components.
O componente será definido por meio de uma arrow function. Veja sua definição inicial na Listagem 2.5.1.
components/Cabecalho.js · Listagem 2.5.1
const Cabecalho = (props) => {
};
Evidentemente precisaremos trazer o React para o contexto, além de alguns componentes próprios do React Native. Esse componente definirá seus objetos de estilo e, por fim, vamos também especificar que ele é exportado por padrão, como na Listagem 2.5.2.
components/Cabecalho.js · Listagem 2.5.2
import React from 'react';
import {View, Text, StyleSheet} from 'react-native';
const Titulo = (props) => {
};
const estilos = StyleSheet.create({
});
export default Titulo;
A seguir, vamos definir a expressão JSX que diz como se dá a apresentação visual do componente. Note que o texto que ele exibe como título é entregue a ele por meio de seu props. Veja a Listagem 2.5.3.
components/Cabecalho.js · Listagem 2.5.3
const Titulo = (props) => {
return (
<View>
<Text>{props.titulo}</Text>
</View>
);
};
Para estilizar o cabeçalho, vamos criar um objeto JSON como propriedade do objeto estilos, que já criamos. Escolhemos suas medidas, cor e alinhamento. Veja a Listagem 2.5.4.
components/Cabecalho.js · Listagem 2.5.4
const estilos = StyleSheet.create({
cabecalho: {
width: '100%',
height: 95,
paddingTop: 40,
backgroundColor: '#2196F3',
alignItems: 'center',
justifyContent: 'center'
}
});
A seguir, vamos estilizar o texto exibido no cabeçalho. Para isso, criaremos outro objeto JSON para agrupar as propriedades de interesse. Veja a Listagem 2.5.5.
components/Cabecalho.js, no objeto estilos · Listagem 2.5.5
titulo: {
color: '#000',
fontSize: 22
}
Não deixe de aplicar os estilos aos componentes desejados, como na Listagem 2.5.6.
components/Cabecalho.js · Listagem 2.5.6
<View style={estilos.cabecalho}>
<Text style={estilos.titulo}>{props.titulo}</Text>
</View>
Para utilizar o componente, volte ao arquivo App.js. Como sabemos, um componente pode ser utilizado como se fosse uma tag. Ele será filho direto da View principal do componente App. Note que é preciso entregar para ele o texto que irá exibir como título, via props. A Listagem 2.5.7 ilustra isso.
App.js · Listagem 2.5.7
import Cabecalho from './components/Cabecalho';
export default function App() {
return (
<View>
<Cabecalho titulo={"Adivinhe o número"}/>
</View>
);
}
Crie um objeto de estilo para a View principal do arquivo App.js para indicar que ele toma a tela inteira do dispositivo, como na Listagem 2.5.8.
App.js · Listagem 2.5.8
const estilos = StyleSheet.create({
tela: {
flex: 1
}
});
4. A tela TelaComecoDoJogo
A tela principal da aplicação, que exibe o jogo, será definida por um novo componente. A aplicação terá três telas e vamos diferenciar os componentes que representam telas daqueles que não representam, muito embora todos eles sejam componentes.
Comece criando uma pasta chamada telas lado a lado com o arquivo App.js. A seguir, crie um arquivo chamado TelaComecoDoJogo.js na pasta telas.
A definição inicial dele é análoga à definição dos componentes que fizemos até então. Veja a Listagem 2.6.1.
telas/TelaComecoDoJogo.js · Listagem 2.6.1
import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
const TelaComecoDoJogo = (props) => {
}
const estilos = StyleSheet.create({
});
export default TelaComecoDoJogo;
Crie um objeto JSON para estilizar o componente, como na Listagem 2.6.2.
telas/TelaComecoDoJogo.js · Listagem 2.6.2
const TelaComecoDoJogo = (props) => {
return (
<View style={estilos.tela}>
</View>
);
}
const estilos = StyleSheet.create({
tela: {
flex: 1, //toma todo o espaço que puder
padding: 10,
alignItems: 'center'
}
});
No arquivo App.js, passe a utilizar o componente por meio de sua tag, logo abaixo do cabeçalho, como mostra a Listagem 2.6.3.
App.js · Listagem 2.6.3
import TelaComecoDoJogo from './telas/TelaComecoDoJogo';
export default function App() {
return (
<View style={estilos.tela}>
<Cabecalho titulo={"Adivinhe o número"}/>
<TelaComecoDoJogo />
</View>
);
}
Inicialmente, a expressão JSX que descreve o componente TelaComecoDoJogo incluirá
- Um
Textpara exibir uma mensagem inicial - Uma
Viewpara abrir a parte principal da tela - Um
Textque instrui o usuário sobre o que fazer e umTextInputpara ele poder digitar seu número, ambos filhos daViewprincipal - Uma
Viewpara abrir dois botões - Um
Buttonpara reiniciar - Um
Buttonpara jogar
Veja a listagem a seguir.
telas/TelaComecoDoJogo.js · Listagem 2.6.3
import { Text, View, StyleSheet, TextInput, Button } from 'react-native';
const TelaComecoDoJogo = (props) => {
return (
<View style={estilos.tela}>
<Text>Comece um jogo</Text>
<View>
<Text>Escolha um número</Text>
<TextInput />
<View>
<Button
title="Reiniciar"
/>
<Button
title="Confirmar"/>
</View>
</View>
</View>
);
}
Para estilizar o componente, criaremos três objetos JSON.
titulo: aplicado ao texto principal da telaentradaView: aplicado àViewque abriga os elementos de entrada de dados, comoTextInputeButtonsbuttonsView: aplicado àViewque abriga os botões
Veja a Listagem 2.6.4.
telas/TelaComecoDoJogo.js · Listagem 2.6.4
const TelaComecoDoJogo = (props) => {
return (
<View style={estilos.tela}>
<Text style={estilos.titulo}>Comece um jogo</Text>
<View style={estilos.entradaView}>
<Text>Escolha um número</Text>
<TextInput />
<View style={estilos.buttonsView}>
<Button
title="Reiniciar"
/>
<Button
title="Confirmar"/>
</View>
</View>
</View>
);
}
//filhos do objeto estilos
titulo: {
fontSize: 20,
marginVertical: 10,
},
entradaView: {
//300 pontos de largura
width: 300,
//mas no máximo, 80% da tela
maxWidth: '80%',
//alinhamento no eixo perpendicular (horizontal, neste caso)
alignItems: 'center'
},
buttonsView: {
//o padrão é column
flexDirection: 'row',
width: '100%',
// alinhamento no eixo principal (horizontal, neste caso)
justifyContent: 'space-between',
paddingHorizontal: 15
}
Aparência de cartão
Um padrão muito comum utilizado em aplicações web e para dispositivos móveis é o uso de "cartões" para a exibição de conteúdo. Vamos aplicar mais algumas propriedades ao container de entrada de dados para que sua aparência seja como a de um cartão. Altere as propriedades como na Listagem 2.6.5.
telas/TelaComecoDoJogo.js, no objeto estilos · Listagem 2.6.5
entradaView: {
//300 pontos de largura
width: 300,
//mas no máximo, 80% da tela
maxWidth: '80%',
//alinhamento no eixo perpendicular (horizontal, neste caso)
alignItems: 'center',
shadowColor: 'black',
shadowOffset: {
width: 0,
height: 2
},
shadowRadius: 6,
shadowOpacity: 0.32,
backgroundColor: 'white',
},
Perceba, porém, que os atributos de sombra (shadow) não funcionam em dispositivos Android. De fato, eles são exclusivos do iOS. Para obter esse efeito no Android, usamos a propriedade elevation. Ela envolve somente um número que indica a "elevação" no eixo z. Os demais atributos referentes à sombra ficam a cargo da especificação Material Design. Veja a Listagem 2.6.6.
telas/TelaComecoDoJogo.js, no objeto estilos · Listagem 2.6.6
entradaView: {
//300 pontos de largura
width: 300,
//mas no máximo, 80% da tela
maxWidth: '80%',
//alinhamento no eixo perpendicular (horizontal, neste caso)
alignItems: 'center',
shadowColor: 'black',
shadowOffset: {
width: 0,
height: 2
},
shadowRadius: 6,
shadowOpacity: 0.32,
backgroundColor: 'white',
elevation: 4
},
Os botões estão muito colados às bordas da View a que pertencem. Vale a pena adicionar um espaço ali, o que pode ser feito usando a propriedade padding. Além disso, vamos arredondar as bordas, com a propriedade borderRadius. Veja a Listagem 2.6.7.
telas/TelaComecoDoJogo.js, no objeto estilos · Listagem 2.6.7
entradaView: {
//300 pontos de largura
width: 300,
//mas no máximo, 80% da tela
maxWidth: '80%',
//alinhamento no eixo perpendicular (horizontal, neste caso)
alignItems: 'center',
shadowColor: 'black',
shadowOffset: {
width: 0,
height: 2
},
shadowRadius: 6,
shadowOpacity: 0.32,
backgroundColor: 'white',
elevation: 4,
padding: 12,
borderRadius: 8
},
5. Refatoração: o componente Cartao
Um cartão se parece bastante com um componente independente que potencialmente será utilizado em outras partes da aplicação. Parece uma boa ideia criar um componente "cartão" que possa ser utilizado em diversas partes da aplicação.
Comece criando um novo arquivo chamado Cartao.js na pasta components. Seu conteúdo inicial é dado na Listagem 2.7.1.
components/Cartao.js · Listagem 2.7.1
import React from 'react';
import { View, StyleSheet } from 'react-native';
const Cartao = (props) => {
};
const estilos = StyleSheet.create({
});
export default Cartao;
O estilo a ser aplicado no cartão é aquele já definido para a View de entrada de dados, do componente TelaComecoDoJogo. Por isso, traga as configurações para o arquivo Cartao.js, criando um novo objeto JSON para agrupá-las. Note que a largura e o alinhamento não necessariamente são iguais para todos os cartões, por isso removemos essas configurações. Veja a Listagem 2.7.2.
components/Cartao.js · Listagem 2.7.2
import React from 'react';
import { View, StyleSheet } from 'react-native';
const Cartao = (props) => {
};
const estilos = StyleSheet.create({
cartao: {
shadowColor: 'black',
shadowOffset: {
width: 0,
height: 2
},
shadowRadius: 6,
shadowOpacity: 0.32,
backgroundColor: 'white',
elevation: 4,
padding: 12,
borderRadius: 12
}
});
export default Cartao;
A expressão JSX do cartão é uma simples View decorada com o objeto JSON que criamos. Além disso, a ideia é que esse seja um componente cujo conteúdo seja diferente, dependendo do contexto em que for utilizado. Ou seja, deve ser possível entregar para ele o conteúdo que ele deve exibir, no momento em que for usado. Isso pode ser feito via props, lembra? Neste caso, há um nome especial para acesso ao conteúdo de uma View: children. Veja o resultado na Listagem 2.7.3.
components/Cartao.js · Listagem 2.7.3
const Cartao = (props) => {
return (
<View style={estilos.cartao}>{props.children}</View>
);
};
É natural desejarmos viabilizar a passagem de estilos para um componente genérico como um cartão. Porém, no momento ele já ocupa a propriedade style de sua View principal com o objeto de estilos que ele mesmo define. Precisamos de algum mecanismo para mesclar essas configurações com configurações eventualmente enviadas via props. A ideia é muito simples. Vamos construir um novo objeto JSON constituído por todos os pares chave/valor que já existem no objeto cartao e por todos os pares chave/valor que existirem no objeto de estilo possivelmente entregue ao componente via props. Para isso, basta aplicar o operador spread em ambos. Veja o resultado na Listagem 2.7.4.
components/Cartao.js · Listagem 2.7.4
const Cartao = (props) => {
return (
<View style={{...estilos.cartao, ...props.estilos}}>
{props.children}
</View>
);
};
Agora o componente TelaComecoDoJogo já pode usar o componente Cartao que criamos. A Listagem 2.7.5 mostra como. Veja que apenas substituímos a tag View por uma tag Cartao.
telas/TelaComecoDoJogo.js · Listagem 2.7.5
import Cartao from '../components/Cartao'
const TelaComecoDoJogo = (props) => {
return (
<View style={estilos.tela}>
<Text style={estilos.titulo}>Comece um jogo</Text>
<Cartao estilos={estilos.entradaView}>
<Text>Escolha um número</Text>
<TextInput />
<View style={estilos.buttonsView}>
<Button
title="Reiniciar"
/>
<Button
title="Confirmar"/>
</View>
</Cartao>
</View>
);
}
Além disso, precisamos também retirar as configurações de estilo próprias do cartão e manter somente as de largura e alinhamento, próprias desse componente específico, o TelaComecoDoJogo. Remova-as como na Listagem 2.7.6.
telas/TelaComecoDoJogo.js, no objeto estilos · Listagem 2.7.6
entradaView: {
//300 pontos de largura
width: 300,
//mas no máximo, 80% da tela
maxWidth: '80%',
//apagar todos os outros daqui
},
6. Ajustes nos botões e cores centralizadas
Ajustar as cores, tamanho e outras propriedades dos componentes visuais é tão importante quanto as funcionalidades da aplicação. Vamos ajustar alguns aspectos dos botões.
Começamos aplicando um estilo aos botões de modo que eles tenham o mesmo tamanho. Veja a Listagem 2.8.1.
telas/TelaComecoDoJogo.js · Listagem 2.8.1
const TelaComecoDoJogo = (props) => {
return (
<View style={estilos.tela}>
<Text style={estilos.titulo}>Comece um jogo</Text>
<Cartao estilos={estilos.entradaView}>
<Text>Escolha um número</Text>
<TextInput />
<View style={estilos.buttonsView}>
<View style={estilos.botao}>
<Button
title="Reiniciar"
/>
</View>
<View style={estilos.botao}>
<Button
title="Confirmar"
/>
</View>
</View>
</Cartao>
</View>
);
}
//no objeto estilos
botao: {
width: 100
}
Agora desejamos alterar a cor dos botões. Porém, faremos isso de uma maneira inteligente. Dado que as cores poderão ser utilizadas diversas vezes por diferentes componentes da aplicação, iremos defini-las em um único arquivo centralizado, dando nome a cada uma delas. Assim, sempre que for necessário usar uma cor, faremos isso pelo seu nome. No futuro, se uma cor for alterada, a alteração será refletida automaticamente em todos os componentes que a utilizam, simplificando a manutenção. Crie uma pasta chamada cores. Dentro dela, crie um arquivo chamado cores.js. O conteúdo inicial do arquivo é dado na Listagem 2.8.2. Os nomes das cores que utilizaremos são os mesmos utilizados no padrão Material Design.
cores/cores.js · Listagem 2.8.2
export default {
accent: '#FFA726'
}
Aplique a cor associada a accent aos dois botões, como na Listagem 2.8.3.
telas/TelaComecoDoJogo.js · Listagem 2.8.3
import Cores from '../cores/cores'
<View style={estilos.botao}>
<Button
title="Reiniciar"
color={Cores.accent}
/>
</View>
<View style={estilos.botao}>
<Button
title="Confirmar"
color={Cores.accent}
/>
</View>
Aproveite também para definir o azul que o componente Cabecalho utiliza. A Listagem 2.8.4 ilustra como.
cores/cores.js e components/Cabecalho.js · Listagem 2.8.4
export default {
accent: '#FFA726',
primary: '#2196F3'
}
//no Cabecalho.js
import Cores from '../cores/cores';
cabecalho: {
width: '100%',
height: 95,
paddingTop: 40,
backgroundColor: Cores.primary,
alignItems: 'center',
justifyContent: 'center'
},
7. Um componente para a entrada de dados
O componente TextInput mal pode ser visto no momento. Vamos resolver isso criando um novo componente e estilizando-o.
Na pasta components, crie um arquivo chamado Entrada.js. Seu conteúdo inicial é dado na Listagem 2.9.1.
components/Entrada.js · Listagem 2.9.1
import React from 'react'
import { TextInput, StyleSheet } from 'react-native';
const Entrada = (props) => {
};
export default Entrada;
A seguir, crie um objeto chamado entrada para abrigar suas configurações de estilo. Use a mesma técnica já vista para mesclar os atributos de estilo do componente com os atributos de estilo eventualmente recebidos via props. A expressão JSX a ser devolvida contém somente um TextInput. Veja a Listagem 2.9.2.
components/Entrada.js · Listagem 2.9.2
const Entrada = (props) => {
return (
<TextInput style={{...estilos.entrada, ...props.estilos}}/>
);
};
const estilos = StyleSheet.create({
entrada: {
}
});
export default Entrada;
A Listagem 2.9.3 mostra algumas possíveis propriedades a serem alteradas para o TextInput. Adicionamos borda inferior, margem em cima e embaixo, além de alterar a altura do componente.
components/Entrada.js · Listagem 2.9.3
const estilos = StyleSheet.create({
entrada: {
height: 30,
borderBottomColor: 'grey',
borderBottomWidth: 1,
marginVertical: 10
}
});
Para utilizar o novo componente, vá até o arquivo TelaComecoDoJogo.js e faça as alterações exibidas pela Listagem 2.9.4. Note que substituímos o componente TextInput pelo componente Entrada.
telas/TelaComecoDoJogo.js · Listagem 2.9.4
import Entrada from '../components/Entrada'
const TelaComecoDoJogo = (props) => {
return (
<View style={estilos.tela}>
<Text style={estilos.titulo}>Comece um jogo</Text>
<Cartao estilos={estilos.entradaView}>
<Text>Escolha um número</Text>
<Entrada />
<View style={estilos.buttonsView}>
<View style={estilos.botao}>
<Button
title="Reiniciar"
color={Cores.accent}
/>
</View>
<View style={estilos.botao}>
<Button
title="Confirmar"
color={Cores.accent}
/>
</View>
</View>
</Cartao>
</View>
);
}
Note que pode ser de interesse tornar o componente mais largo, além de não permitir que ele cresça horizontalmente conforme o usuário digita. Essas são configurações que dependem do contexto em que o componente está sendo utilizado, por isso, caberá ao componente TelaComecoDoJogo fazer as definições. Veja a Listagem 2.9.5.
telas/TelaComecoDoJogo.js · Listagem 2.9.5
//no objeto estilos
entrada: {
width: 50,
textAlign: 'center'
}
//no uso do componente
<Entrada
style={estilos.entrada}
/>
Repassando propriedades ao TextInput
Em geral, os componentes React possuem diversas propriedades que podem ser alteradas. Nosso componente Entrada encapsula um TextInput e, assim, as propriedades dele ficam escondidas do código cliente do componente Entrada. Há um meio de permitir a especificação de propriedades esperadas pelo TextInput de modo que o componente Entrada as receba e as repasse para ele. Basta aplicar o operador spread no componente TextInput. Veja a Listagem 2.9.6.
components/Entrada.js · Listagem 2.9.6
const Entrada = (props) => {
return (
<TextInput {...props} style={{...estilos.entrada, ...props.estilos}}/>
);
};
Assim, todas as propriedades recebidas pelo componente Entrada serão repassadas para o componente TextInput.
Agora, no ponto em que o componente Entrada é usado, podemos especificar propriedades que ele entregará para o componente TextInput encapsulado por ele. Veja a Listagem 2.9.7.
telas/TelaComecoDoJogo.js · Listagem 2.9.7
<Entrada
style={estilos.entrada}
autoCapitalize='none'
blurOnSubmit
autoCorrect={false}
keyboardType="number-pad"
maxLenth={2}
/>
8. Encerramento
A tela inicial do jogo está pronta visualmente: cabeçalho, cartão com a entrada de dados e botões estilizados, construídos a partir de componentes reutilizáveis (Cabecalho, Cartao e Entrada) e de cores centralizadas.
Próximos passos
- Na parte 2 (codelab
rn-jogo-adivinhacao-parte-2), você implementa as regras do jogo: validação da entrada, estado, alertas, a tela do jogo, os palpites do computador e a tela de fim de jogo.
Referências
- React: A JavaScript library for building user interfaces. 2020. Disponível em https://reactjs.org/. Acesso em janeiro de 2020.
- React Native: A framework for building native apps using React. 2020. Disponível em https://facebook.github.io/react-native/. Acesso em janeiro de 2020.