Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

Componentes e estilos no React Native

Aprenda a criar componentes React Native reutilizáveis, passar props, compor telas e aplicar StyleSheet com um cartão acessível e testado.

Rodolfo Mori5 min de leitura

Um componente React Native é uma função que recebe informações por props e devolve uma parte da interface. Nesta lição, você transforma um cartão repetido em uma peça reutilizável, organiza estilos com StyleSheet e testa a interação sem precisar tocar manualmente na tela a cada mudança.

Use o projeto da primeira lição ou um projeto Expo SDK 57 equivalente. A meta é sair de uma tela que conhece todos os detalhes e chegar a uma tela que combina peças com responsabilidades claras.

Peças de LEGO com encaixes nomeados

Imagine uma caixa de LEGO. Uma peça pode aparecer em várias construções, mas cada encaixe aceita uma conexão prevista. No nosso mapa, o componente é a peça, as props são os encaixes nomeados e a tela é a construção. titulo e concluido, por exemplo, dizem quais informações o cartão aceita.

O nome técnico é composição de componentes. Em vez de criar uma classe que herda toda a tela, a gente monta componentes menores dentro de outros. O TypeScript descreve o contrato das props e o React entrega os valores de cada instância durante a renderização.

A analogia tem limite: uma peça física não muda depois de encaixada. Um componente pode receber novas props e renderizar outra representação. Voltando ao comportamento técnico, React chama a função novamente quando a entrada relevante muda e reconcilia o novo resultado com a interface existente.

Essa composição faz parte do modelo mostrado no guia de React Native: o componente descreve intenção em JSX, React coordena renderizações e React Native traduz os elementos para cada plataforma. Separar as camadas ajuda a diagnosticar. Uma prop ausente é problema do contrato; uma propriedade visual inexistente pertence à API de estilos; um comportamento diferente no Android pede verificação na plataforma.

Comece pelo contrato que a tela precisa cumprir

Crie src/types/habit.ts:

ts
export type Habit = {
  id: string;
  title: string;
  done: boolean;
};

Habit é um alias de tipo. Ele não existe no aplicativo final: serve para o editor e o compilador conferirem que todo hábito tem id, título e estado de conclusão. Agora declare apenas o que o cartão precisa receber:

tsx
type HabitCardProps = {
  habit: Habit;
  onToggle: (id: string) => void;
};

onToggle é uma callback prop, uma função entregue pelo componente pai. O cartão avisa que houve intenção de alternar; ele não decide onde os dados são salvos. Essa fronteira deixa a peça visual simples e testável.

Extraia o cartão sem esconder o que ele faz

Crie src/components/habit-card.tsx:

tsx
import { Pressable, StyleSheet, Text, View } from 'react-native';

import type { Habit } from '@/types/habit';

type HabitCardProps = {
  habit: Habit;
  onToggle: (id: string) => void;
};

export function HabitCard({ habit, onToggle }: HabitCardProps) {
  return (
    <Pressable
      accessibilityLabel={`${habit.title}, ${habit.done ? 'concluído' : 'pendente'}`}
      accessibilityRole="checkbox"
      accessibilityState={{ checked: habit.done }}
      onPress={() => onToggle(habit.id)}
      style={({ pressed }) => [styles.card, pressed && styles.pressed]}
    >
      <View style={[styles.marker, habit.done && styles.markerDone]} />
      <Text style={[styles.title, habit.done && styles.titleDone]}>
        {habit.title}
      </Text>
    </Pressable>
  );
}

Pressable detecta diferentes estados de interação. A prop style recebe uma função porque o valor pressed muda enquanto o dedo ou mouse pressiona. O array de estilos é lido da esquerda para a direita: valores posteriores sobrescrevem propriedades anteriores quando a condição é verdadeira.

Os atributos de acessibilidade não são decoração. accessibilityRole comunica que a peça se comporta como checkbox, accessibilityState informa se está marcada, e o label une nome e situação. Um leitor de tela recebe uma descrição útil, não apenas “botão”.

StyleSheet organiza objetos, não uma folha CSS

Complete o mesmo arquivo:

tsx
const styles = StyleSheet.create({
  card: {
    alignItems: 'center',
    backgroundColor: '#ffffff',
    borderRadius: 14,
    flexDirection: 'row',
    gap: 12,
    minHeight: 64,
    paddingHorizontal: 16,
    paddingVertical: 12,
  },
  pressed: { opacity: 0.72 },
  marker: {
    borderColor: '#486581',
    borderRadius: 10,
    borderWidth: 2,
    height: 20,
    width: 20,
  },
  markerDone: { backgroundColor: '#2f855a', borderColor: '#2f855a' },
  title: { color: '#102a43', flex: 1, fontSize: 17 },
  titleDone: { color: '#627d98', textDecorationLine: 'line-through' },
});

O nome correto continua sendo estilo, mas o formato não é CSS textual. StyleSheet.create recebe objetos JavaScript, usa nomes camelCase como backgroundColor e números sem px para dimensões independentes da densidade. Nem toda propriedade CSS existe em React Native, e algumas têm comportamento específico da plataforma.

Pense no StyleSheet como um guarda-roupa catalogado: card é um conjunto básico, pressed é uma sobreposição temporária e titleDone troca a aparência quando o dado muda. O limite é que estilos não guardam estado; eles só são selecionados pela renderização. Quem decide se titleDone entra no array é habit.done.

Componha duas instâncias na tela

Importe a peça em src/app/index.tsx e passe dados diferentes:

tsx
import { StyleSheet, View } from 'react-native';

import { HabitCard } from '@/components/habit-card';
import type { Habit } from '@/types/habit';

const habits: Habit[] = [
  { id: 'agua', title: 'Beber água', done: true },
  { id: 'leitura', title: 'Ler por 20 minutos', done: false },
];

export default function HomeScreen() {
  function handleToggle(id: string) {
    console.log('alternar', id);
  }

  return (
    <View style={styles.screen}>
      {habits.map((habit) => (
        <HabitCard habit={habit} key={habit.id} onToggle={handleToggle} />
      ))}
    </View>
  );
}

const styles = StyleSheet.create({
  screen: { backgroundColor: '#f0f4f8', flex: 1, gap: 12, padding: 24 },
});

key ajuda o React a identificar cada item entre renderizações. Ela não chega como prop ao HabitCard. Para listas maiores, não mantenha map dentro de uma tela rolável improvisada; a gente usará FlatList na lição de listas.

Entenda a cascata do array de estilos

Você pode provar a ordem com um componente pequeno:

tsx
const styles = StyleSheet.create({
  base: { color: '#102a43', fontSize: 16 },
  warning: { color: '#c53030' },
});

<Text style={[styles.base, true && styles.warning]}>Prazo vencido</Text>;

O texto conserva fontSize: 16 de base, mas a cor final é vermelha porque warning veio depois. Isso é composição de objetos de estilo, não a cascata de seletores do CSS. Duplicar a propriedade depois é uma decisão explícita e local.

Evite também usar o StyleSheet como depósito de nomes sem relação com a peça. card, marker e titleDone dizem qual elemento ou variação recebem. Se dois componentes compartilham apenas a mesma cor, um token de tema pode ser útil; compartilhar o objeto inteiro só para remover três linhas cria acoplamento. Reutilização vale quando preserva uma intenção comum, não apenas quando reduz caracteres.

Um erro comum é passar texto onde a propriedade espera número:

tsx
const styles = StyleSheet.create({
  title: { fontSize: '17px' },
});

Ao rodar npx tsc --noEmit, a conferência reproduzida foi:

Type 'string' is not assignable to type 'number'.

Remova px e mantenha 17. O tipo não garante que a escolha visual ficou boa, mas impede um formato que a API não aceita.

Teste o comportamento público da peça

Um teste valioso não pergunta qual função interna foi chamada pelo React. Ele interage com o papel acessível que a pessoa encontra. Crie o teste:

tsx
import { fireEvent, render, screen } from '@testing-library/react-native';

import { HabitCard } from '@/components/habit-card';

test('envia o id ao pressionar um hábito pendente', () => {
  const onToggle = jest.fn();
  render(
    <HabitCard
      habit={{ id: 'agua', title: 'Beber água', done: false }}
      onToggle={onToggle}
    />,
  );

  fireEvent.press(screen.getByRole('checkbox', { name: /beber água, pendente/i }));
  expect(onToggle).toHaveBeenCalledWith('agua');
});

O comando npm test -- --ci executou essa interação no projeto de prova:

PASS __tests__/habit-card-test.tsx Tests: 1 passed, 1 total

Isso não prova cor, espaçamento ou toque num aparelho real. Prova o contrato observável do componente: ele expõe um checkbox acessível e entrega o id certo quando pressionado.

Missão: crie uma variação sem copiar o componente

Adicione a prop opcional emphasis?: boolean. Quando ela for verdadeira, aplique uma borda azul; quando for falsa ou ausente, preserve o cartão atual. Depois renderize apenas o hábito pendente com ênfase.

text
Pronto quando:
1. HabitCardProps aceita emphasis opcional.
2. O mesmo componente renderiza as duas variações.
3. O teste anterior continua verde.
4. Um novo teste encontra a variação pelo texto e não por detalhes internos.

Se você precisou duplicar todo o JSX, volte ao contrato: a mudança provavelmente é uma prop, não um novo componente. A próxima etapa liga essas peças a dados que mudam em estado e formulários no React Native.

  • componentes react native
  • stylesheet react native
  • props
  • pressable
  • estilos mobile

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, Expo SDK 57.0.15, React Native 0.86.2, React 19.2.3, TypeScript 6.0.3, Jest 29.7.0 e Testing Library 14.0.1, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. React Native — Core Components and Native Components — reactnative.dev
  2. React Native — Style — reactnative.dev
  3. React Native — Pressable — reactnative.dev
  4. React Native — Accessibility — reactnative.dev

Continue por aqui