Teu progresso
0 / 83 módulos0%
Estágio 01 · 01-08
BloqueadoTypeScript não é "JS com :string aqui e ali". É um sistema de tipos completo, com computação em compile-time, capaz de modelar invariantes complexas e prevenir bugs na compilação. Devs medianos usam ~10% do que o TS oferece. O resto distingue Senior de Pleno.
Entender TS profundamente significa:
any.TS é structural (estrutural), não nominal. Dois tipos com mesma forma são compatíveis mesmo que de origem diferente.
type Point = { x: number; y: number };
type Vector = { x: number; y: number };
const p: Point = { x: 1, y: 2 };
const v: Vector = p; // OK, mesma forma
Java é nominal: class A e class B com mesma estrutura são incompatíveis. TS não.
Implicação: "duck typing" formalizado. Se anda como pato, é pato.
Tipos primitivos: number, string, boolean, bigint, symbol, null, undefined, void, never, any, unknown.
never: bottom type. Subtipo de tudo. Resultado de função que nunca retorna (throw, loop infinito).
function fail(msg: string): never { throw new Error(msg); }
unknown: top type seguro. Tem que estreitar antes de usar.
const x: unknown = JSON.parse('...');
if (typeof x === 'string') x.toUpperCase(); // OK
x.toUpperCase(); // erro
any: top type inseguro. Aceita qualquer operação. Use raramente: é "desligar o type checker" pontualmente.
void: ausência de retorno (function), mas não exatamente undefined. Tipo retorno void aceita função que retorna qualquer coisa (intencional pra callbacks).
Tipos literais:
type Yes = 'yes';
type Status = 'idle' | 'loading' | 'success' | 'error';
type Code = 200 | 400 | 500;
Combinados com union, modelam estados.
Union (|): A ou B.
type ID = string | number;
Intersection (&): A e B (todas propriedades).
type Person = { name: string };
type Employee = { id: number };
type EmployeePerson = Person & Employee;
Discriminated union (tagged union): sum type com campo discriminante.
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
function handle(r: Result<number, string>) {
if (r.ok) console.log(r.value); // TS infere number
else console.log(r.error); // TS infere string
}
Pattern matching via switch + never pra exaustividade:
function area(s: Shape): number {
switch (s.kind) {
case 'circle': return Math.PI * s.radius ** 2;
case 'square': return s.side ** 2;
default:
const _: never = s; // erro de compilação se faltar caso
throw new Error('unhandled');
}
}
Funções/tipos parametrizados.
function identity<T>(x: T): T { return x; }
identity<number>(5); // T = number
identity('foo'); // T = string (inferido)
class Box<T> {
constructor(public value: T) {}
}
type Pair<K, V> = { key: K; value: V };
Constraints:
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
K extends keyof T força K a ser uma chave de T.
Default generic:
type ApiResponse<T = unknown> = { data: T };
keyof, typeof, indexed accesskeyof T: union dos nomes das chaves de T.
type User = { id: number; name: string };
type UserKey = keyof User; // 'id' | 'name'
typeof x: tipo da expressão (use em valores).
const config = { port: 3000, host: 'localhost' };
type Config = typeof config; // { port: number; host: string }
Indexed access T[K]:
type IdType = User['id']; // number
Tipos derivados iterando sobre keys.
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Partial<T> = { [K in keyof T]?: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] };
type Nullable<T> = { [K in keyof T]: T[K] | null };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Record<K extends keyof any, V> = { [P in K]: V };
Modificadores: +readonly, -readonly, +?, -?. (A maioria são default +.)
Key remapping (TS 4.1+): as cláusula:
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
Tipos que dependem de condições (ternário em tipo).
type IsString<T> = T extends string ? true : false;
type A = IsString<'foo'>; // true
type B = IsString<42>; // false
infer captura tipo dentro de uma condição:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type R = ReturnType<() => number>; // number
type Awaited<T> = T extends Promise<infer U> ? U : T;
Distributive conditional types: quando o tipo testado é union, distribui:
type ToArray<T> = T extends any ? T[] : never;
type R = ToArray<string | number>; // string[] | number[]
Pra evitar distribuição, envolva em tuple: [T] extends [U].
Manipulam strings em tipo:
type Greeting<N extends string> = `hello, ${N}`;
type X = Greeting<'world'>; // 'hello, world'
type EventName<E extends string> = `on${Capitalize<E>}`;
type Y = EventName<'click'>; // 'onClick'
Combinado com mapped types, permite gerar tipos derivados (gettters, action types em redux).
Type guards estreitam tipos no fluxo do código.
Narrowing built-in:
function fn(x: string | number) {
if (typeof x === 'string') x.toUpperCase(); // x: string
else x.toFixed(); // x: number
}
instanceof, in, discriminant.
User-defined type guard:
function isString(x: unknown): x is string {
return typeof x === 'string';
}
function fn(x: unknown) {
if (isString(x)) x.toUpperCase(); // narrowed
}
Assertion functions:
function assert(cond: unknown, msg?: string): asserts cond {
if (!cond) throw new Error(msg);
}
function fn(x: number | undefined) {
assert(x !== undefined);
x.toFixed(); // x: number
}
| Utility | Faz |
|---|---|
Partial<T> | Todas optional |
Required<T> | Todas required |
Readonly<T> | Todas readonly |
Pick<T, K> | Subset de keys |
Omit<T, K> | Tudo menos K |
Record<K, V> | Object map |
Exclude<T, U> | Remove U de union T |
Extract<T, U> | Mantém só U de union T |
NonNullable<T> | Remove null/undefined |
ReturnType<F> | Return type de função |
Parameters<F> | Tuple de args |
Awaited<T> | Unwrap Promise |
InstanceType<C> | Type da instância de class |
Implemente cada um manualmente como exercício, fortalece intuição.
Cat extends Animal, então Producer<Cat> é assignável a Producer<Animal>. Producers (return).Cat extends Animal, então Consumer<Animal> é assignável a Consumer<Cat>. Consumers (args).--strictFunctionTypes): aceita ambos. Use --strict ou --strictFunctionTypes pra checagem correta.Regra prática: function arguments são contravariant, returns são covariant.
tsconfig.json)Ative sempre:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true
}
}
noUncheckedIndexedAccess: arr[i] vira T | undefined. Reflete realidade.
exactOptionalPropertyTypes: distingue { x?: T } de { x: T | undefined }. Importante.
TS suporta declaration merging: múltiplas declarações com mesmo nome são unidas.
interface User { id: number }
interface User { name: string }
// User: { id, name }
Útil pra augmentar libs (declare module 'express' adicionando propriedades).
Module resolution: node, node10, node16, nodenext, bundler. Em projeto TS moderno: nodenext (ESM correto) ou bundler (Vite/esbuild).
TS infere muito. Cenários:
as const): torna literal.
const config = { port: 3000 } as const; // port: 3000 (literal)
satisfies (TS 4.9+): valida tipo sem perder inferência.
const colors = {
red: '#f00',
green: '#0f0',
} satisfies Record<string, `#${string}`>;
// type de colors mantido literal
TS é structural, mas você pode simular nominal:
type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };
const uid: UserId = 'abc' as UserId;
const oid: OrderId = uid; // erro, nominal
Útil pra prevenir mistura de IDs.
O TS de 2026 não é o TS de 2022. Compiler ganhou flags que mudam emit (--erasableSyntaxOnly, --rewriteRelativeImportExtensions), reescrita em Go (tsgo) está em preview com 10x speedup, e o ecossistema de validation runtime (Zod 4, Valibot, ArkType, Effect Schema) consolidou single-source-of-truth schemas substituindo JSON Schema duplicado. Quem ainda escreve enum e namespace em código novo está em 2020.
TS 5.6 (Set 2024). --noUncheckedSideEffectImports (default true sob strict) emite erro quando import "./side-effect" aponta pra arquivo inexistente — antes era silently ignored, fonte de bugs em refactor. Iterator Helper types alinham com ES2025 (.map(), .filter(), .take() em iterators). Disallowed truthy/nullish checks: if (someFn) quando someFn sempre defined vira erro (provavelmente faltou ()).
TS 5.7 (Nov 2024). --rewriteRelativeImportExtensions reescreve .ts → .js em emit, encerrando o debate "preciso escrever .js em import mesmo em source TS?" — escreve .ts em source, sai .js em build. Stricter checks pra variáveis nunca inicializadas. Editor: project loading mais rápido, inlay hints melhores.
TS 5.8 (Mar 2025). --erasableSyntaxOnly é a flag que define o futuro: emite erro em features que TS codegen (não apenas remove types) — enum, namespace com valores, parameter properties (constructor(private x: number)), decorators experimentais. Alinha com Node --experimental-strip-types (Node 22.6+) e Bun strip mode, que rodam TS source apenas removendo annotations. Granular checks em return dentro de conditional types.
const type parameters (TS 5.0, mainstream 2026). Preserva literal types em arguments sem as const no caller — fundamental pra DSL builders e route definitions.
function defineRoutes<const T extends readonly { path: string }[]>(routes: T): T {
return routes;
}
const r = defineRoutes([
{ path: "/users", method: "GET" },
{ path: "/orders", method: "POST" },
] as const); // sem `const T`, `path` viraria `string`; com `const T`, fica literal "/users" | "/orders"
NoInfer<T> (TS 5.4+). Bloqueia inference numa posição específica do generic, forçando outro arg a ser fonte da inferência.
function move<T extends string>(start: T, dest: NoInfer<T>): void {
console.log(`${start} → ${dest}`);
}
move("home", "office"); // T inferido só de start; dest validado contra T sem expandir union
// move("home", "house"); // erro: "house" não é "home"
isolatedDeclarations (TS 5.5+, mainstream 2026). Exige type annotations explícitos em fns exportadas — permite tools terceiros (oxc, swc, esbuild, tsgo) gerar .d.ts sem rodar tsc full. Ganho 10-100x em build pipelines monorepo. Vue 3.5+ adotou; RxJS 8 considera. Em Turborepo, packages com isolatedDeclarations buildam em paralelo sem bloquear declaration emit.
using / await using + Symbol.dispose types (TS 5.2+). Type system reconhece o runtime feature ES2024 (cruza com 01-07) — Disposable e AsyncDisposable interfaces.
async function withDb() {
await using db = await connectPool(); // dispose automático no fim do scope
const rows = await db.query("SELECT 1");
return rows;
} // db.dispose() chamado mesmo se query throw
tsgo — TypeScript em Go (Microsoft, anunciado Mar 2025, expected GA late 2026). Reescrita do compiler em Go pra 10x speedup. Alvo: typecheck instantâneo em monorepos grandes (próprio TS codebase, VS Code, Office). Em 2026 ainda alpha/preview — não substitui tsc em produção, mas vale rodar em CI pra tsc --noEmit quando GA. Fonte: Microsoft DevBlogs "A 10x Faster TypeScript" (Mar 2025).
Validation libs 2026. O ecossistema consolidou em quatro players, cada um com nicho:
parse(schema, value). Use quando bundle critical (edge functions, Cloudflare Workers).type({ name: "string" }). Runtime types == compile-time types automaticamente, sem dupla declaração.// Zod 4 — default
import { z } from "zod";
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
age: z.number().int().min(18),
});
type User = z.infer<typeof UserSchema>;
const result = UserSchema.safeParse(rawInput);
if (!result.success) {
console.error(result.error.issues);
} else {
const user: User = result.data;
}
// ArkType — syntax TS-native
import { type } from "arktype";
const User = type({
id: "string",
email: "string.email",
"age?": "number > 17",
});
type UserT = typeof User.infer; // tipo TS inferido sem declaração separada
tRPC v11 (Mar 2025). Type-safe RPC TS↔TS. Removeu legacy v10 procedures middleware, integrou React Query melhor, suporta streamed responses, file uploads e RSC. Compete com gRPC-web — gRPC mais portable cross-language, tRPC mais ergonômico mas TS-only. Em monorepo Next.js + Node backend, tRPC v11 + Zod 4 schemas dão end-to-end type safety sem codegen.
Type narrowing patterns 2026. Discriminated unions + exhaustiveness via never continuam o padrão. satisfies operator (TS 4.9, mainstream 2026) valida value contra type sem widening — substitui as em config objects. Branded types (nominal via intersection com tag fantasma) pra domain primitives (UserId, OrderId).
type Event =
| { kind: "click"; x: number; y: number }
| { kind: "key"; code: string }
| { kind: "scroll"; delta: number };
const handlers = {
click: (e) => `${e.x},${e.y}`,
key: (e) => e.code,
scroll: (e) => `${e.delta}px`,
} satisfies { [K in Event["kind"]]: (e: Extract<Event, { kind: K }>) => string };
// `satisfies` valida shape; preserva literal types das keys; sem widening
Logística aplicada. API contracts entre app/, web/ e backend/ ficam em Zod 4 schemas + tRPC v11 — single source of truth, type-safe end-to-end. await using db = await connectPool() em handlers garante connection cleanup mesmo em throw. --isolatedDeclarations em packages de monorepo permite Turborepo paralelizar declaration emit. Quando tsgo GA (late 2026), roda em CI pra tsc --noEmit e cai pipeline de 90s pra 9s.
Anti-patterns:
enum em código novo — não eraseable, quebra --erasableSyntaxOnly e --isolatedDeclarations. Use as const literal unions: const Status = { Active: "active", Done: "done" } as const; type Status = typeof Status[keyof typeof Status];.namespace com valor (não apenas types) — quebra strip-types tooling (Node, Bun). Use ES modules.any cast pra "fix" type error — esconde bug. Use unknown + narrow via type guard ou Zod parse.if (typeof x === ...) quando discriminated union resolve. Adiciona campo discriminante (kind, type) e usa exhaustive switch + never.constructor(private x: number)) — quebra isolatedDeclarations e erasableSyntaxOnly. Declara field e atribui no body.--experimentalDecorators) — TC39 stage 3 stable já disponível em TS 5.0+. Migra antes do tooling abandonar legacy.<const T> em toda fn signature por hábito — só onde literal type matters (DSL, config builders). Em fn comum vira ruído.Cruza com: 01-07 (JS/ES2024 — using, iterator helpers, decorators stage 3 que TS types refletem), 02-04 (React — types pra components, hooks, Suspense boundaries), 02-05 (Next.js 15 + RSC — server/client component type integration), 02-08 (backend frameworks — Fastify/Hono types end-to-end com tRPC), 04-05 (API design — Zod schemas como contracts versionáveis).
Fontes: TypeScript release notes 5.6 (Set 2024), 5.7 (Nov 2024), 5.8 (Mar 2025); Microsoft DevBlogs "A 10x Faster TypeScript" (Mar 2025); Zod 4 changelog (Mai 2025); ArkType release notes (Mai 2024); tRPC v11 docs (Mar 2025); Effect Schema docs (effect.website).
Pra passar o Portão Conceitual, sem consultar:
never como bottom type e dar 3 lugares onde aparece.unknown de any com exemplo de uso seguro.Result<T, E>.Partial<T>, Pick<T, K>, Omit<T, K>.ReturnType<F> usando infer.tsconfig.json --strict e o que cada uma faz.as const vs satisfies com exemplo.Implementar um mini Zod-like (validador com inferência de tipos) em TS puro.
Construa uma lib chamada zod-mini com:
Schemas builders:
z.string(), z.number(), z.boolean(), z.literal(value)z.object({ ... }), schema de objetoz.array(schema)z.union([s1, s2]), schema A ou Bz.optional(schema), T | undefinedz.tuple([s1, s2, s3]), schema fixo de tuplez.record(keyS, valS)Refinements:
s.min(n), s.max(n), s.length(n) (string/array)s.regex(re) (string)s.email(), s.uuid() (string)s.int(), s.positive() (number)API:
schema.parse(value): throw em erro, retorna value typed.schema.safeParse(value): retorna { success: true, data } | { success: false, error: ZodError }.z.infer<typeof schema>: tipo TS inferido.Inferência:
const userSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1),
age: z.number().int().min(0).optional(),
roles: z.array(z.union([z.literal('admin'), z.literal('user')])),
});
type User = z.infer<typeof userSchema>;
// type User = {
// id: string;
// name: string;
// age?: number;
// roles: ('admin' | 'user')[];
// }
Erros estruturados (path + code + message), agregados (todos erros, não para no primeiro).
vitest).tsd ou expectType.parse retorna tipo correto sem as.z.literal('admin') mantém tipo literal (precisa de <const>).z.string().transform(s => s.toUpperCase()) muda tipo.s.refine((v) => predicate(v), 'msg').parseAsync que faz validações assíncronas.src/types.ts.Encerramento: após 01-08 você usa TypeScript como ferramenta de modelagem, não só "JS com tipos". Modelagem precisa do domínio em tipos elimina classes inteiras de bugs antes de chegar a runtime.
Threshold de Maestria
Acerte todas as 5 pra marcar o módulo como concluído. Sem pressa, sem timer. Tudo fica salvo no teu navegador.
Q1Por que TypeScript é classificado como structurally typed em vez de nominally typed?
Q2Qual a diferença prática fundamental entre `unknown` e `any` ao consumir JSON externo?
Q3Por que `enum` é desencorajado em código TypeScript novo escrito em 2026?
Q4Em distributive conditional types, como evitar a distribuição sobre uma union?
Q5Qual a diferença prática entre `as const` e o operator `satisfies` (TS 4.9+)?
Destrava
01-08 é prereq dos seguintes módulos: