Identificação
- Duração: 2 horas
- Tipo: teoria aplicada e laboratório
- Entrega: modelo tipado de tarefas
- Laboratório:
../exemplos/aula-3.2/index.html
Introdução
O que é uma interface no TypeScript?
Uma interface descreve o formato esperado de um objeto:
interface Task {
id: number;
title: string;
completed: boolean;
}
Ela funciona como um contrato estático. Um valor tratado como Task precisa
apresentar propriedades compatíveis.
Interface TypeScript é interface visual?
Não. A palavra “interface” possui significados diferentes:
- interface visual: elementos com os quais o usuário interage;
- interface TypeScript: contrato de estrutura no código;
- interface de API: contrato de comunicação entre sistemas.
Nesta aula, interface significa uma declaração de tipo.
O que é um type alias?
Um alias atribui um nome a qualquer tipo:
type TaskStatus = "todo" | "doing" | "done";
Também pode nomear objetos:
type Task = {
id: number;
title: string;
};
Interfaces e aliases se sobrepõem em muitos usos, mas não são idênticos.
Interface ou type: qual devo usar?
Uma convenção prática:
interfacepara contratos de objetos que podem ser estendidos;typepara unions, tuplas, primitivas nomeadas e composições;- consistência da equipe é mais importante que transformar a escolha em disputa.
type TaskStatus = "todo" | "doing" | "done";
interface Task {
id: number;
status: TaskStatus;
}
O contrato existe no navegador?
Não. Interfaces e aliases são removidos na compilação:
interface Task {
title: string;
}
Não produz objeto ou função no JavaScript final. Por isso, dados de formulário, JSON e API continuam precisando de validação.
O que significa readonly?
interface Task {
readonly id: number;
title: string;
}
TypeScript impede reatribuir id por código tipado. Isso não congela o objeto em
ambiente de execução e não torna automaticamente propriedades internas imutáveis.
Onde isso aparece no projeto?
O laboratório modelará Task, TaskStatus e TaskPriority. Ele validará strings
recebidas do formulário antes de construir um objeto compatível com o contrato.
Objetivos
Ao final da aula, o aluno deverá conseguir:
- explicar interface como contrato estático;
- criar interfaces e aliases;
- diferenciar interface visual e interface de tipo;
- escolher entre
interfaceetype; - declarar propriedades opcionais;
- utilizar
readonlyconscientemente; - estender e compor tipos;
- compreender tipagem estrutural;
- reconhecer verificações de propriedades excedentes;
- modelar entradas, entidades e atualizações separadamente;
- validar dados antes de afirmar um contrato.
Pré-requisitos
- Aula 3.1 concluída;
- objetos, funções, arrays e JSON;
- noção de compilação e apagamento de tipos.
Pergunta orientadora
Como transformar o formato esperado dos dados em contratos reutilizáveis sem confundir contrato estático com validação real?
Roteiro sugerido
| Etapa | Duração |
|---|---|
| Interfaces e aliases | 25 min |
| Opcionais, readonly e funções | 25 min |
| Extensão e composição | 25 min |
| Modelagem por responsabilidade | 20 min |
| Laboratório | 20 min |
| Revisão | 5 min |
1. Declarando uma interface
interface Student {
id: number;
name: string;
active: boolean;
}
Uso:
const student: Student = {
id: 1,
name: "Ana",
active: true,
};
Se faltar uma propriedade obrigatória, TypeScript informa erro.
2. Alias de objeto
type Student = {
id: number;
name: string;
active: boolean;
};
Para esse caso simples, o comportamento é muito parecido com a interface.
3. Alias de union
type TaskStatus = "todo" | "doing" | "done";
Agora qualquer outro texto é rejeitado:
const status: TaskStatus = "cancelled";
Unions serão aprofundadas na Aula 3.3.
4. Propriedade opcional
interface Task {
title: string;
description?: string;
}
Um objeto pode omitir description:
const task: Task = {
title: "Estudar",
};
Ao ler:
task.description?.toUpperCase();
O tipo é string | undefined.
5. Opcional versus undefined
interface OptionalDescription {
description?: string;
}
Permite omitir a chave.
interface RequiredDescriptionKey {
description: string | undefined;
}
Exige a chave, mesmo que o valor seja undefined.
Essa diferença importa em atualizações, serialização e contratos de API.
6. Opcional versus null
interface Task {
completedAt: string | null;
}
A chave sempre existe, mas null representa ausência intencional.
Escolha conforme o significado do domínio:
- opcional: campo pode não fazer parte daquela representação;
null: campo faz parte do modelo, mas ainda não possui valor;- string vazia: existe texto, porém sem conteúdo.
7. readonly
interface Task {
readonly id: number;
title: string;
}
const task: Task = {
id: 1,
title: "Estudar",
};
task.id = 2; // erro estático
task.title = "Praticar"; // permitido
readonly protege a atribuição pelo sistema de tipos.
8. readonly é raso
interface Task {
readonly metadata: {
source: string;
};
}
task.metadata.source = "manual";
A propriedade metadata não pode apontar para outro objeto, mas seu conteúdo ainda
pode ser alterado.
Para estruturas imutáveis:
interface Task {
readonly metadata: {
readonly source: string;
};
}
9. Arrays somente leitura
interface Task {
readonly tags: readonly string[];
}
Isso impede:
task.tags.push("nova");
Podemos criar um novo array:
const updatedTags = [...task.tags, "nova"];
10. Métodos em interfaces
interface TaskRepository {
findById(id: number): Task | undefined;
save(task: Task): void;
}
A interface descreve o comportamento esperado, não implementa o método.
11. Tipos de função
type TaskFormatter = (task: Task) => string;
const formatTask: TaskFormatter = (task) => {
return `${task.id} — ${task.title}`;
};
O alias pode nomear uma assinatura reutilizável.
12. Extensão de interfaces
interface Entity {
readonly id: number;
createdAt: string;
}
interface Task extends Entity {
title: string;
status: TaskStatus;
}
Task recebe as propriedades de Entity.
Use extensão quando existe uma relação conceitual clara. Não crie hierarquias profundas apenas para reduzir linhas.
13. Intersections
type Entity = {
readonly id: number;
};
type Timestamped = {
createdAt: string;
};
type Task = Entity & Timestamped & {
title: string;
};
O tipo resultante precisa satisfazer todas as partes.
Intersections não são “mesclar objetos em ambiente de execução”. Elas compõem contratos estáticos.
14. Declaration merging
Interfaces com o mesmo nome no mesmo escopo podem ser combinadas:
interface Window {
courseVersion: string;
}
Esse recurso ajuda a ampliar tipos de bibliotecas e do ambiente.
Aliases não permitem a mesma redeclaração. Em contratos internos comuns, evite declarar a mesma interface em vários lugares sem intenção explícita.
15. Tipagem estrutural
TypeScript considera a estrutura:
interface Named {
name: string;
}
const student = {
id: 1,
name: "Ana",
};
function greet(value: Named): string {
return `Olá, ${value.name}`;
}
greet(student);
student é aceito porque possui ao menos a estrutura necessária. Não precisa
declarar que “implementa” Named.
16. Propriedades excedentes
Ao passar um objeto literal diretamente, TypeScript verifica campos inesperados:
function createTask(input: { title: string }): void {
}
createTask({
title: "Estudar",
typo: true,
});
Essa verificação ajuda a encontrar erros de digitação.
Uma variável com propriedades adicionais pode ser compatível estruturalmente:
const input = {
title: "Estudar",
source: "manual",
};
createTask(input);
Isso não significa que TypeScript remove source em ambiente de execução.
17. Index signatures
Quando as chaves são dinâmicas:
interface ErrorMessages {
[field: string]: string;
}
const errors: ErrorMessages = {
title: "Título obrigatório",
estimate: "Estimativa inválida",
};
Uma assinatura ampla diz que toda chave string produz aquele tipo. Use com cuidado, pois ela pode esconder chaves digitadas incorretamente.
18. Record
type TaskStatus = "todo" | "doing" | "done";
const statusLabels: Record<TaskStatus, string> = {
todo: "A fazer",
doing: "Em andamento",
done: "Concluída",
};
Record exige uma entrada para cada status. Ele é um utility type genérico.
19. Separar entrada e entidade
O formulário ainda não possui identificador ou datas:
interface CreateTaskInput {
title: string;
status: TaskStatus;
priority: TaskPriority;
estimate: number;
description?: string;
}
A entidade completa:
interface Task extends CreateTaskInput {
readonly id: number;
readonly createdAt: string;
completedAt: string | null;
}
Não force a interface do formulário a inventar campos que pertencem à criação.
20. Tipo de atualização
Uma atualização não precisa repetir todos os campos:
type UpdateTaskInput = {
title?: string;
status?: TaskStatus;
priority?: TaskPriority;
estimate?: number;
description?: string;
};
Mais adiante poderemos usar:
type UpdateTaskInput = Partial<CreateTaskInput>;
Porém regras específicas podem impedir que todo campo seja opcional ou atualizável.
21. Pick e Omit
type TaskSummary = Pick<
Task,
"id" | "title" | "status"
>;
type EditableTask = Omit<
Task,
"id" | "createdAt"
>;
Esses tipos derivam contratos existentes e reduzem duplicação. Não os use se o resultado ficar mais difícil de compreender que uma declaração direta.
22. satisfies
const statusLabels = {
todo: "A fazer",
doing: "Em andamento",
done: "Concluída",
} satisfies Record<TaskStatus, string>;
satisfies verifica compatibilidade sem substituir o tipo inferido do valor. É útil
para configurações completas.
23. Contrato não valida formulário
Este código é inseguro:
const status = formData.get("status") as TaskStatus;
A assertion não confirma o texto. Valide:
function isTaskStatus(value: unknown): value is TaskStatus {
return (
typeof value === "string" &&
["todo", "doing", "done"].includes(value)
);
}
Depois da verificação, TypeScript estreita o tipo.
24. Modelo do laboratório
type TaskStatus = "todo" | "doing" | "done";
type TaskPriority = "low" | "planned" | "high" | "critical";
interface Task {
readonly id: number;
title: string;
status: TaskStatus;
priority: TaskPriority;
estimate: number;
description?: string;
}
O JavaScript gerado não contém essas declarações. As funções de validação continuam no ambiente de execução.
25. Laboratório guiado
Abra o modelador tipado.
Etapa 1 — Preencha o contrato
Informe título, status, prioridade, estimativa e descrição opcional.
Etapa 2 — Teste campo opcional
Crie uma tarefa com descrição vazia. A propriedade será omitida. Depois, informe uma descrição e compare o objeto.
Etapa 3 — Teste valores
Use estimativa inválida e título curto. A interface deve impedir a criação antes de
afirmar que existe uma Task.
Etapa 4 — Compare TypeScript e JavaScript
Localize em src/app.ts:
TaskStatus;TaskPriority;Task;CreateTaskInput;- validações de ambiente de execução.
Abra dist/app.js e confirme o apagamento dos tipos.
26. Erros comuns
Criar uma interface gigantesca
Contratos que servem formulário, entidade, resposta e atualização ao mesmo tempo acumulam opcionais e perdem significado.
Usar readonly como segurança
É proteção estática contra atribuição, não criptografia ou autorização.
Usar assertion para “resolver”
as Task não cria propriedades nem valida dados.
Confundir opcional e anulável
Ausência da chave e valor null podem ter contratos de API diferentes.
Estender sem relação conceitual
Herança de tipos deve comunicar o domínio, não apenas evitar repetição.
27. Boas práticas
- nomeie contratos pelo papel;
- separe entrada, entidade, resumo e atualização;
- use aliases para unions;
- use interfaces para objetos extensíveis;
- mantenha propriedades obrigatórias quando o domínio exige;
- use
readonlypara identidade e dados imutáveis; - derive tipos sem esconder regras;
- prefira
unknownpara entrada externa; - valide antes de afirmar o contrato;
- documente diferenças entre ausência e
null.
28. Exercícios
Exercício 1 — Interface
Crie Student com readonly id, name e email?.
Exercício 2 — Alias
Crie EnrollmentStatus com três valores literais.
Exercício 3 — Extensão
Crie Entity e estenda em Course.
Exercício 4 — Função
Crie o tipo de uma função que recebe Task e retorna string.
Exercício 5 — Entrada
Modele CreateTaskInput sem os campos gerados pelo sistema.
29. Desafio
Modele:
- entidade completa;
- entrada de criação;
- entrada de atualização;
- resumo para lista;
- mapa de rótulos com
Record; - validador de ambiente de execução para status e prioridade.
Explique cada campo opcional ou anulável.
30. Lista de verificação de conclusão
- Sei explicar interface TypeScript.
- Sei criar aliases de union.
- Diferencio opcional e
undefined. - Diferencio ausente e
null. - Entendo que
readonlyé estático e raso. - Consigo estender e compor contratos.
- Entendo tipagem estrutural.
- Sei separar entrada e entidade.
- Não uso
ascomo validação. - Valido dados de formulário em ambiente de execução.
31. Critérios de avaliação
| Critério | Pontos |
|---|---|
| Modelagem da entidade | 25 |
| Separação dos contratos | 20 |
| Opcionais e readonly coerentes | 15 |
| Composição e reutilização | 15 |
| Validação em ambiente de execução | 20 |
| Explicação das decisões | 5 |
| Total | 100 |
Resumo
Nesta aula, aprendemos que:
- interfaces e aliases nomeiam contratos;
- interfaces não são elementos visuais;
- aliases representam objetos, unions e outros tipos;
- opcionais,
nulleundefinedtêm significados diferentes; readonlyé uma proteção estática e rasa;- TypeScript usa tipagem estrutural;
- contratos de entrada e entidade devem ser separados;
- tipos apagados não validam dados externos.
Próxima aula
Na Aula 3.3, aprofundaremos unions, narrowing, type guards, discriminated unions e generics para representar diferentes estados com segurança.