React e TypeScript: Melhores Práticas para Desenvolvimento Web Moderno
Domine React com TypeScript: patterns, hooks, performance e arquitetura para criar aplicações web robustas e escaláveis.
React consolidou-se como a biblioteca JavaScript mais popular para construção de interfaces de usuário, e TypeScript adicionou type safety que revolucionou a qualidade do código. Juntos, formam uma combinação poderosa para desenvolvimento web moderno.
Por Que React + TypeScript?
Benefícios do TypeScript
- Type Safety: Menos bugs em produção
- IntelliSense: Autocomplete e documentação inline
- Refactoring Seguro: Mudanças com confiança
- Documentação Viva: Tipos servem como documentação
- Escalabilidade: Código mais maintainable
Configuração Inicial
# Criar novo projeto com Vite
npm create vite@latest my-app -- --template react-ts
# Ou com Next.js
npx create-next-app@latest --typescript
# Ou adicionar TypeScript a projeto existente
npm install --save-dev typescript @types/react @types/react-dom
npx tsc --init
Componentes com TypeScript
Functional Components
// Componente simples
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
export const Button: React.FC<ButtonProps> = ({
label,
onClick,
variant = 'primary',
disabled = false
}) => {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-${variant}`}
>
{label}
</button>
);
};
Props com Children
interface CardProps {
title: string;
children: React.ReactNode;
footer?: React.ReactNode;
}
export const Card: React.FC<CardProps> = ({ title, children, footer }) => {
return (
<div className="card">
<div className="card-header">{title}</div>
<div className="card-body">{children}</div>
{footer && <div className="card-footer">{footer}</div>}
</div>
);
};
Event Handlers
interface FormProps {
onSubmit: (data: FormData) => void;
}
export const Form: React.FC<FormProps> = ({ onSubmit }) => {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
onSubmit(formData);
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleInputChange} />
</form>
);
};
Hooks com TypeScript
useState
// Tipo inferido
const [count, setCount] = useState(0);
// Tipo explícito
const [user, setUser] = useState<User | null>(null);
// Com interface
interface User {
id: number;
name: string;
email: string;
}
const [users, setUsers] = useState<User[]>([]);
useEffect
useEffect(() => {
const fetchData = async () => {
const response = await fetch('/api/users');
const data: User[] = await response.json();
setUsers(data);
};
fetchData();
}, []); // Dependências tipadas automaticamente
useContext
interface AuthContextType {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
};
Custom Hooks
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
refetch: () => void;
}
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 () => {
try {
setLoading(true);
const response = await fetch(url);
const json = await response.json();
setData(json);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [url]);
return { data, loading, error, refetch: fetchData };
}
// Uso
const { data, loading } = useFetch<User[]>('/api/users');
Patterns Avançados
Compound Components
interface TabsProps {
defaultValue: string;
children: React.ReactNode;
}
interface TabsContextType {
activeTab: string;
setActiveTab: (tab: string) => void;
}
const TabsContext = createContext<TabsContextType | undefined>(undefined);
export const Tabs: React.FC<TabsProps> & {
List: typeof TabsList;
Tab: typeof Tab;
Panel: typeof TabPanel;
} = ({ defaultValue, children }) => {
const [activeTab, setActiveTab] = useState(defaultValue);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
};
Tabs.List = TabsList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;
Render Props
interface DataFetcherProps<T> {
url: string;
render: (data: T, loading: boolean, error: Error | null) => React.ReactNode;
}
function DataFetcher<T>({ url, render }: DataFetcherProps<T>) {
const { data, loading, error } = useFetch<T>(url);
return <>{render(data, loading, error)}</>;
}
// Uso
<DataFetcher<User[]>
url="/api/users"
render={(users, loading, error) => (
loading ? <Loading /> : <UserList users={users} />
)}
/>
Higher-Order Components (HOC)
function withAuth<P extends object>(
Component: React.ComponentType<P>
): React.FC<P> {
return (props) => {
const { user } = useAuth();
if (!user) {
return <Navigate to="/login" />;
}
return <Component {...props} />;
};
}
// Uso
const ProtectedDashboard = withAuth(Dashboard);
Performance Optimization
React.memo
interface TodoItemProps {
todo: Todo;
onToggle: (id: number) => void;
}
export const TodoItem = React.memo<TodoItemProps>(
({ todo, onToggle }) => {
return (
<div onClick={() => onToggle(todo.id)}>
{todo.title}
</div>
);
},
(prevProps, nextProps) => {
// Custom comparison
return prevProps.todo.id === nextProps.todo.id;
}
);
useCallback
const TodoList: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const handleToggle = useCallback((id: number) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
}, []);
return (
<div>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
))}
</div>
);
};
useMemo
const Dashboard: React.FC<{ data: number[] }> = ({ data }) => {
const statistics = useMemo(() => {
return {
total: data.reduce((sum, val) => sum + val, 0),
average: data.length > 0 ? data.reduce((sum, val) => sum + val, 0) / data.length : 0,
max: Math.max(...data),
min: Math.min(...data),
};
}, [data]);
return <div>Total: {statistics.total}</div>;
};
State Management
Zustand com TypeScript
import create from 'zustand';
interface Todo {
id: number;
title: string;
completed: boolean;
}
interface TodoStore {
todos: Todo[];
addTodo: (title: string) => void;
toggleTodo: (id: number) => void;
removeTodo: (id: number) => void;
}
export const useTodoStore = create<TodoStore>((set) => ({
todos: [],
addTodo: (title) => set((state) => ({
todos: [...state.todos, { id: Date.now(), title, completed: false }]
})),
toggleTodo: (id) => set((state) => ({
todos: state.todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
})),
removeTodo: (id) => set((state) => ({
todos: state.todos.filter(todo => todo.id !== id)
})),
}));
Testing
Jest + React Testing Library
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('should render with label', () => {
render(<Button label="Click me" onClick={() => {}} />);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('should call onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button label="Click" onClick={handleClick} />);
fireEvent.click(screen.getByText('Click'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('should be disabled when prop is true', () => {
render(<Button label="Click" onClick={() => {}} disabled />);
expect(screen.getByRole('button')).toBeDisabled();
});
});
Conclusão
React + TypeScript é a combinação perfeita para desenvolvimento web moderno. Com type safety, melhor DX e código mais maintainable, você estará preparado para criar aplicações escaláveis e robustas.
Precisa de ajuda com seu projeto React? A Inteligencialy tem expertise em React, TypeScript e arquiteturas escaláveis. Entre em contato!
Tags
Artigos relacionados
Desenvolvimento de Plataformas SaaS: Guia Completo para Criar seu Software as a Service
Aprenda tudo sobre desenvolvimento SaaS: arquitetura, tecnologias, modelo de negócio e boas práticas para criar plataformas escaláveis.
REST vs GraphQL com Node.js: quando usar cada um
Compare API REST vs GraphQL com Node.js: diferenças, exemplos práticos, performance e critérios para decidir com segurança.
PWA: transforme seu site em aplicativo
Entenda o que é PWA, como funciona, vantagens, diferenças para app nativo e como transformar site em aplicativo.
Gostou do conteudo?
Entre em contato e descubra como podemos ajudar sua empresa
Falar com especialista