Skip to main content

Melhores Práticas

Este guia apresenta as melhores práticas para trabalhar com o Fluid, ajudando você a construir aplicativos de alta qualidade, acessíveis e fáceis de manter.

Estrutura do Projeto

Organização de Componentes

Recomendamos organizar seus componentes em uma estrutura clara:

src/
├── components/ # Componentes reutilizáveis
├── screens/ # Telas do aplicativo
├── navigation/ # Configuração de navegação
├── hooks/ # Hooks personalizados
├── utils/ # Funções utilitárias
├── services/ # Serviços e APIs
├── context/ # Providers de contexto
└── theme/ # Configurações de tema

Componentes Compostos

Para componentes complexos, crie subcomponentes para partes específicas:

// Componente principal
const Form = ({ children, ...props }) => {
return <View {...props}>{children}</View>;
};

// Subcomponentes
Form.Header = ({ title }) => <Text>{title}</Text>;
Form.Input = ({ label, ...props }) => <TextInput label={label} {...props} />;
Form.Button = ({ label, ...props }) => <Button label={label} {...props} />;

// Uso
<Form>
<Form.Header title="Registro" />
<Form.Input label="Nome" />
<Form.Input label="Email" />
<Form.Button label="Enviar" />
</Form>

Uso de Temas

Crie Variáveis de Tema

Em vez de hardcoded values, sempre use o sistema de temas:

// ❌ Evite
<View style={{
backgroundColor: '#FFFFFF',
padding: 16,
borderRadius: 8,
}}>
<Text style={{ color: '#333333', fontSize: 16 }}>
Conteúdo
</Text>
</View>

// ✅ Recomendado
<View style={{
backgroundColor: theme.colors.background.primary,
padding: theme.spacing.md,
borderRadius: theme.borders.radius.md,
}}>
<Text style={{
color: theme.colors.text.primary,
fontSize: theme.typography.fontSize.md
}}>
Conteúdo
</Text>
</View>

Crie Hooks Personalizados para Estilos

Para estilos complexos ou repetitivos, crie hooks:

// hooks/useStyles.js
import { useTheme } from '@platformbuilders/fluid-react-native';

export const useCardStyles = () => {
const theme = useTheme();

return {
container: {
backgroundColor: theme.colors.background.primary,
padding: theme.spacing.md,
borderRadius: theme.borders.radius.md,
...theme.elevations.md,
},
title: {
color: theme.colors.text.primary,
fontSize: theme.typography.fontSize.lg,
fontFamily: theme.typography.fontFamily.bold,
marginBottom: theme.spacing.sm,
},
content: {
color: theme.colors.text.secondary,
fontSize: theme.typography.fontSize.md,
fontFamily: theme.typography.fontFamily.regular,
},
};
};

// Uso
const MyComponent = () => {
const styles = useCardStyles();

return (
<View style={styles.container}>
<Text style={styles.title}>Título</Text>
<Text style={styles.content}>Conteúdo</Text>
</View>
);
};

Performance

Memoização

Use React.memo para componentes que renderizam frequentemente com as mesmas props:

import React from 'react';
import { Text } from '@platformbuilders/fluid-react-native';

const ExpensiveComponent = ({ value }) => {
console.log('Rendering ExpensiveComponent');
// Operação custosa
return <Text>{value}</Text>;
};

// Memoizado - só rerenderiza se as props mudarem
export default React.memo(ExpensiveComponent);

Otimizando Listas

Para listas longas, use FlatList com otimizações:

import React, { useCallback } from 'react';
import { FlatList } from 'react-native';
import { ListItem } from '@platformbuilders/fluid-react-native';

const OptimizedList = ({ data }) => {
// Função memoizada para evitar recriações
const renderItem = useCallback(({ item }) => (
<ListItem
title={item.title}
subtitle={item.subtitle}
/>
), []);

// Extrator de chave memoizado
const keyExtractor = useCallback((item) => item.id, []);

return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
// Otimizações de desempenho
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={5}
removeClippedSubviews={true}
/>
);
};

export default OptimizedList;

Evite Renderizações Aninhadas

Prefira componentes planos em vez de aninhamentos profundos:

// ❌ Evite aninhamentos profundos
<View>
<View>
<View>
<View>
<Text>Conteúdo muito aninhado</Text>
</View>
</View>
</View>
</View>

// ✅ Prefira estruturas mais planas
<View style={containerStyle}>
<Text>Conteúdo com menos aninhamento</Text>
</View>

Acessibilidade

Labels Descritivos

Sempre forneça labels de acessibilidade claros:

// ❌ Evite
<TouchableOpacity onPress={onShare}>
<Icon name="share" />
</TouchableOpacity>

// ✅ Recomendado
<TouchableOpacity
onPress={onShare}
accessibilityLabel="Compartilhar publicação"
accessibilityRole="button"
>
<Icon name="share" />
</TouchableOpacity>

Teste com Leitores de Tela

Teste regularmente sua aplicação com VoiceOver (iOS) e TalkBack (Android).

Agrupe Elementos Relacionados

Agrupe elementos relacionados para leitores de tela:

<View
accessible={true}
accessibilityLabel="Pontuação: 4.5 estrelas de 5"
>
<StarRating rating={4.5} />
<Text>4.5/5</Text>
</View>

Testabilidade

Use TestIDs

Adicione testIDs para facilitar testes automatizados:

<Button 
label="Enviar"
onPress={handleSubmit}
testID="submit_button"
/>

Componentes Puros

Mantenha seus componentes puros e previsíveis:

// ❌ Evite efeitos colaterais imprevisíveis
const ImpureComponent = ({ data }) => {
// Não faça isso dentro do componente
fetch('https://api.example.com')
.then(response => response.json())
.then(result => console.log(result));

return <Text>{data}</Text>;
};

// ✅ Recomendado - componente puro, previsível
const PureComponent = ({ data, onFetch }) => {
useEffect(() => {
if (onFetch) {
onFetch();
}
}, [onFetch]);

return <Text>{data}</Text>;
};

Testes Unitários

Escreva testes unitários para componentes e lógica:

// Button.test.tsx
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import { Button } from '@platformbuilders/fluid-react-native';

describe('Button', () => {
it('renders correctly with label', () => {
const { getByText } = render(
<Button label="Teste" onPress={() => {}} />
);
expect(getByText('Teste')).toBeTruthy();
});

it('calls onPress when pressed', () => {
const onPress = jest.fn();
const { getByText } = render(
<Button label="Teste" onPress={onPress} />
);

fireEvent.press(getByText('Teste'));
expect(onPress).toHaveBeenCalledTimes(1);
});

it('displays loading indicator when loading', () => {
const { getByTestId, queryByText } = render(
<Button label="Teste" onPress={() => {}} loading />
);

expect(getByTestId('loading_indicator')).toBeTruthy();
expect(queryByText('Teste')).toBeNull();
});
});

Reutilização de Código

Hooks Personalizados

Crie hooks para lógica reutilizável:

// hooks/useForm.js
import { useState, useCallback } from 'react';

export const useForm = (initialValues = {}) => {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});

const handleChange = useCallback((field, value) => {
setValues(prev => ({ ...prev, [field]: value }));
// Limpar erro quando o campo é alterado
setErrors(prev => ({ ...prev, [field]: undefined }));
}, []);

const validate = useCallback((validationRules) => {
const newErrors = {};
let isValid = true;

Object.keys(validationRules).forEach(field => {
const value = values[field];
const error = validationRules[field](value, values);

if (error) {
newErrors[field] = error;
isValid = false;
}
});

setErrors(newErrors);
return isValid;
}, [values]);

return {
values,
errors,
handleChange,
validate,
reset: () => setValues(initialValues),
};
};

// Uso
const LoginScreen = () => {
const { values, errors, handleChange, validate } = useForm({
email: '',
password: '',
});

const handleSubmit = () => {
const isValid = validate({
email: value => !value ? 'Email é obrigatório' : null,
password: value => !value ? 'Senha é obrigatória' : null,
});

if (isValid) {
// Lógica de login
}
};

return (
<View>
<TextInput
label="Email"
value={values.email}
error={errors.email}
onChangeText={(text) => handleChange('email', text)}
/>
<TextInput
label="Senha"
value={values.password}
error={errors.password}
secureTextEntry
onChangeText={(text) => handleChange('password', text)}
/>
<Button label="Entrar" onPress={handleSubmit} />
</View>
);
};

Higher-Order Components (HOCs)

Use HOCs para adicionar funcionalidades a componentes existentes:

// withLoading.tsx
import React from 'react';
import { View, ActivityIndicator } from 'react-native';

export const withLoading = (Component) => {
return ({ isLoading, ...props }) => {
if (isLoading) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="#0000ff" />
</View>
);
}

return <Component {...props} />;
};
};

// Uso
const UserProfile = ({ user }) => (
<View>
<Text>{user.name}</Text>
<Text>{user.email}</Text>
</View>
);

const UserProfileWithLoading = withLoading(UserProfile);

// Na tela
<UserProfileWithLoading
isLoading={loading}
user={userData}
/>

Estilização Consistente

Use o Sistema de Espaçamento

Mantenha margens e paddings consistentes usando o sistema de espaçamento:

// ❌ Evite valores arbitrários
<View style={{ margin: 17, padding: 23 }}>
<Text>Conteúdo</Text>
</View>

// ✅ Use o sistema de espaçamento
<View style={{
margin: theme.spacing.md,
padding: theme.spacing.lg
}}>
<Text>Conteúdo</Text>
</View>

Adapte-se a Diferentes Tamanhos de Tela

Use estilos responsivos:

import { useWindowDimensions } from 'react-native';
import { useTheme } from '@platformbuilders/fluid-react-native';

const ResponsiveLayout = ({ children }) => {
const { width } = useWindowDimensions();
const theme = useTheme();

// Determine o layout baseado na largura
const isTablet = width >= theme.breakpoints.tablet;

return (
<View style={{
flexDirection: isTablet ? 'row' : 'column',
padding: isTablet ? theme.spacing.lg : theme.spacing.md,
}}>
{children}
</View>
);
};

Integração com Outras Bibliotecas

Formik

Integrando com Formik para gerenciamento de formulários:

import React from 'react';
import { View } from 'react-native';
import { Formik } from 'formik';
import * as Yup from 'yup';
import { TextInput, Button } from '@platformbuilders/fluid-react-native';

// Schema de validação com Yup
const validationSchema = Yup.object().shape({
name: Yup.string().required('Nome é obrigatório'),
email: Yup.string()
.email('Email inválido')
.required('Email é obrigatório'),
password: Yup.string()
.min(6, 'Senha deve ter pelo menos 6 caracteres')
.required('Senha é obrigatória'),
});

const SignupForm = () => {
return (
<Formik
initialValues={{ name: '', email: '', password: '' }}
validationSchema={validationSchema}
onSubmit={(values) => console.log(values)}
>
{({
handleChange,
handleBlur,
handleSubmit,
values,
errors,
touched,
isSubmitting,
}) => (
<View>
<TextInput
label="Nome"
value={values.name}
onChangeText={handleChange('name')}
onBlur={handleBlur('name')}
error={touched.name && errors.name}
/>

<TextInput
label="Email"
value={values.email}
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
error={touched.email && errors.email}
keyboardType="email-address"
autoCapitalize="none"
/>

<TextInput
label="Senha"
value={values.password}
onChangeText={handleChange('password')}
onBlur={handleBlur('password')}
error={touched.password && errors.password}
secureTextEntry
/>

<Button
label="Cadastrar"
onPress={handleSubmit}
loading={isSubmitting}
/>
</View>
)}
</Formik>
);
};

export default SignupForm;

Redux/Context API

Integrando com gerenciamento de estado:

import React, { createContext, useContext, useReducer } from 'react';

// Criando contexto
const AppContext = createContext();

// Reducer
const reducer = (state, action) => {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'LOGOUT':
return { ...state, user: null };
default:
return state;
}
};

// Provider
export const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, {
user: null,
});

return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};

// Hook personalizado para usar o contexto
export const useApp = () => {
const context = useContext(AppContext);

if (!context) {
throw new Error('useApp deve ser usado dentro de um AppProvider');
}

return context;
};

// Uso com componentes Fluid
import { Button, Text } from '@platformbuilders/fluid-react-native';

const ProfileScreen = () => {
const { state, dispatch } = useApp();

const handleLogout = () => {
dispatch({ type: 'LOGOUT' });
};

return (
<View>
{state.user ? (
<>
<Text>Olá, {state.user.name}</Text>
<Button label="Logout" onPress={handleLogout} />
</>
) : (
<Text>Faça login para continuar</Text>
)}
</View>
);
};

Gerenciamento de Dependências

Mantenha Dependências Atualizadas

Atualize regularmente suas dependências para obter correções de bugs e melhorias:

# Verificar dependências desatualizadas
npm outdated

# Atualizar dependências
npm update

# Para atualizações de versão principal
npx npm-check-updates -u
npm install

Minimize o Número de Dependências

Avalie cuidadosamente cada dependência antes de adicioná-la:

  • O Fluid já fornece muitos componentes comuns
  • Para funcionalidades simples, considere implementá-las você mesmo
  • Verifique o tamanho e a manutenção de cada dependência

Conclusão

Seguindo estas melhores práticas, você poderá construir aplicativos React Native mais eficientes, acessíveis e fáceis de manter usando o Fluid. Lembre-se de que essas diretrizes devem ser adaptadas ao contexto específico do seu projeto.