Estágio 01 · 01-08
LockedTypeScript 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.
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.
Destrava
01-08 é prereq dos seguintes módulos: