Uma lista vazia não parece perigosa até o código executar users[0].name. Um dicionário também parece completo até uma chave vir de uma requisição, de um arquivo ou de uma configuração que mudou. Sem uma checagem, o TypeScript pode tratar os dois acessos como se o valor existisse.

noUncheckedIndexedAccess muda esse contrato. Ao ativar a opção, uma leitura por índice que pode não encontrar um valor passa a incluir undefined. O compilador não escolhe a correção por você. Ele obriga o código a decidir entre uma guarda, um fallback, outra forma de iteração, um Map ou uma invariável que você consegue provar.

Este artigo mostra o comportamento em arrays e objetos com chaves dinâmicas, uma fixture mínima verificada com TypeScript 5.9.3 e uma migração em etapas. A opção trata de segurança estática do código. Ela não valida o JSON que chega pela rede.

Diagrama mostra arrays e records passando por uma checagem antes de produzir um valor seguro.

Resposta curta

  • Ative noUncheckedIndexedAccess junto de strict quando quiser que leituras indexadas expressem a possibilidade de ausência.
  • Trate array[i] e record[key] como valores que podem ser undefined.
  • Prefira for...of, uma guarda explícita ou um fallback que faça sentido para o domínio.
  • Use ! somente quando uma invariável foi estabelecida fora daquela linha e continua verificável.

O que noUncheckedIndexedAccess muda?

Segundo a documentação do TypeScript sobre noUncheckedIndexedAccess, a opção adiciona undefined a uma propriedade não declarada coberta por uma index signature. Em um array, a mesma ideia vale para uma posição que pode estar fora dos limites. O tipo passa a representar a ausência que já era possível em runtime.

Sem a opção, este código compila mesmo que users esteja vazio:

interface User {
  id: string;
  name: string;
}

const users: User[] = [];
const first = users[0];

console.log(first.name);

Com noUncheckedIndexedAccess: true, first é User | undefined. A linha que acessa name precisa escolher o que fazer quando não existe primeiro usuário:

const first = users[0];

if (first) {
  console.log(first.name);
} else {
  console.log("nenhum usuário encontrado");
}

O ganho não é fazer o array ficar mais seguro em runtime. O array continua podendo estar vazio. O ganho é tirar uma suposição escondida do tipo e colocar a decisão no ponto em que o programa realmente sabe se a ausência é esperada, um erro ou um caso para tentar novamente.

Por que strict não ativa essa opção?

O TypeScript 4.1 explica que noUncheckedIndexedAccess pode gerar ruído em muitos arquivos e, por isso, ela não faz parte do grupo ativado por strict. Um projeto pode usar strict: true e ainda tratar items[index] como se sempre retornasse um item.

Esse detalhe muda a leitura do tsconfig.json. strict é um bom ponto de partida, mas não é uma promessa de que todo acesso por índice foi verificado. Para habilitar a checagem, declare a opção de forma explícita:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true
  }
}

A opção não transforma todos os tipos em T | undefined. Propriedades declaradas continuam conhecidas. O efeito aparece onde a chave ou a posição não é garantida pelo tipo, como um índice numérico de array ou uma chave string usada contra um Record<string, T>.

Onde os erros aparecem primeiro?

O FAQ do TypeScript chama essa configuração de uma verificação ampla: ela não tenta provar que cada acesso está dentro dos limites. Por isso, a primeira compilação costuma apontar padrões que pareciam inofensivos, mas dependiam de dados presentes.

Leitura Tipo com a opção Correção que costuma comunicar melhor a intenção
users[0] User | undefined testar a ausência ou usar uma API que já retorne uma opção explícita
users[index] User | undefined guardar o item em uma variável e estreitar essa variável
usersById[id] User | undefined tratar chave desconhecida ou usar Map.get com a mesma honestidade
path.split("/")[3] string | undefined validar a estrutura antes de usar a parte
map.get(id) já pode ser User | undefined manter o tratamento de ausência

Uma armadilha comum é esperar que um teste de limite resolva tudo:

for (let index = 0; index < users.length; index += 1) {
  users[index].name;
}

O release note do TypeScript registra que até um loop com index < length pode continuar produzindo um erro. O compilador não transforma esse teste em uma prova permanente sobre uma estrutura que pode sofrer mutação. Se o índice é realmente necessário, leia o item, teste o resultado e só depois acesse suas propriedades.

Como corrigir sem usar ! para calar o compilador?

Escolha a correção pelo significado da ausência. Quando todo item presente deve ser processado, for...of elimina o índice e expressa essa intenção diretamente:

for (const user of users) {
  console.log(user.name);
}

Quando a ausência é possível e muda o resultado, use uma guarda:

const user = usersById[userId];

if (!user) {
  return { ok: false, reason: "user-not-found" };
}

return { ok: true, user };

Um fallback também pode ser correto, mas precisa pertencer ao domínio. const limit = settings[key] ?? defaultLimit comunica uma regra. Já settings[key]! apenas afirma que a regra existe sem registrá-la no código. Se alguém renomear a chave, mover a inicialização ou mudar a origem dos dados, o cast não encontra o problema.

Para dicionários com muitas leituras e chaves externas, avalie se Map representa melhor a operação. Map.get já retorna T | undefined, então a decisão de ausência aparece mesmo sem essa opção. Isso não significa que Map seja sempre superior. Significa que o tipo acompanha melhor um conjunto em que a chave pode não existir.

Uma boa migração não transforma todos os erros em if (!value) return. Ela classifica cada leitura: ausência normal, configuração inválida, dado que precisa de fallback ou invariável garantida por outra etapa. O tipo só fica honesto quando o comportamento escolhido também é.

Como ativar a opção sem parar o projeto inteiro?

Trate a ativação como uma mudança de contrato do compilador. Antes de corrigir qualquer linha, rode o typecheck atual e registre qual comando o CI usa. Depois faça a mudança em uma configuração ou pacote que tenha uma fronteira clara.

Uma sequência pequena ajuda:

  1. Ative noUncheckedIndexedAccess no tsconfig do pacote escolhido.
  2. Rode npx tsc --noEmit com o mesmo projeto usado pelo CI.
  3. Separe os erros entre arrays, records, strings divididas e tipos de bibliotecas.
  4. Corrija cada grupo com guarda, iteração, fallback ou uma estrutura de dados adequada.
  5. Execute os testes que cobrem listas vazias, chaves desconhecidas e configurações incompletas.
  6. Mantenha a opção ligada no CI para que novos acessos não reintroduzam a suposição.

Nesta página, eu verifiquei uma fixture mínima com strict, noUncheckedIndexedAccess e noEmit usando TypeScript 5.9.3. Ela compila depois de estreitar o item lido e demonstra o comportamento descrito pela documentação. Isso não é um benchmark de migração: uma base real pode ter erros em tipos de dependências, geradores, testes e código que mistura JavaScript com TypeScript.

Se o pacote faz parte de um monorepo, a opção pode ser introduzida por uma fronteira de projeto e depois avançar para os dependentes. O artigo sobre Project References no TypeScript explica como separar projetos e manter a ordem do build explícita. A preocupação aqui é diferente: o build graph organiza a compilação, enquanto esta opção muda o tipo dos acessos dentro dela.

Se a leitura faz parte do retorno de uma requisição, tipar sucesso e erro de um fetch em TypeScript ajuda a separar a falha de transporte da ausência dentro do dado.

O que a opção não verifica?

noUncheckedIndexedAccess age durante a checagem do código que você escreveu. Ela não inspeciona um JSON vindo da rede, não confirma que uma chave externa tem o formato esperado e não impede que outra camada passe um valor errado por meio de any. Uma interface TypeScript também não faz essa validação em runtime.

Quando o valor atravessa uma fronteira externa, leia-o como unknown e valide sua forma antes de usar. A validação de JSON externo em TypeScript cobre essa etapa. Usar as duas proteções faz sentido: a validação prova o valor recebido, enquanto noUncheckedIndexedAccess mantém honestas as leituras que o programa faz depois.

A opção também não detecta todos os problemas de objetos dinâmicos. Uma chave como __proto__, uma colisão de propriedade herdada ou um contrato de autorização exige uma decisão de runtime. Para esses casos, combine tipos com validação, estruturas de dados e testes que alcancem a fronteira real.

Use testes unitários ou de integração em Node.js para decidir se a correção precisa provar apenas a lógica de estreitamento ou também a fonte que produz as chaves e posições.

Perguntas frequentes

noUncheckedIndexedAccess faz parte de strict?

Não. O release note do TypeScript 4.1 explica que a opção não é ativada por strict porque pode gerar muitos erros de migração. Declare "noUncheckedIndexedAccess": true separadamente quando quiser que arrays e index signatures expressem a possibilidade de undefined.

Por que for...of costuma resolver o erro do array?

for...of entrega cada elemento existente da iteração, em vez de pedir ao compilador que prove o resultado de um índice numérico. Ele não torna uma fonte externa confiável nem impede mutações fora do loop. Apenas escolhe uma operação cujo contrato já representa melhor o processamento de todos os itens presentes.

Posso usar ! depois de ativar a opção?

Pode, mas o operador deve representar uma invariável que o programa realmente estabeleceu. Se uma função só pode ser chamada depois de carregar uma configuração obrigatória, documente e teste essa pré-condição. Para uma chave, posição ou resposta que pode faltar normalmente, ! remove a evidência sem corrigir o comportamento.

A opção substitui validação de JSON?

Não. Ela verifica tipos e acessos no código compilado. JSON, variáveis de ambiente e respostas HTTP chegam em runtime e precisam de uma validação própria. Trate a entrada como unknown, valide o formato e só depois use o código TypeScript que continua protegido contra índices ausentes dentro do fluxo.

Conclusão

Ativar noUncheckedIndexedAccess torna explícita uma pergunta que muitos arrays e records escondem: o valor está realmente lá? A resposta pode ser uma guarda, um fallback, for...of, Map.get ou uma invariável verificável. O compilador não impõe uma política única, mas deixa de aceitar o silêncio como prova.

Comece por um pacote pequeno, rode o mesmo typecheck do CI e corrija os erros pelo significado da ausência. Não use o operador ! para transformar uma dúvida de dados em certeza de tipo. E mantenha a fronteira clara: esta opção melhora o código que você compila, enquanto valores externos continuam precisando de validação em runtime.

Fontes consultadas

Este texto foi produzido com assistência de IA para organizar pesquisa, rascunhar prosa, localizar versões e criar a ilustração. A fixture e as afirmações técnicas foram verificadas contra a documentação citada; a imagem é explicativa e não representa uma saída real do compilador.