Kaique Mitsuo Silva Yamamoto
Arquitetura software

Ecossistema React

React e a biblioteca mais popular para construcao de interfaces de usuario. Este guia cobre o ecossistema completo para desenvolvimento web e mobile.

React para Web

Frameworks

FrameworkTipoCaracteristica Principal
Next.jsFull-stackSSR, SSG, App Router
Vite + ReactSPABuild ultra-rapido
RemixFull-stackWeb standards, nested routes
GatsbyStaticCMS, sites estaticos

Next.js

Framework React mais popular para producao.

// app/page.tsx (App Router)
export default function Home() {
  return (
    <main>
      <h1>Bem-vindo</h1>
    </main>
  )
}

// app/users/[id]/page.tsx
interface Props {
  params: { id: string }
}

export default async function UserPage({ params }: Props) {
  const user = await fetch(`/api/users/${params.id}`).then(r => r.json())

  return <div>{user.name}</div>
}

Server Components vs Client Components

// Server Component (padrao)
// Executa no servidor, sem JavaScript no cliente
async function ServerComponent() {
  const data = await db.query('SELECT * FROM users')
  return <ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>
}

// Client Component
'use client'

import { useState } from 'react'

function ClientComponent() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

Server Actions

// app/actions.ts
'use server'

export async function createUser(formData: FormData) {
  const name = formData.get('name')
  await db.insert({ name })
  revalidatePath('/users')
}

// app/users/new/page.tsx
import { createUser } from '../actions'

export default function NewUser() {
  return (
    <form action={createUser}>
      <input name="name" />
      <button type="submit">Criar</button>
    </form>
  )
}

React Native e Expo

Expo

Framework para desenvolvimento mobile com React Native.

# Criar projeto
npx create-expo-app@latest meu-app

# Rodar
npx expo start
// app/(tabs)/index.tsx (Expo Router)
import { View, Text, StyleSheet } from 'react-native'

export default function HomeScreen() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Meu App</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
  },
})

Expo Router

Navegacao baseada em arquivos (similar ao Next.js).

app/
├── (tabs)/
│   ├── _layout.tsx    # Tab Navigator
│   ├── index.tsx      # Tab 1
│   └── settings.tsx   # Tab 2
├── user/
│   └── [id].tsx       # Rota dinamica
└── _layout.tsx        # Layout raiz
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router'
import { Ionicons } from '@expo/vector-icons'

export default function TabLayout() {
  return (
    <Tabs>
      <Tabs.Screen
        name="index"
        options={{
          title: 'Home',
          tabBarIcon: ({ color }) => (
            <Ionicons name="home" size={24} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="settings"
        options={{
          title: 'Config',
          tabBarIcon: ({ color }) => (
            <Ionicons name="settings" size={24} color={color} />
          ),
        }}
      />
    </Tabs>
  )
}

Gerenciamento de Estado

Zustand

Estado global simples e performatico.

import { create } from 'zustand'

interface UserStore {
  user: User | null
  setUser: (user: User) => void
  logout: () => void
}

const useUserStore = create<UserStore>((set) => ({
  user: null,
  setUser: (user) => set({ user }),
  logout: () => set({ user: null }),
}))

// Uso
function Profile() {
  const { user, logout } = useUserStore()

  return (
    <div>
      <p>{user?.name}</p>
      <button onClick={logout}>Sair</button>
    </div>
  )
}

TanStack Query (React Query)

Gerenciamento de estado de servidor.

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'

function UserList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(r => r.json()),
  })

  if (isLoading) return <p>Carregando...</p>
  if (error) return <p>Erro: {error.message}</p>

  return (
    <ul>
      {data.map((user: User) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

function CreateUser() {
  const queryClient = useQueryClient()

  const mutation = useMutation({
    mutationFn: (newUser: NewUser) =>
      fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(newUser),
      }),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] })
    },
  })

  return (
    <button onClick={() => mutation.mutate({ name: 'Novo User' })}>
      Criar
    </button>
  )
}

Estilizacao

Tailwind CSS

function Card({ title, description }: CardProps) {
  return (
    <div className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow">
      <h2 className="text-xl font-bold text-gray-800 mb-2">{title}</h2>
      <p className="text-gray-600">{description}</p>
    </div>
  )
}

NativeWind (Tailwind para React Native)

import { View, Text } from 'react-native'

function Card({ title }: { title: string }) {
  return (
    <View className="bg-white rounded-lg p-4 shadow-md">
      <Text className="text-xl font-bold text-gray-800">{title}</Text>
    </View>
  )
}

Styled Components

import styled from 'styled-components'

const Card = styled.div`
  background: white;
  border-radius: 8px;
  padding: 24px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

  &:hover {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
  }
`

const Title = styled.h2`
  color: #1a1a1a;
  font-size: 1.25rem;
  margin-bottom: 8px;
`

Formularios

React Hook Form + Zod

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'

const schema = z.object({
  email: z.string().email('Email invalido'),
  password: z.string().min(8, 'Minimo 8 caracteres'),
})

type FormData = z.infer<typeof schema>

function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<FormData>({
    resolver: zodResolver(schema),
  })

  const onSubmit = async (data: FormData) => {
    await login(data)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} placeholder="Email" />
      {errors.email && <span>{errors.email.message}</span>}

      <input {...register('password')} type="password" placeholder="Senha" />
      {errors.password && <span>{errors.password.message}</span>}

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Entrando...' : 'Entrar'}
      </button>
    </form>
  )
}

Testes

Vitest + Testing Library

import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { Counter } from './Counter'

describe('Counter', () => {
  it('renders initial count', () => {
    render(<Counter initialCount={5} />)
    expect(screen.getByText('Count: 5')).toBeInTheDocument()
  })

  it('increments count on click', async () => {
    render(<Counter initialCount={0} />)

    fireEvent.click(screen.getByRole('button', { name: /increment/i }))

    expect(screen.getByText('Count: 1')).toBeInTheDocument()
  })
})

React Native Testing Library

import { render, fireEvent } from '@testing-library/react-native'
import { LoginScreen } from './LoginScreen'

describe('LoginScreen', () => {
  it('shows error for invalid email', () => {
    const { getByPlaceholderText, getByText } = render(<LoginScreen />)

    fireEvent.changeText(getByPlaceholderText('Email'), 'invalid')
    fireEvent.press(getByText('Entrar'))

    expect(getByText('Email invalido')).toBeTruthy()
  })
})

Estrutura de Projeto

Next.js (App Router)

src/
├── app/
│   ├── (auth)/
│   │   ├── login/page.tsx
│   │   └── register/page.tsx
│   ├── (dashboard)/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── api/
│   │   └── users/route.ts
│   ├── layout.tsx
│   └── page.tsx
├── components/
│   ├── ui/
│   │   ├── Button.tsx
│   │   └── Input.tsx
│   └── features/
│       └── UserCard.tsx
├── lib/
│   ├── api.ts
│   └── utils.ts
├── hooks/
│   └── useUser.ts
└── types/
    └── user.ts

Expo

app/
├── (tabs)/
│   ├── _layout.tsx
│   ├── index.tsx
│   └── profile.tsx
├── user/[id].tsx
├── _layout.tsx
└── +not-found.tsx
src/
├── components/
├── hooks/
├── services/
├── stores/
└── types/

Recursos