Sobre nós Guias Projetos Contactos
Админка
please wait

Introdução

O React Native permite criar aplicações móveis nativas utilizando JavaScript e React. Ao contrário das frameworks híbridas, o React Native renderiza componentes nativos reais, proporcionando um desempenho próximo do desenvolvimento nativo, ao mesmo tempo que permite partilhar a maior parte do código entre iOS e Android. Este guia aborda o desenvolvimento prático em React Native com Expo, a toolchain recomendada para a maioria dos projetos.

Configuração do Projeto

Criar Projeto com Expo

# Instalar o Expo CLI globalmente
npm install -g expo-cli
# Criar um novo projeto
npx create-expo-app@latest MyApp
cd MyApp
# Iniciar o servidor de desenvolvimento
npx expo start

Estrutura do Projeto

MyApp/
├── app/ # Ecrãs do Expo Router
│ ├── (tabs)/ # Navegação por tabs
│ │ ├── index.tsx # Tab Início
│ │ ├── profile.tsx # Tab Perfil
│ │ └── _layout.tsx # Layout de tabs
│ ├── _layout.tsx # Layout raiz
│ └── modal.tsx # Ecrã modal
├── components/ # Componentes reutilizáveis
├── constants/ # Constantes da app
├── hooks/ # Custom hooks
├── services/ # Serviços de API
├── assets/ # Imagens, fontes
├── app.json # Configuração do Expo
└── package.json

Componentes Principais

Views e Layout

import { View, Text, StyleSheet } from 'react-native';
export default function HomeScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>Welcome</Text>
<View style={styles.card}>
<Text>Card content</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
backgroundColor: '#fff',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
},
card: {
backgroundColor: '#f5f5f5',
padding: 16,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3, // Sombra no Android
},
});

Layout com Flexbox

<View style={styles.row}>
<View style={styles.box} />
<View style={styles.box} />
<View style={styles.box} />
</View>
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
justifyContent: 'space-between', // Eixo principal
alignItems: 'center', // Eixo transversal
gap: 12,
},
box: {
flex: 1, // Largura igual
height: 100,
backgroundColor: '#007AFF',
},
});

Listas

import { FlatList, View, Text } from 'react-native';
interface Item {
id: string;
title: string;
}
export default function ItemList() {
const items: Item[] = [
{ id: '1', title: 'First Item' },
{ id: '2', title: 'Second Item' },
];
const renderItem = ({ item }: { item: Item }) => (
<View style={styles.item}>
<Text>{item.title}</Text>
</View>
);
return (
<FlatList
data={items}
renderItem={renderItem}
keyExtractor={(item) => item.id}
ItemSeparatorComponent={() => <View style={styles.separator} />}
ListEmptyComponent={<Text>No items</Text>}
refreshing={false}
onRefresh={() => {/*Lógica de refresh*/}}
/>
);
}

Entrada do Utilizador

import { useState } from 'react';
import {
TextInput,
TouchableOpacity,
Text,
View
} from 'react-native';
export default function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = () => {
console.log('Login:', email, password);
};
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="Email"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
autoComplete="email"
/>
<TextInput
style={styles.input}
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
autoComplete="password"
/>
<TouchableOpacity style={styles.button} onPress={handleSubmit}>
<Text style={styles.buttonText}>Login</Text>
</TouchableOpacity>
</View>
);
}

Navegação com Expo Router

Navegação por Tabs

// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
export default function TabLayout() {
return (
<Tabs screenOptions={{
tabBarActiveTintColor: '#007AFF',
}}>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
}}
/>
</Tabs>
);
}

Navegação em Stack

// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="details/[id]"
options={{ title: 'Details' }}
/>
<Stack.Screen
name="modal"
options={{ presentation: 'modal' }}
/>
</Stack>
);
}

Navegação e Parâmetros

// Navegar com parâmetros
import { Link, router } from 'expo-router';
// Declarativo
<Link href="/details/123">View Details</Link>
// Imperativo
router.push('/details/123');
router.replace('/home');
router.back();
// Aceder aos parâmetros
// app/details/[id].tsx
import { useLocalSearchParams } from 'expo-router';
export default function DetailsScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
return <Text>Item ID: {id}</Text>;
}

Gestão de Estado

Contexto para Estado Global

// context/AuthContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';
interface User {
id: string;
email: string;
}
interface AuthContextType {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = async (email: string, password: string) => {
// Chamada à API
const response = await api.login(email, password);
setUser(response.user);
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}

Integração de API

Fetch com Custom Hook

// hooks/useFetch.ts
import { useState, useEffect } from 'react';
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
refetch: () => void;
}
export function useFetch<T>(url: string): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network error');
const json = await response.json();
setData(json);
} catch (e) {
setError(e as Error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [url]);
return { data, loading, error, refetch: fetchData };
}
// Utilização
function UserProfile({ userId }: { userId: string }) {
const { data: user, loading, error } = useFetch<User>(
`https://api.example.com/users/${userId}`
);
if (loading) return <ActivityIndicator />;
if (error) return <Text>Error: {error.message}</Text>;
if (!user) return <Text>User not found</Text>;
return <Text>{user.name}</Text>;
}

Funcionalidades do Dispositivo

Acesso à Câmara

import { Camera, CameraView } from 'expo-camera';
import { useState } from 'react';
export default function CameraScreen() {
const [permission, requestPermission] = Camera.useCameraPermissions();
if (!permission) {
return <View />;
}
if (!permission.granted) {
return (
<View style={styles.container}>
<Text>We need camera permission</Text>
<Button onPress={requestPermission} title="Grant Permission" />
</View>
);
}
return (
<View style={styles.container}>
<CameraView style={styles.camera} facing="back">
{/*Overlay da câmara*/}
</CameraView>
</View>
);
}

Armazenamento Local

import AsyncStorage from '@react-native-async-storage/async-storage';
// Armazenar dados
await AsyncStorage.setItem('userToken', 'abc123');
// Recuperar dados
const token = await AsyncStorage.getItem('userToken');
// Remover dados
await AsyncStorage.removeItem('userToken');
// Armazenar objeto
await AsyncStorage.setItem('user', JSON.stringify(user));
const user = JSON.parse(await AsyncStorage.getItem('user') || '{}');

Notificações Push

import * as Notifications from 'expo-notifications';
import { useEffect, useRef, useState } from 'react';
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
export function usePushNotifications() {
const [expoPushToken, setExpoPushToken] = useState<string>('');
const notificationListener = useRef<Notifications.Subscription>();
useEffect(() => {
registerForPushNotificationsAsync().then(token => {
if (token) setExpoPushToken(token);
});
notificationListener.current = Notifications.addNotificationReceivedListener(
notification => {
console.log('Notification:', notification);
}
);
return () => {
if (notificationListener.current) {
Notifications.removeNotificationSubscription(notificationListener.current);
}
};
}, []);
return expoPushToken;
}
async function registerForPushNotificationsAsync() {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
return null;
}
const token = await Notifications.getExpoPushTokenAsync();
return token.data;
}

Build e Publicação

Build para Produção

# Instalar o EAS CLI
npm install -g eas-cli
# Configurar o projeto
eas build:configure
# Build para iOS
eas build --platform ios
# Build para Android
eas build --platform android
# Build para ambos
eas build --platform all

Configuração da App Store

// app.json
{
"expo": {
"name": "MyApp",
"slug": "myapp",
"version": "1.0.0",
"ios": {
"bundleIdentifier": "com.company.myapp",
"buildNumber": "1"
},
"android": {
"package": "com.company.myapp",
"versionCode": 1
}
}
}

Atualizações Over-the-Air

# Publicar atualização
eas update --branch production --message "Bug fix"
# Configurar atualizações em app.json
{
"expo": {
"updates": {
"url": "https://u.expo.dev/your-project-id"
},
"runtimeVersion": {
"policy": "sdkVersion"
}
}
}

Dicas de Desempenho

  1. Use FlatList para listas longas, não ScrollView
  2. Memorize cálculos dispendiosos com useMemo
  3. Otimize imagens — use tamanhos e formatos adequados
  4. Evite estilos inline — use StyleSheet.create
  5. Use React.memo para componentes puros
  6. Faça profiling com React DevTools para encontrar re-renders

Conclusão

O React Native com Expo proporciona um ambiente produtivo para desenvolvimento mobile. Use o Expo Router para navegação, faça a gestão de estado com Contexto ou Zustand e tire partido da vasta biblioteca de APIs de dispositivo do Expo. A capacidade de partilhar código entre plataformas, mantendo desempenho nativo, faz do React Native uma excelente escolha para desenvolvimento mobile multiplataforma.

 
 
 
Языки
Темы
Copyright © 1999 — 2026
ZK Interactive