Engenharia de IA Aplicada à Programação Web
Aula 2 de 6
Navegar por módulos e aulas
  1. 01 Fundamentos da Web
  2. 02 JavaScript moderno
  3. 03 TypeScript, Git e qualidade
  4. 04 Angular
  5. 05 Node.js, NestJS e APIs
  1. 3.1 Introdução ao TypeScript
  2. 3.2 Interfaces, aliases e contratos
  3. 3.3 Unions, narrowing e generics
  4. 3.4 Configuração, lint e formatação
  5. 3.5 Git, branches e pull requests
  6. 3.6 Testes, documentação e projeto

Módulo 3 · Aula 3.2

Interfaces, type aliases e contratos

2 horas Teoria + laboratório TypeScript, Git e qualidade
Ver fonte Markdown

Identificação

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:

  • interface para contratos de objetos que podem ser estendidos;
  • type para 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:

  1. explicar interface como contrato estático;
  2. criar interfaces e aliases;
  3. diferenciar interface visual e interface de tipo;
  4. escolher entre interface e type;
  5. declarar propriedades opcionais;
  6. utilizar readonly conscientemente;
  7. estender e compor tipos;
  8. compreender tipagem estrutural;
  9. reconhecer verificações de propriedades excedentes;
  10. modelar entradas, entidades e atualizações separadamente;
  11. 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 readonly para identidade e dados imutáveis;
  • derive tipos sem esconder regras;
  • prefira unknown para 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 as como 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, null e undefined tê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.