7 Técnicas Avançadas de TypeScript que Você Deveria Conhecer em 2025
Olá HaWkers, TypeScript se consolidou como a escolha padrão para projetos JavaScript sérios. Em 2025, mais de 78% dos novos projetos web utilizam TypeScript desde o início, segundo pesquisas recentes da comunidade.
Você domina o básico de TypeScript mas sente que está apenas arranhando a superfície? Este artigo vai além de interfaces e tipos básicos, explorando técnicas que separaram desenvolvedores intermediários de especialistas em TypeScript.
1. Strict Mode: A Base de Código Seguro
O modo strict do TypeScript não é uma técnica única, mas um conjunto de flags que tornam seu código drasticamente mais seguro. Em 2025, projetos sem strict: true
são considerados legacy.
// tsconfig.json - Configuração moderna
{
"compilerOptions": {
"strict": true, // Ativa todas as flags strict
"noUncheckedIndexedAccess": true, // TS 5.0+
"noPropertyAccessFromIndexSignature": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true
}
}
O que muitos não percebem é que strict: true
ativa várias flags individualmente:
noImplicitAny
: Proíbeany
implícitostrictNullChecks
:null
eundefined
são tipos distintosstrictFunctionTypes
: Verificação contravariant de parâmetrosstrictBindCallApply
: Tipagem correta para.bind()
,.call()
,.apply()
strictPropertyInitialization
: Propriedades de classe devem ser inicializadasnoImplicitThis
:this
deve ter tipo explícito em contextos ambíguos
Mas o verdadeiro poder vem das flags adicionais como noUncheckedIndexedAccess
, introduzida em versões mais recentes.
// Sem noUncheckedIndexedAccess
const users: Record<string, User> = {};
const user = users['123']; // Type: User
user.name; // ❌ Runtime error: Cannot read property 'name' of undefined
// Com noUncheckedIndexedAccess
const users: Record<string, User> = {};
const user = users['123']; // Type: User | undefined
// user.name; // ❌ Erro de compilação
user?.name; // ✅ Seguro
Esta flag sozinha previne incontáveis bugs em produção relacionados a acessos a objetos que podem não existir.
2. Template Literal Types: Tipos Dinâmicos Poderosos
Template Literal Types, introduzidos no TypeScript 4.1 e expandidos em versões posteriores, permitem criar tipos baseados em strings de forma dinâmica.
// Criando tipos dinâmicos para eventos
type EventName = 'click' | 'focus' | 'blur';
type HandlerName = `on${Capitalize<EventName>}`;
// Type: 'onClick' | 'onFocus' | 'onBlur'
type EventHandlers = {
[K in HandlerName]: (event: Event) => void;
};
// Uso prático
const handlers: EventHandlers = {
onClick: (e) => console.log('Clicked'),
onFocus: (e) => console.log('Focused'),
onBlur: (e) => console.log('Blurred')
};
Isso é extremamente poderoso para APIs que seguem convenções de nomenclatura. Veja um exemplo real com rotas de API:
// Sistema de rotas type-safe
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Resource = 'user' | 'product' | 'order';
type Route = `/${Resource}` | `/${Resource}/${string}`;
type RouteHandler<M extends HTTPMethod, R extends Route> = {
method: M;
route: R;
handler: (req: Request) => Promise<Response>;
};
// Uso com autocomplete total
const userRoute: RouteHandler<'GET', '/user'> = {
method: 'GET',
route: '/user',
handler: async (req) => {
// Implementation
return new Response();
}
};
// ❌ Erro de compilação: 'PATCH' não existe em HTTPMethod
// const invalidRoute: RouteHandler<'PATCH', '/user'> = { ... };
3. Conditional Types e Inferência: Lógica em Tipos
Conditional Types permitem criar lógica complexa no sistema de tipos, similar a operadores ternários em JavaScript.
// Type que extrai o tipo de retorno de uma Promise
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>; // number
// Aplicação prática: função que retorna Promise ou valor direto
async function fetchData<T>(
useCache: boolean,
data: T
): Promise<UnwrapPromise<T>> {
if (useCache) {
return data as UnwrapPromise<T>;
}
// Simula fetch assíncrono
return new Promise(resolve =>
setTimeout(() => resolve(data as UnwrapPromise<T>), 100)
) as any;
}
Um padrão avançado é combinar conditional types com mapped types:
// Transforma propriedades opcionais em obrigatórias e vice-versa
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
type RequiredKeys<T, K extends keyof T> = T & {
[P in K]-?: T[P];
};
interface User {
id: string;
name?: string;
email?: string;
}
// Torna name e email obrigatórios
type FullUser = RequiredKeys<User, 'name' | 'email'>;
// Type: { id: string; name: string; email: string; }
Este padrão é incrivelmente útil para validação de formulários e DTOs onde certas propriedades tornam-se obrigatórias em contextos específicos.
4. Branded Types: Segurança em Tipos Primitivos
Branded Types (ou Nominal Types) resolvem um problema comum: como diferenciar strings com significados diferentes?
// Problema: userId e productId são ambos strings
function getUser(id: string) { /* ... */ }
function getProduct(id: string) { /* ... */ }
const userId = "user123";
const productId = "prod456";
getUser(productId); // ❌ Bug, mas TypeScript não reclama
Solução com Branded Types:
// Criando tipos branded
type Brand<K, T> = K & { __brand: T };
type UserId = Brand<string, 'UserId'>;
type ProductId = Brand<string, 'ProductId'>;
// Funções de criação (smart constructors)
function createUserId(id: string): UserId {
// Validação se necessário
return id as UserId;
}
function createProductId(id: string): ProductId {
return id as ProductId;
}
// Uso type-safe
function getUser(id: UserId) { /* ... */ }
function getProduct(id: ProductId) { /* ... */ }
const userId = createUserId("user123");
const productId = createProductId("prod456");
getUser(userId); // ✅ OK
// getUser(productId); // ❌ Erro de compilação!
Isso é especialmente valioso em sistemas financeiros onde você trabalha com diferentes moedas ou IDs de diferentes domínios.
5. Utility Types Avançados: Além do Partial e Pick
TypeScript oferece utility types poderosos, mas a maioria dos desenvolvedores usa apenas Partial
, Pick
e Omit
. Vamos além:
// ReturnType: Extrai tipo de retorno de função
function createUser() {
return {
id: '123',
name: 'John',
createdAt: new Date()
};
}
type User = ReturnType<typeof createUser>;
// Type: { id: string; name: string; createdAt: Date; }
// Parameters: Extrai tipos de parâmetros de função
function updateUser(id: string, data: { name: string; email: string }) {
// Implementation
}
type UpdateUserParams = Parameters<typeof updateUser>;
// Type: [id: string, data: { name: string; email: string; }]
// Awaited: Unwrap Promise types (TS 4.5+)
type Response = Awaited<Promise<{ data: string }>>;
// Type: { data: string; }
// Combinando utility types
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;
type UserKeys = keyof User; // 'id' | 'name' | 'createdAt'
Um padrão poderoso é criar seus próprios utility types customizados:
// DeepPartial: Torna todas as propriedades aninhadas opcionais
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? DeepPartial<T[P]>
: T[P];
};
interface Config {
database: {
host: string;
port: number;
credentials: {
user: string;
password: string;
};
};
cache: {
ttl: number;
};
}
// Permite updates parciais profundos
function updateConfig(config: DeepPartial<Config>) {
// Você pode passar apenas o que quer atualizar
}
updateConfig({
database: {
credentials: {
password: 'new-password' // Apenas password, resto é opcional
}
}
});
6. Discriminated Unions: Type Guards Automáticos
Discriminated Unions (ou Tagged Unions) são um padrão que torna impossible states impossível de representar.
// Estado de requisição usando union discriminada
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function handleRequest<T>(state: RequestState<T>) {
// TypeScript estreita o tipo automaticamente
switch (state.status) {
case 'idle':
// state.status é 'idle'
// state.data não existe (erro de compilação se tentar acessar)
return 'Aguardando...';
case 'loading':
return 'Carregando...';
case 'success':
// TypeScript sabe que state.data existe aqui!
return `Sucesso: ${state.data}`;
case 'error':
// TypeScript sabe que state.error existe aqui!
return `Erro: ${state.error.message}`;
}
}
// Uso em componentes React
function UserProfile() {
const [userState, setUserState] =
useState<RequestState<User>>({ status: 'idle' });
useEffect(() => {
setUserState({ status: 'loading' });
fetchUser()
.then(data => setUserState({ status: 'success', data }))
.catch(error => setUserState({ status: 'error', error }));
}, []);
return <div>{handleRequest(userState)}</div>;
}
Este padrão elimina bugs comuns onde você acessa data
quando está em estado de loading, ou tenta mostrar error
quando na verdade tem sucesso.
7. Type Predicates: Type Guards Customizados
Type predicates permitem criar funções que informam ao TypeScript sobre o tipo de um valor em runtime.
// Type predicate básico
function isString(value: unknown): value is string {
return typeof value === 'string';
}
// Uso
function processValue(value: string | number) {
if (isString(value)) {
// TypeScript sabe que value é string aqui
console.log(value.toUpperCase());
} else {
// TypeScript sabe que value é number aqui
console.log(value.toFixed(2));
}
}
// Type predicate avançado com validação
interface User {
id: string;
name: string;
email: string;
}
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'name' in obj &&
'email' in obj &&
typeof (obj as any).id === 'string' &&
typeof (obj as any).name === 'string' &&
typeof (obj as any).email === 'string'
);
}
// Uso com API responses
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
if (!isUser(data)) {
throw new Error('Invalid user data from API');
}
// TypeScript sabe que data é User aqui
return data;
}
Bônus: Integração com Zod para Runtime Validation
TypeScript verifica tipos em compile-time, mas APIs retornam dados em runtime. Zod fecha essa lacuna:
import { z } from 'zod';
// Schema Zod
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(2).max(50),
email: z.string().email(),
age: z.number().int().positive().optional()
});
// TypeScript type inferido automaticamente
type User = z.infer<typeof UserSchema>;
// Validação em runtime com type narrowing
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
// Parse valida e retorna User ou lança erro
return UserSchema.parse(data);
}
// Validação segura sem exceções
const result = UserSchema.safeParse(unknownData);
if (result.success) {
// result.data é User
console.log(result.data.name);
} else {
// result.error contém detalhes
console.error(result.error.errors);
}
Esta combinação de TypeScript com Zod é o padrão-ouro em 2025 para aplicações que lidam com dados externos.
O Impacto Real Dessas Técnicas
Implementar essas técnicas não é sobre escrever código "mais bonito" — é sobre prevenir bugs. Em um estudo de 2024, times que adotaram strict mode + branded types + discriminated unions reportaram:
- 38% menos bugs em produção relacionados a tipos incorretos
- 22% de redução em tempo de debugging
- Aumento de 15% na confiança ao refatorar código
TypeScript bem usado transforma seu editor em um assistente que previne erros antes mesmo de você executar o código.
Quer entender melhor os fundamentos de JavaScript que tornam TypeScript possível? Confira meu artigo sobre Programação Funcional em JavaScript onde você vai descobrir conceitos que TypeScript utiliza para inferência de tipos.
Bora pra cima! 🦅
📚 Quer Aprofundar Seus Conhecimentos em JavaScript?
Este artigo cobriu técnicas avançadas de TypeScript, mas há muito mais para explorar no mundo do desenvolvimento moderno.
Desenvolvedores que investem em conhecimento sólido e estruturado tendem a ter mais oportunidades no mercado.
Material de Estudo Completo
Se você quer dominar JavaScript do básico ao avançado, preparei um guia completo:
Opções de investimento:
- 3x de R$34,54 no cartão
- ou R$97,90 à vista
💡 Material atualizado com as melhores práticas do mercado