Typescript: Sugestão: opção de incluir indefinido nas assinaturas de índice

Criado em 31 jan. 2017  ·  242Comentários  ·  Fonte: microsoft/TypeScript

Atualização : para minha última proposta, consulte o comentário https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -406316164

Com strictNullChecks habilitado, o TypeScript não inclui undefined em assinaturas de índice (por exemplo, em um objeto ou array). Esta é uma advertência bem conhecida e discutida em vários problemas, nomeadamente https://github.com/Microsoft/TypeScript/issues/9235 , https://github.com/Microsoft/TypeScript/issues/13161 , https: // github .com / Microsoft / TypeScript / issues / 12287 e https://github.com/Microsoft/TypeScript/pull/7140#issuecomment -192606629.

Exemplo:

const xs: number[] = [1,2,3];
xs[100] // number, even with strictNullChecks

No entanto, ao ler as edições acima, parece que muitos usuários do TypeScript gostariam que não fosse esse o caso. Concedido, se as assinaturas de índice incluíram undefined , o código provavelmente exigirá muito mais proteção, mas - para alguns - esta é uma troca aceitável para maior segurança de tipo.

Exemplo de assinaturas de índice incluindo undefined :

const xs: number[] = [1,2,3];
xs[100] // number | undefined

Gostaria de saber se esse comportamento pode ser considerado como uma opção extra do compilador além de strictNullChecks . Desta forma, podemos satisfazer todos os grupos de usuários: aqueles que desejam verificações estritas de nulos com ou sem indefinido em suas assinaturas de índice.

Committed Suggestion

Comentários muito úteis

Continuamos bastante céticos de que alguém obteria algum benefício com esta bandeira na prática. Mapas e coisas semelhantes a mapas já podem ativar | undefined em seus sites de definição

Um dos objetivos do TypeScript não é permitir que erros sejam detectados no momento da "compilação", em vez de confiar que o usuário se lembre / saiba fazer algo específico? Isso parece ir contra esse objetivo; exigir que o usuário faça algo para evitar travamentos. O mesmo pode ser dito para muitos outros recursos; eles não são necessários se o desenvolvedor sempre fizer x. O objetivo do TypeScript é (presumivelmente) tornar o trabalho mais fácil e eliminar essas coisas.

Encontrei esse bug porque estava habilitando strictNullChecks no código existente e já tinha uma comparação, então recebi o erro. Se eu estivesse escrevendo um código totalmente novo, provavelmente não teria percebido o problema aqui (o sistema de tipos estava me dizendo que eu sempre recebo um valor) e acabaria com uma falha de tempo de execução. Contar com os desenvolvedores de TS para lembrar (ou pior, até saber) que eles deveriam declarar todos os seus mapas com | undefined parece que o TypeScript está falhando em fazer o que as pessoas realmente desejam.

Todos 242 comentários

Com exceção de strictNullChecks, não temos sinalizadores que alteram o comportamento do sistema de tipo. sinalizadores geralmente habilitam / desabilitam o relatório de erros.
você sempre pode ter uma versão personalizada da biblioteca que define todos os indexadores com | undefined . deve funcionar como esperado.

@mhegazy Essa é uma ideia interessante. Alguma orientação sobre como substituir as assinaturas de tipo para array / objeto?

interface Array<T> em lib.d.ts . Eu pesquisei pela regexp \[\w+: (string|number)\] para encontrar outras assinaturas de indexação também.

Interessante, então tentei isto:

{
    // https://github.com/Microsoft/TypeScript/blob/1f92bacdc81e7ae6706ad8776121e1db986a8b27/lib/lib.d.ts#L1300
    declare global {
        interface Array<T> {
            [n: number]: T | undefined;
        }
    }

    const xs = [1,2,3]
    const x = xs[100]
    x // still number :-(
}

Alguma ideia?

copie lib.d.ts localmente, digamos lib.strict.d.ts , mude a assinatura do índice para [n: number]: T | undefined; , inclua o arquivo em sua compilação. você deve ver o efeito pretendido.

Legal, obrigado por isso.

O problema com a correção sugerida aqui é que requer bifurcação e manutenção de um arquivo lib separado.

Eu me pergunto se esse recurso é exigido o suficiente para garantir algum tipo de opção fora da caixa.

Em uma nota lateral, é interessante que a assinatura de tipo para o método get em coleções ES6 ( Map / Set ) retorna T | undefined quando Array / Object assinaturas de índice não.

esta é uma decisão consciente. seria muito chato se este código fosse um erro:

var a = [];
for (var i =0; i< a.length; i++) {
    a[i]+=1; // a[i] is possibly undefined
}

e não seria razoável pedir a todos os usuários que usassem ! . ou para escrever

var a = [];
for (var i =0; i< a.length; i++) {
    if (a[i]) {
        a[i]+=1; // a[i] is possibly undefined
    }
}

Para mapas, esse não é o caso geral.

Da mesma forma para seus tipos, você pode especificar | undefined em todas as suas assinaturas de índice e obterá o comportamento esperado. mas para Array não é razoável. você está convidado a bifurcar a biblioteca e fazer as alterações necessárias, mas não temos planos de alterar a declaração na biblioteca padrão neste momento.

Não creio que adicionar um sinalizador para alterar a forma de uma declaração seja algo que faríamos.

@mhegazy, mas para matrizes com buracos a[i] é possivelmente indefinido:

let a: number[] = []
a[0] = 0
a[5] =0
for (let i = 0; i < a.length; i++) {
  console.log(a[i])
}

O resultado é:

undefined
undefined
undefined
undefined
0

Continuamos bastante céticos de que alguém obteria algum benefício com esta bandeira na prática. Mapas e coisas do tipo maplike já podem optar por | undefined em seus sites de definição, e impor um comportamento semelhante ao EULA no acesso ao array não parece uma vitória. Provavelmente precisaríamos melhorar substancialmente o CFA e os protetores de tipo para tornar isso palatável.

Se alguém quiser modificar seu lib.d.ts e corrigir todas as quebras de downstream em seu próprio código e mostrar como é a diferença geral para mostrar que isso tem alguma proposição de valor, estamos abertos a esses dados. Alternativamente, se muitas pessoas estão realmente animadas para usar o postfix ! mais, mas ainda não têm amplas oportunidades para fazer isso, este sinalizador seria uma opção.

Continuamos bastante céticos de que alguém obteria algum benefício com esta bandeira na prática. Mapas e coisas semelhantes a mapas já podem ativar | undefined em seus sites de definição

Um dos objetivos do TypeScript não é permitir que erros sejam detectados no momento da "compilação", em vez de confiar que o usuário se lembre / saiba fazer algo específico? Isso parece ir contra esse objetivo; exigir que o usuário faça algo para evitar travamentos. O mesmo pode ser dito para muitos outros recursos; eles não são necessários se o desenvolvedor sempre fizer x. O objetivo do TypeScript é (presumivelmente) tornar o trabalho mais fácil e eliminar essas coisas.

Encontrei esse bug porque estava habilitando strictNullChecks no código existente e já tinha uma comparação, então recebi o erro. Se eu estivesse escrevendo um código totalmente novo, provavelmente não teria percebido o problema aqui (o sistema de tipos estava me dizendo que eu sempre recebo um valor) e acabaria com uma falha de tempo de execução. Contar com os desenvolvedores de TS para lembrar (ou pior, até saber) que eles deveriam declarar todos os seus mapas com | undefined parece que o TypeScript está falhando em fazer o que as pessoas realmente desejam.

Continuamos bastante céticos de que alguém obteria algum benefício com esta bandeira na prática. Mapas e coisas semelhantes a mapas já podem optar por | indefinido em seus locais de definição
Um dos objetivos do TypeScript não é permitir que erros sejam detectados no momento da "compilação", em vez de confiar que o usuário se lembre / saiba fazer algo específico?

Na verdade, o objetivo é:

1) Identifique estaticamente construções que provavelmente são erros.

O que está sendo discutido aqui é a probabilidade de um erro (baixo na opinião da equipe do TypeScript) e a usabilidade produtiva comum da linguagem. Algumas das primeiras mudanças no CFA foram para ser menos alarmista ou melhorar a análise do CFA para determinar essas coisas de forma mais inteligente.

Acho que a questão da equipe do TypeScript é que, em vez de argumentar sobre a exatidão disso, fornecer exemplos de onde esse tipo de rigidez, em uso comum, na verdade identificaria um erro contra o qual deve ser evitado.

Eu entrei no raciocínio um pouco mais neste comentário https://github.com/Microsoft/TypeScript/issues/11238#issuecomment -250562397

Pense nos dois tipos de chaves no mundo: aquelas que você sabe que tem uma propriedade correspondente em algum objeto (seguro), aquelas que você não conhece para ter uma propriedade correspondente em algum objeto (perigoso).

Você obtém o primeiro tipo de chave, uma chave "segura", escrevendo o código correto como

for (let i = 0; i < arr.length; i++) {
  // arr[i] is T, not T | undefined

ou

for (const k of Object.keys(obj)) {
  // obj[k] is T, not T | undefined

Você obtém o segundo tipo de chave, o tipo "perigoso", de coisas como entradas do usuário ou arquivos JSON aleatórios do disco ou alguma lista de chaves que podem estar presentes, mas podem não estar.

Portanto, se você tiver uma chave do tipo perigoso e indexada por ela, seria bom ter | undefined aqui. Mas a proposta não é "Trate as chaves todas as chaves, mesmo as seguras, como perigosas". E quando você começa a tratar as chaves seguras como perigosas, a vida é uma merda. Você escreve código como

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i].name);

e o TypeScript está reclamando com você que arr[i] pode ser undefined , embora olhe, eu apenas @ #% # ing testado para isso . Agora você adquire o hábito de escrever código como este, e parece estúpido:

for (let i = 0; i < arr.length; i++) {
  // TypeScript makes me use ! with my arrays, sad.
  console.log(arr[i]!.name);

Ou talvez você escreva um código como este:

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k].name);
  }
}

e o TypeScript diz "Ei, essa expressão de índice pode ser | undefined , então você deve corrigi-la porque já viu este erro 800 vezes:

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k]!.name); // Shut up TypeScript I know what I'm doing
  }
}

Mas você não corrigiu o bug . Você pretendia escrever Object.keys(yourObj) , ou talvez myObj[k] . Esse é o pior tipo de erro do compilador, porque não está realmente ajudando em nenhum cenário - está apenas aplicando o mesmo ritual a todo tipo de expressão, independentemente de ser ou não realmente mais perigoso do que qualquer outra expressão da mesma forma.

Eu penso no antigo "Tem certeza de que deseja excluir este arquivo?" diálogo. Se essa caixa de diálogo aparecesse toda vez que você tentasse excluir um arquivo, você aprenderia muito rapidamente a pressionar del y quando costumava pressionar del , e suas chances de não excluir algo importante redefinir para o pré -dialog baseline. Se, em vez disso, a caixa de diálogo só apareceu quando você estava excluindo arquivos quando eles não estavam indo para a lixeira, agora você tem segurança significativa. Mas não temos ideia (nem poderíamos ) se as chaves do seu objeto são seguras ou não, portanto, mostrar a mensagem "Tem certeza de que deseja indexar esse objeto?" diálogo toda vez que você fizer isso, provavelmente não encontrará erros em uma taxa melhor do que não mostrar tudo.

Identifique estaticamente construções que provavelmente são erros.

Talvez isso precise ser corrigido para dizer "Identifique estaticamente construções que são mais prováveis ​​do que outras de serem erros." : wink : "Usei * quando pretendia usar / , você pode fazer * um aviso?"

Eu entendo, mas o índice fora do intervalo é um problema real e comum; forçar as pessoas a enumerar matrizes de uma maneira que elas não possam fazer isso não seria uma coisa ruim.

A correção com ! Eu também não gosto - e se alguém aparecer e fizer uma alteração de forma que a suposição agora seja inválida? Você está de volta à estaca zero (uma falha potencial de tempo de execução para algo que o compilador deve detectar). Deve haver maneiras seguras de enumerar matrizes que não dependem de mentir sobre os tipos ou usar ! (por exemplo, você não pode fazer algo como array.forEach(i => console.log(i.name) ?).

Você já restringe os tipos com base no código, portanto, em teoria, não poderia identificar padrões que são seguros restringir o tipo para remover | undefined nesses casos, oferecendo o melhor dos dois mundos? Eu diria que, se você não puder transmitir facilmente ao compilador que não está acessando um elemento válido, talvez sua garantia seja inválida ou possa ser facilmente quebrada acidentalmente no futuro.

Dito isso, eu só uso o TS em um projeto e ele será migrado para o Dart, então é improvável que faça alguma diferença real para mim. Só estou triste porque a qualidade geral do software é ruim e há uma oportunidade de ajudar a eliminar erros aqui que parecem estar sendo ignorados por uma questão de conveniência. Tenho certeza de que o sistema de tipos pode se tornar sólido e os aborrecimentos comuns tratados de uma forma que não introduza esses furos.

De qualquer forma, são apenas meus 2 centavos .. Não quero arrastar isso - tenho certeza que você entende de onde estamos vindo e você está muito melhor posicionado para tomar decisões sobre isso do que eu :-)

Acho que há algumas coisas a serem consideradas. Existem muitos padrões de iteração em arrays de uso comum que levam em consideração o número de elementos. Embora seja um padrão possível acessar índices apenas aleatoriamente em matrizes, na natureza esse é um padrão muito incomum e provavelmente não será um erro estático. Embora existam maneiras modernas de iterar, a mais comum seria algo como:

for (let i = 0; i < a.length; i++) {
  const value = a[i];
}

Se você presumir que os arrays sobressalentes são incomuns (eles são), é de pouca ajuda que value seja | undefined . Se houver um padrão comum, em estado selvagem, onde isso é arriscado (e provavelmente um erro), então acho que o TypeScript ouviria para considerar isso, mas os padrões que estão em uso geral, precisam novamente contra todos os valores de um o acesso ao índice ser indefinido é claramente algo que afeta a produtividade e, conforme apontado, pode ser optado se você estiver em uma situação em que seja potencialmente útil.

Acho que já houve uma conversa antes sobre como melhorar o CFA para que houvesse uma maneira de expressar a co-dependência de valores (por exemplo, Array.prototype.length está relacionado ao valor do índice) para que coisas como índice fora dos limites pudessem ser analisadas estaticamente. Obviamente, esse é um trabalho significativo, trabalhado com todos os tipos de casos extremos e considerações que eu não gostaria de compreender (embora seja provável que Anders acorde suando frio por causa de coisas como essa).

Portanto, torna-se uma troca ... Sem melhorias CFA, complique 90% do código com pistas falsas para capturar potencialmente 10% de código ruim . Caso contrário, está investindo em melhorias importantes no CFA, que podem ser forjadas com suas próprias consequências de estabilidade e problemas contra novamente, encontrando o que seria um código inseguro.

Há tanta coisa que o TypeScript pode fazer para nos salvar de nós mesmos.

Todo esse foco está em matrizes e eu concordo que é menos provável que seja um problema lá, mas a maioria das questões originais levantadas (como a minha) eram sobre mapas onde eu não acho que o caso comum são chaves sempre existentes.

Todo esse foco está em matrizes e eu concordo que é menos provável que seja um problema lá, mas a maioria das questões originais levantadas (como a minha) eram sobre mapas onde eu não acho que o caso comum são chaves sempre existentes.

Se este for o seu tipo, adicione | undefined à assinatura do índice. Já é um erro indexar em um tipo sem assinatura de índice em --noImplicitAny .
ES6 Map já está definido com get como get(key: K): V | undefined; .

Reescrevi todas as definições de Arrays e Maps para fazer assinaturas de índice retornando | undefined , nunca me arrependi desde então, encontrei alguns bugs, não causa nenhum desconforto porque trabalho com arrays indiretamente por meio de uma biblioteca feita à mão que mantém verificações para indefinido ou ! dentro dele

seria ótimo se o TypeScript pudesse controlar o fluxo das verificações como o C # faz (para eliminar as verificações de intervalo de índice e economizar algum tempo do processador), por exemplo:

declare var values: number[];
for (let index = 0, length = values.length; index< length; index ++) {
   const value = value[index]; // always defined, because index is within array range and only controlled by it
}

(para aqueles que usam matrizes esparsas - mate-se com fogo ardente)

quanto a Object.keys , é necessário um tipo especial, digamos allkeysof T para permitir que a análise de fluxo de controle faça estreitamentos seguros

Acho que seria uma boa opção, porque agora estamos essencialmente mentindo sobre o tipo de operação de indexação, e pode ser fácil esquecer de adicionar | undefined aos meus tipos de objeto. Acho que adicionar ! nos casos em que sabemos que queremos ignorar undefined s seria uma boa maneira de lidar com operações de indexação quando esta opção está habilitada.

Existem (pelo menos) dois outros problemas em colocar |undefined em suas definições de tipo de objeto:

  • isso significa que você pode atribuir undefined a esses objetos, o que definitivamente não é pretendido
  • outras operações, como Object.values (ou _.values ) exigirão que você trate undefined nos resultados

tslint relata um aviso de falso positivo de condição constante, porque o typescript retorna informações de tipo incorretas (= faltando | undefined ).
https://github.com/palantir/tslint/issues/2944

Um dos erros regularmente ignorados com a ausência de | undefined na indexação da matriz é este padrão quando usado no lugar de find :

const array = [ 1, 2, 3 ];
const firstFour = array.filter((x) => (x === 4))[0];
// if there is no `4` in the `array`,
// `firstFour` will be `undefined`, but TypeScript thinks `number` because of the indexer signature.
const array = [ 1, 2, 3 ];
const firstFour = array.find((x) => (x === 4));
// `firstFour` will be correctly typed as `number | undefined` because of the `find` signature.

Eu definitivamente usaria esta bandeira. Sim, os loops for antigos serão irritantes de se trabalhar, mas temos o operador ! para informar ao compilador quando sabemos que está definido:

for (let i = 0; i < arr.length; i++) {
  foo(arr[i]!)
}

Além disso, este problema não é um problema com os loops for of mais novos e muito melhores e há até mesmo uma regra TSLint prefer-for-of que diz para você não usar os loops for antigos mais.

Atualmente eu sinto que o sistema de tipos é inconsistente para o desenvolvedor. array.pop() requer um cheque if ou uma asserção ! , mas o acesso via [array.length - 1] não. ES6 map.get() requer um cheque if ou uma asserção ! , mas um hash de objeto não. O exemplo de @sompylasar também é bom.

Outro exemplo é a desestruturação:

const specifier = 'Microsoft/TypeScript'
const [repo, revision] = specifier.split('@') // types of repo and revision are string
console.log('Repo: ' + repo)
console.log('Short rev: ' + revision.slice(0, 7)) // Error: Cannot call function 'slice' on undefined

Eu teria preferido que o compilador me obrigasse a fazer isso:

const specifier = 'Microsoft/TypeScript'
const [repo, revision] = specifier.split('@') // types of repo and revision are string | undefined
console.log('Repo: ', repo || 'no repo')
console.log('Short rev:', revision ? revision.slice(0, 7) : 'no revision')

Esses são bugs reais que eu vi que poderiam ter sido evitados pelo compilador.

Imo, isso não deve pertencer aos arquivos de digitação, mas sim ser um mecanismo de sistema de tipo - ao acessar qualquer coisa com uma assinatura de índice, pode ser undefined . Se sua lógica garantiu que não, apenas use ! . Caso contrário, adicione um if e está tudo bem.

Acho que muitas pessoas prefeririam que o compilador fosse rígido com algumas afirmações necessárias do que solto com bugs não detectados.

Eu realmente gostaria de ver esta bandeira adicionada. Na base de código da minha empresa, o acesso aleatório ao array é a exceção rara e for loops são odores de código que normalmente desejaríamos reescrever com funções de ordem superior.

@pelotom, qual é a sua preocupação então (já que parece que você se

@ aleksey-bykov principalmente assinaturas de índice de objetos, que ocorrem extensivamente em bibliotecas de terceiros. Gostaria de acessar uma propriedade em { [k: string]: A } para me avisar que o resultado possivelmente é indefinido. Eu só mencionei a indexação de array porque ela foi levantada como um caso de por que o sinalizador seria muito chato de se trabalhar.

você sabe que pode reescrevê-los exatamente do jeito que quiser? (dado um pouco de trabalho extra)

Sim, eu poderia reescrever as tipificações de todos para eles ou poderia ativar um sinalizador de compilador 😜

continue jogando capitão O ...: você pode reescrever seu lib.d.ts hoje e ser um feliz proprietário de mais codebase de som ou pode esperar pela bandeira pelos próximos N anos

@aleksey-bykov como isso pode ser feito reescrevendo lib.d.ts ?

declare type Keyed<T> = { [key: string]: T | undefined; }

em seguida, na definição Array em lib.es2015.core.d.ts , substitua

[n: number]: T;

com

[n: number]: T | undefined;

@aleksey-bykov talvez você tenha perdido a parte em que eu disse que não me importo com matrizes. Preocupo-me com o local onde as bibliotecas de terceiros declararam algo como sendo do tipo { [k: string]: T } e quero acessar esse objeto para retornar algo possivelmente indefinido. Não há como fazer isso simplesmente editando lib.d.ts ; requer a mudança das assinaturas da biblioteca em questão.

você tem controle sobre os arquivos de definição de terceiros? se assim for, você pode consertá-los

E agora estamos de volta a

Sim, eu poderia reescrever as tipificações de todos para eles ou poderia ativar um sinalizador de compilador 😜

O tempo é um círculo plano.

não seja bobo, você não usa "a digitação de todo mundo", usa? é literalmente um dia de trabalho máximo para um projeto típico, já fiz isso

Sim, tenho que editar a digitação de outras pessoas o tempo todo e gostaria de fazer menos.

e você vai em N anos, talvez, por agora você pode sofrer ou homem até

Obrigado por sua contribuição incrivelmente construtiva 👍

A entrada construtiva para isso é a seguinte, este problema precisa ser resolvido, porque:
uma. a decisão se [x] pode ser undefined ou não é deixada para os desenvolvedores

  • deixando-os manter tudo em suas cabeças como sempre faziam antes
  • ou alterando lib.d.ts e 3rd-party.d.ts conforme sugerido

b. ou leva sintaxe / tipos / análise de fluxo / N anos especiais para mitigar algo que pode ser feito facilmente em #a

O problema é uma proposta para (b), exceto que nenhuma nova sintaxe está sendo proposta, é apenas um sinalizador do compilador.

O que acontece é que o tipo { [x: string]: {} } é quase sempre uma mentira; barrando o uso de Proxy , não há objeto que possa ter um número infinito de propriedades, muito menos todas as strings possíveis. A proposta é ter um flag do compilador que reconheça isso. Pode ser que seja muito difícil implementar isso para o que é ganho; Vou deixar essa ligação para os implementadores.

o ponto é que nem

  • T | undefined
  • nem T

é certo para o caso geral

a fim de torná-lo correto para o caso geral, você precisa codificar as informações sobre a prerensa de valores nos tipos de seus contêineres, o que exige um sistema de tipo dependente ... o que por si só não é uma coisa ruim de se ter :) mas pode ser tão complexo quanto todo sistema de tipo de texto datilografado atual feito até hoje, para ... salvar algumas edições?

T | undefined está correto para o caso geral, pelos motivos que acabei de apresentar. Vou ignorar suas divagações sem sentido sobre tipos dependentes, tenha um bom dia.

você pode me ignorar o quanto quiser, mas T | undefined é um overshoot para

declare var items: number[];
for (var index = 0; index < items.length; index ++) {
   void items[index];
}

Eu prefiro ter T | undefined lá por padrão e dizer ao compilador que index é um intervalo de índice numérico de items portanto, não sai se limites quando aplicado a items ; nos casos simples, como um conjunto de formas de loop for / while freqüentemente usadas, o compilador poderia inferir isso automaticamente; em casos complexos, desculpe, pode haver undefined s. E sim, tipos baseados em valor seriam um bom ajuste aqui; tipos de string literais são tão úteis, por que não ter tipos booleanos literais e de número e intervalo / conjunto de intervalos? No que diz respeito ao TypeScript, ele tenta cobrir tudo o que pode ser expresso com JavaScript (em contraste com, por exemplo, Elm que limita isso).

é literalmente um dia de trabalho máximo para um projeto típico, já fiz isso

@aleksey-bykov, curioso, como foi sua experiência depois dessa mudança? com que frequência você tem que usar ! ? e com que frequência você encontra o compilador sinalizando bugs reais?

@mhegazy honestamente, não notei muita diferença ao passar de T para T | undefined , nem detectei nenhum bug, acho que meu problema é que trabalho com matrizes por meio de funções de utilitário que mantêm ! neles, então literalmente não houve efeito para o código externo:

https://github.com/aleksey-bykov/basic/blob/master/array.ts

Em qual lib arquivo posso encontrar a definição do tipo de índice para objetos? Eu localizei e atualizei Array de [n: number]: T para [n: number]: T | undefined . Agora eu gostaria de fazer a mesma coisa com os objetos.

não há interface padrão (como Array para matrizes) para objetos com a assinatura de índice, você precisa procurar por definições exatas para cada caso em seu código e corrigi-las

você precisa procurar por definições exatas para cada caso em seu código e corrigi-las

Que tal uma pesquisa de chave direta? Por exemplo

const xs = { foo: 'bar' }
xs['foo']

Existe alguma maneira de impor T | undefined vez de T aqui? Atualmente, eu uso esses auxiliares em minha base de código em todos os lugares, como alternativas seguras de tipo para indexar pesquisas em matrizes e objetos:

// TS doesn't return the correct type for array and object index signatures. It returns `T` instead
// of `T | undefined`. These helpers give us the correct type.
// https://github.com/Microsoft/TypeScript/issues/13778
export const getIndex = function<X> (index: number, xs: X[]): X | undefined {
  return xs[index];
};
export const getKeyInMap = function<X> (key: string, xs: { [key: string]: X }): X | undefined {
  return xs[key];
};

@mhegazy Enquanto escrevo isso, estou corrigindo um bug na produção em https://unsplash.com que pode ter sido detectado com tipos de assinatura de índice mais restritos.

entendo, considere o operador de tipo mapeado:

const xs = { foo: 'bar' };
type EachUndefined<T> = { [P in keyof T]: T[P] | undefined; }
const xu : EachUndefined<typeof xs> = xs;
xu.foo; // <-- string | undefined

Se um sinalizador como --strictArrayIndex não for uma opção, porque os sinalizadores não foram projetados para alterar os arquivos lib.d.ts . Talvez vocês possam lançar versões restritas de lib.d.ts arquivos como " lib": ['strict-es6'] ?

Ele pode conter várias melhorias, não apenas índice de array estrito. Por exemplo, Object.keys :

interface ObjectConstructor {
    // ...
    keys(o: {}): string[];
}

Poderia ser:

interface ObjectConstructor {
    // ...
    keys<T>(o: T): (keyof T)[];
}

Atualização do SBS de hoje: Nós gritamos um com o outro por 30 minutos e nada aconteceu. 🤷‍♂️

@RyanCavanaugh O que é um SBS, por curiosidade?

@radix "Sugestão Backlog Slog"

isso é intrigante, porque a solução é óbvia:
tanto T quanto T | undefined estão errados, a única maneira certa é tornar a variável de índice ciente da capacidade de seu contêiner, seja escolhendo em um conjunto ou encerrando-o em um intervalo numérico conhecido

@RyanCavanaugh, estive pensando sobre isso, parece que o seguinte truque cobriria 87% de todos os casos:

  • values[index]T se o índice declarado em for (HERE; ...)
  • values[somethingElse]T | undefined para todas as variáveis ​​declaradas fora de for

@aleksey-bykov, discutimos algo ainda mais inteligente - que poderia haver um mecanismo de proteção de tipo real para " arr[index] foi testado por index < arr.length . Mas isso não ajudaria no caso de você pop 'd no meio de um loop ou passou seu array para uma função que removeu elementos dele. Realmente não parece que as pessoas estão procurando por um mecanismo que evita erros OOB 82,9% das vezes - afinal , já é o caso que aproximadamente essa fração do código de indexação de matriz está correta de qualquer maneira. Adicionar cerimônia para corrigir o código sem detectar bugs nos casos incorretos é um resultado ruim.

não quer dizer que Aleksey jamais mudaria um array

Transformei um aplicativo do Elm para o Typescript recentemente e as operações de indexação digitadas incorretamente são provavelmente uma das maiores fontes de bugs que encontrei, com todas as configurações mais estritas de TS habilitadas (também coisas como this sendo desvinculadas )

  • você não pode rastrear mutações para o contêiner
  • você não pode rastrear manipulações de índice

com isso dito pouco você pode garantir, se sim, por que você tentaria se um caso de uso típico é uma iteração burra através de todos os elementos dentro de < array.length

como eu costumava dizer

  • se houver um erro, é provável que se origine de algum lugar nas cláusulas da instrução for : inicialização, incremento, condição de parada e, portanto, não é algo que você pode verificar, porque requer a verificação completa do tipo
  • fora das cláusulas for geralmente não há lugar para erros

então, desde que o índice seja declarado de alguma forma (nada que possamos dizer sobre isso com segurança), é razoavelmente justo acreditar que o índice está correto dentro do corpo do loop

Mas isso não ajudaria no caso em que você colocava um loop no meio ou passava seu array para uma função que removia elementos dele

Este parece ser um argumento muito fraco contra isso. Esses casos já foram resolvidos - usá-los como desculpa para não consertar outros casos não faz sentido. Ninguém aqui está pedindo que nenhum desses casos seja tratado corretamente nem que seja 100% correto. Queremos apenas tipos precisos em um caso realmente comum. Existem muitos casos no TypeScript que não são tratados perfeitamente; se você estiver fazendo algo estranho, os tipos podem estar errados. Nesse caso , não estamos fazendo nada estranho e os tipos estão errados.

Adicionando cerimônia ao código correto

Estou curioso para ver um exemplo de código de onde isso é "adicionar cerimônia ao código correto". Pelo que entendi, um loop for básico sobre uma matriz é fácil de detectar / estreitar. Qual é o código do mundo real que não é um simples loop for onde isso se torna inconveniente, o que não é potencialmente um bug? (agora ou pode ser de uma mudança trivial no futuro). Não estou dizendo que não existe; Eu simplesmente não consigo imaginar e não vi nenhum exemplo (eu vi muitos exemplos usando loops for, mas a menos que você esteja dizendo que eles não podem ser reduzidos, eles parecem irrelevantes).

enquanto não pega insetos

Houve exemplos de código de trabalho que falha ao compilar por causa disso e código que lança em tempo de execução porque os tipos enganam o desenvolvedor. Dizer que não há valor em apoiar isso é um absurdo.

Por que não podemos simplesmente mantê-lo simples e não adicionar _qualquer_ comportamento mágico em torno dos loops for antigos? Você sempre pode usar ! para fazer as coisas funcionarem. Se sua base de código estiver cheia de loops for estilo antigo, não use o sinalizador. Todas as bases de código modernas com as quais trabalhei usam forEach ou for of para iterar arrays e essas bases de código se beneficiariam da segurança de tipo adicional.

não estamos fazendo nada estranho e os tipos estão errados.

Este parágrafo, para mim, parece uma boa razão para não ter esse recurso. Acessar um array fora dos limites é uma coisa estranha de se fazer; os tipos só estarão "errados" se você estiver fazendo algo incomum (OOBing). A grande maioria da leitura de código de um array o faz dentro dos limites; seria "errado" incluir undefined nesses casos por este argumento.

... código de trabalho que falha ao compilar

Não estou ciente de nenhum - você pode apontá-los especificamente?

Por que não podemos simplesmente mantê-lo simples e não adicionar nenhum comportamento mágico em torno dos loops for antigos? Você sempre pode usar! para fazer as coisas funcionarem.

Qual é a diferença útil entre isso e uma regra TSLint que diz "Todas as expressões de acesso à matriz devem ter um ! final"?

@RyanCavanaugh minha suposição é que a regra TSLint não seria capaz de restringir tipos ou usar análise de tipo de fluxo de controle (por exemplo, agrupar o acesso em um if , lançar uma exceção, return ing ou continue ing se não estiver definido, etc). Também não se trata apenas de expressões de acesso a array, trata-se também de desestruturação. Como seria a implementação para o exemplo em https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -336265143? Para impor ! , seria essencialmente necessário criar uma tabela para rastrear os tipos dessas variáveis ​​como sendo possivelmente undefined . O que para mim soa como algo que o verificador de tipo deve fazer, não um linter.

@RyanCavanaugh

Este parágrafo, para mim, parece uma boa razão para não ter esse recurso. Acessar um array fora dos limites é uma coisa estranha de se fazer; os tipos só estarão "errados" se você estiver fazendo algo incomum (OOBing).

... código de trabalho que falha ao compilar

Não estou ciente de nenhum - você pode apontá-los especificamente?

No meu caso, não era um array, mas foi fechado como um dupe, então presumo que esse problema também cobre isso. Veja # 11186 para meu problema original. Eu estava analisando um arquivo em um mapa e, em seguida, comparando-os com undefined . IIRC Recebi o erro na comparação de que eles não podem ser indefinidos, embora pudessem (e fossem).

É sempre permitido comparar algo com undefined

o que é uma pena

const canWeDoIt = null === undefined; // yes we can!

É sempre permitido comparar algo com undefined

Já faz tanto tempo que não me lembro exatamente qual foi o erro. Definitivamente, tive um erro de código que estava funcionando bem (sem strictNullChecks ) e isso me levou ao teste que resultou no caso acima.

Se eu tiver algum tempo, verei se consigo descobrir exatamente o que era de novo. Definitivamente estava relacionado a esta digitação.

A maior parte desta discussão foi focada em matrizes, mas, na minha experiência, o acesso a objetos é onde esse recurso seria mais útil. Por exemplo, este exemplo (simplificado) da minha base de código parece razoável e compila bem, mas é definitivamente um bug:

export type Chooser = (context?: Context) => number | string;
export interface Choices {
    [choice: number]: Struct;
    [choice: string]: Struct;
}

export const Branch = (chooser: Chooser, choices: Choices, context?: Context): Struct => {
    return choices[chooser(context)];  // Could be undefined
}

Com relação aos objetos e simplesmente alterar a assinatura para incluir | undefined , @types/node faz isso para process.env :

    export interface ProcessEnv {
        [key: string]: string | undefined;
    }

mas não permite restringir o tipo:

process.env.SOME_CONFIG && JSON.parse(process.env.SOME_CONFIG)

[ts]
Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.

@felixfbecker

Acredito que esteja relacionado a este bug: https://github.com/Microsoft/TypeScript/issues/17960

Você pode contornar isso atribuindo o env var a uma variável e protegendo:

const foo = process.env.SOME_CONFIG
foo && JSON.parse(foo);

A adição de | undefined em algumas tipificações de nós foi feita para permitir o aumento dessa interface por conveniência com propriedades usadas com frequência para obter o preenchimento de código. ea

interface ProcessEnv {
  foo?: string;
  bar?: string;
}

Sem o | undefined na assinatura do índice, o TSC reclama com Property 'foo' of type 'string | undefined' is not assignable to string index type 'string'. .

Eu sei que isso tem um custo se você tiver que trabalhar com tipos que têm | undefined extras e aqueles que não os têm.

Seria muito bom se pudéssemos nos livrar disso.

Estou tentando organizar minhas idéias sobre este assunto.

Os objetos são um saco misturado em JavaScript. Eles podem ser usados ​​para duas finalidades :

  • dicionários também conhecidos como mapas, onde as chaves são desconhecidas
  • registros, onde as chaves são conhecidas

Para objetos usados ​​como registros, as assinaturas de índice não precisam ser usadas. Em vez disso, as chaves conhecidas são definidas no tipo de interface. As pesquisas de chaves válidas retornam o tipo correspondente, as pesquisas de chaves inválidas retornam para a assinatura do índice. Se não houver assinatura de índice definida (o que não deveria haver para objetos usados ​​como registros) e noImplicitAny estiver habilitado, ocorrerá o erro conforme desejado.

Para objetos usados ​​como dicionários (também conhecidos como mapas) e matrizes, assinaturas de índice são usadas e podemos escolher incluir | undefined no tipo de valor. Por exemplo, { [key: index]: string | undefined } . Todas as pesquisas de chaves são válidas (porque as chaves não são conhecidas no momento da compilação) e todas as chaves retornam o mesmo tipo (neste exemplo, T | undefined ).

Visto que as assinaturas de índice devem ser usadas apenas para o padrão de objetos de dicionário e matrizes, é desejável que o TypeScript imponha | undefined no tipo de valor de assinatura de índice: se as chaves não forem conhecidas, e as pesquisas de chave possivelmente retornarão undefined .

Existem bons exemplos de bugs que podem parecer invisíveis sem isso, como Array.prototype.find retornando undefined , ou pesquisas de chave como array[0] retornando undefined . (A desestruturação é apenas uma sintaxe de açúcar para pesquisas de chave.) É possível escrever funções como getKey para corrigir o tipo de retorno, mas temos que confiar na disciplina para forçar o uso dessas funções em uma base de código.

Se bem entendi, o problema passa a ser sobre a reflexão sobre objetos e matrizes de dicionário, de modo que, ao mapear as chaves de um objeto de dicionário ou matriz, as pesquisas de chave são conhecidas como válidas, ou seja, não retornarão undefined . Nesse caso, seria indesejável que os tipos de valor incluíssem undefined . Pode ser possível usar a análise de fluxo de controle para corrigir isso.

Este é o problema pendente ou eu não entendi?

Quais casos de uso e problemas eu não mencionei?

Existem (pelo menos) dois outros problemas em colocar | undefined em suas definições de tipo de objeto:

  • isso significa que você pode atribuir undefined a esses objetos, o que definitivamente não é pretendido
  • outras operações, como Object.values ​​(ou _.values) exigirão que você trate o indefinido nos resultados

Acho que esse é um ponto muito importante.

No momento, estou testando a seguinte abordagem:

Defina const safelyAccessProperty = <T, K extends keyof T>(object: T, key: K): T[K] | undefined => object[key];

Em seguida, acesse propriedades como safelyAccessProperty(myObject, myKey) , em vez de myObject[myKey] .

@plul Good catch. A discussão está atualmente focada nas operações de leitura, mas a definição do tipo de indexador é, na verdade, dupla, e adicionar | undefined permitiria escrever undefined valores.

A função safelyAccessProperty você está experimentando ( mencionada acima como getKey por @OliverJAsh) requer disciplina e / ou uma regra de linter para proibir operações de indexação em todos os arrays e objetos.

Isso pode ser escalonável se a função for fornecida em todas as instâncias de array e objeto (cada tipo que fornece operações de indexador), como em C ++ std::vector tem .at() que lança uma exceção no tempo de execução para acesso OOB , e um operador [] desmarcado que na melhor das hipóteses trava com SEGFAULT no acesso OOB, na pior das hipóteses corrompe a memória.

Acho que o problema de acesso OOB não pode ser resolvido em TypeScript / JavaScript apenas no nível de definição de tipo e requer suporte de linguagem para restringir operações de indexador potencialmente perigosas se esse recurso de rigidez estiver habilitado.

A natureza dupla do indexador poderia ser modelada como uma propriedade com operações get e set como funções, mas isso seria uma alteração significativa para todas as definições de tipo de indexador existentes.

Uma ideia que parece ser promissora: e se você pudesse usar ?: ao definir uma assinatura de índice para indicar que você espera que os valores às vezes faltem sob o uso normal? Ele agiria como | undefined mencionado acima, mas sem as desvantagens estranhas. Seria necessário proibir os valores undefined explícitos, o que eu acho que é uma diferença dos ?: usuais.

Ficaria assim:

type NewWay = {[key: string]?: string};
const n: NewWay = {};

// Has type string | undefined
n['foo']

// Has type Array<string>
Object.values(n)

// Doesn't work
n['foo'] = undefined;

// Works
delete n['foo'];

Em comparação com a abordagem anterior de | undefined :

type OldWay = {[key: string]: string | undefined};
const o: OldWay = {};

// Has type string | undefined
o['foo']

// Has type Array<string | undefined>
Object.values(o)

// Works
o['foo'] = undefined;

// Works
delete o['foo'];

Eu vim aqui por rejeitar a adição de | undefined no DT PR acima, já que quebraria todos os usuários existentes dessa API - isso poderia ser melhor visto como permitindo ao usuário escolher o quão exigente deseja ser, em vez do que a biblioteca?


Vou observar que as propriedades opcionais adicionam | undefined também, e isso me incomodou algumas vezes - essencialmente, o TS não distingue entre uma propriedade ausente e uma propriedade definida como indefinida. Eu gostaria apenas que { foo?: T, bar?: T } fosse tratado da mesma forma que { [name: 'foo' | 'bar']: T } , qualquer que seja o caminho (veja também os process.env comentários acima)


O TS é contra quebrar a simetria aqui nos indexadores de número e string?

foo[bar] && foo[bar].baz() é um padrão JS muito comum, parece desajeitado quando não é suportado por TS (no sentido de lembrá-lo de que você precisa se você não adicionar | undefined , e avisar quando for obviamente não é necessário se você fizer isso).


Em relação a matrizes mutantes durante a iteração, quebrando a garantia de expressão de guarda, isso também é possível com os outros guardas:

class Foo {
    foo: string | number = 123

    bar() {
        this.foo = 'bar'
    }

    broken() {
        if (typeof this.foo === 'number') {
            this.bar();
            this.foo.toLowerCase(); // right, but type error
            this.foo.toExponential(); // wrong, but typechecks
        }
    }
}

mas acho que é muito menos provável no código real que os loops antigos alterando o iteratee.

É claro que existe uma demanda por esse recurso. Eu realmente espero que a equipe de TS encontre alguma solução. Não apenas adicionando | undefined ao indexador porque ele tem seus próprios problemas (já mencionado), mas de uma maneira mais "inteligente" (a leitura retorna T|undefined , a escrita requer T , bom compilador verificar o loop de for , etc. boa proposição também já foi mencionada.)

Estamos bem com o erro de tempo de execução quando mudamos e trabalhamos com arrays de uma forma não trivial, difícil de verificar pelo compilador. Queremos apenas a verificação de erros para a maioria dos casos e podemos usar ! às vezes.

Dito isso, se esse tratamento mais rígido de arrays fosse implementado, agora com o # 24897 seria possível implementar um bom estreitamento de tipo ao verificar o comprimento do array com constante. Poderíamos apenas estreitar o array para a tupla com o elemento rest.

let arr!: string[];
if (arr.length == 3) {
  //arr is of type [string, string, string]
}

if (arr.length > 3) {
  //arr is of type [string, string, string, string, ...string[]]
}

if (arr.length) {
  //arr is of type [string, ...string[]]
}

if (arr.length < 3) {
  //arr is of type [string?, string?, string?]
  if (arr.length > 0) {
    //arr is of type [string, string?, string?]
  }
}

Seria útil quando você indexa por constante ou destrói um array.

let someNumber = 55;
if (arr.length) {
  let el1 = arr[0]; //string
  let el2 = arr[1]; //string | undefined
  let el3 = arr[someNumber]; //string | undefined
}

if(arr.length >= 3){
    let [el1, el2, el3, el4] = arr;
    //el1, el2, el3 are string
    // el4 is string | undefined    
}

if (arr.length == 2){
    let [el1, el2, el3] = arr; //compiler error: "Tuple type '[string, string]' with length '2' cannot be assigned to tuple with length '3'.",
}

Outra questão é o que faríamos com grandes números, como:

if(arr.length >= 99999){
    // arr is [string, string, ... , string, ...string[]]
}

Não podemos mostrar o tipo dessa enorme tupla no IDE ou nas mensagens do compilador.

Acho que poderíamos ter alguma sintaxe para representar "tupla de determinado comprimento com o mesmo tipo para todos os itens". Então, por exemplo, a tupla de 1000 strings é string[10000] e o tipo de array estreito do exemplo acima pode ser [...string[99999], ...string[]] .

A outra preocupação é se a infraestrutura do compilador pode suportar tuplas tão grandes agora e, se não, quão difícil seria fazê-lo.

Objetos

Sempre quero um tipo de índice [key: string (or number, symbol)]: V | undefined , mas às vezes esqueço o caso undefined . Sempre que um desenvolvedor tem que dizer explicitamente ao compilador "confie em mim, este é o tipo de coisa real", você sabe que não é seguro.
Não faz muito sentido digitar Map.get corretamente (estritamente), mas de alguma forma os objetos simples obtêm um passe livre.
Ainda assim, isso pode ser facilmente corrigido no terreno do usuário, então não é tão ruim. Não tenho solução, de qualquer forma.

Matrizes

Talvez eu esteja faltando alguma coisa, mas parece que o argumento de que "você quase nunca acessa um Array de maneira insegura" pode ir para os dois lados, especialmente com um novo sinalizador de compilador.

Eu tendo a pensar que mais e mais pessoas seguem estas duas práticas recomendadas:

  • Use métodos nativos funcionais ou bibliotecas para iterar ou transformar Arrays. Sem acesso de suporte aqui.
  • Não modifique os Arrays no lugar

Com isso em mente, os únicos casos raros e restantes em que você precisa da lógica de acesso do colchete de baixo nível realmente se beneficiariam com a segurança de tipo.

Este parece ser um acéfalo e não acho que copiar e colar todo o lib.d.ts localmente seja uma solução alternativa aceitável.

Quando indexamos explicitamente em um array / objeto, por exemplo, para obter o primeiro item de um array ( array[0] ), queremos que o resultado inclua undefined .

Isso é possível adicionando undefined à assinatura do índice.

No entanto, se bem entendi, o problema de incluir undefined na assinatura do índice é que, ao mapear a matriz ou objeto, os valores incluirão undefined , embora não o façam ser undefined (caso contrário, não estaríamos mapeando sobre eles).

Tipos / assinaturas de índice são usados ​​para pesquisas de índice (por exemplo, array[0] ) e mapeamento (por exemplo, for loops e Array.prototype.map ), mas exigimos tipos diferentes para cada um desses casos.

Este não é um problema com Map porque Map.get é uma função e, portanto, seu tipo de retorno pode ser separado do tipo de valor interno, ao contrário da indexação em uma matriz / objeto que não é uma função, e, portanto, usa a assinatura do índice diretamente.

Portanto, a questão é: como podemos satisfazer os dois casos?

// Manually adding `undefined` to the index signature
declare const array: (number | undefined)[];

const first = array[0]; // number | undefined, as desired :-)
type IndexValue = typeof array[0]; // number | undefined, as desired! :-)

array.map(x => {
  x // number | undefined, not desired! :-(
})

Proposta

Uma opção de compilador que trata pesquisas de índice (por exemplo, array[0] ) de forma semelhante a como Set.get e Map.get são digitados, incluindo undefined no tipo de valor de índice (não a própria assinatura do índice). A própria assinatura do índice real não incluiria undefined , de modo que as funções de mapeamento não são afetadas.

Exemplo:

declare const array: number[];

// The compiler option would include `undefined` in the index value type
const first = array[0]; // number | undefined, as desired :-)
type IndexValue = typeof array[0]; // number | undefined, as desired :-)

array.map(x => {
  x // number, as desired :-)
})

Isso, entretanto, não resolverá o caso de loop em array / objetos usando um loop for , já que essa técnica usa pesquisas de índice.

for (let i = 0; i < array.length; i++) {
  const x = array[i];
  x; // number | undefined, not desired! :-(
}

Para mim e suspeito de muitos outros, isso é aceitável porque for loops não são usados, ao invés disso, prefiro usar um estilo funcional, por exemplo, Array.prototype.map . Se tivéssemos que usá-los, ficaríamos felizes em usar a opção do compilador sugerida aqui junto com os operadores de asserção não nulos.

for (let i = 0; i < array.length; i++) {
  const x = array[i]!;
  x; // number, as desired :-)
}

Também poderíamos fornecer uma maneira de aceitar ou cancelar, por exemplo, com alguma sintaxe para decorar a assinatura do índice (perdoe a sintaxe ambígua que criei para o exemplo). Essa sintaxe seria apenas uma forma de sinalizar qual comportamento queremos para pesquisas de índice.

Desativação (opção do compilador ativa por padrão, desativação quando necessário):

declare const array: { [index: number]!!: string };

declare const dictionary: { [index: string]!!: string }

Aceite (sem opção de compilador, apenas aceite quando necessário):

declare const array: { [index: string]!!: string };

declare const dictionary: { [index: string]??: string }

Eu não li sobre este problema ou os prós e contras, várias propostas, etc. (apenas encontrei no Google depois de ser repetidamente surpreso que o acesso de array / objeto não é tratado de forma consistente com verificações nulas estritas), mas eu tenho um sugestão relacionada: uma opção para tornar a inferência de tipo de array o mais estrita possível, a menos que seja especificamente substituída.

Por exemplo:

const balls = [1, 2 ,3];

Por padrão, balls seria tratado como [number, number, number] . Isso pode ser substituído escrevendo:

const balls: number[] = [1, 2 ,3];

Além disso, o acesso ao elemento de tupla seria tratado de forma consistente com verificações de nulos estritas. É surpreendente para mim que no exemplo a seguir n seja atualmente inferido como number mesmo com verificações de nulos estritas habilitadas.

const balls: [number, number, number] = [1, 2 ,3];
const n = balls[100];

Eu também esperaria que métodos de mutação de array, como .push não existissem na definição de tipo de tupla, uma vez que tais métodos mudam o tipo de tempo de execução para ser inconsistente com o tipo de tempo de compilação.

Agradável! Bem, esse é um momento interessante. Eu estava com o anúncio de lançamento aberto, mas ainda não tinha lido; só cheguei aqui depois de me deparar com uma situação em que precisei fazer um casting estranho ( (<(T|undefined)[]> arr).slice(-1)[0] ) para fazer o TypeScript (2.9) fazer o que eu queria.

Só queria trazer de volta esta sugestão: https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -383072468

Isso resolveria os problemas com tipos indexados que experimentei. Seria ótimo se fosse o padrão, mas entendo que isso quebraria muitas coisas no mundo real.

@mhegazy @RyanCavanaugh Alguma opinião sobre a minha proposta? https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -406316164

Para mim, existe uma solução simples. Habilite um sinalizador que verifica isso. Então, em vez de:

const array = [1, 2, 3];
for (var i =0; i< array.length; i++) {
    array[i]+=1; // array[i] is possibly undefined
}

Você faz:

const array = [1, 2, 3];
array.forEach((value, i) => array[i] = value + 1);

Então, ao fazer acessos de índice aleatórios, você deve verificar se o resultado é indefinido, mas não durante a iteração de uma coleção enumerada.

Ainda acho que isso justifica ter um problema em aberto.

Como um programador que é novo no TypeScript, descobri que a situação em torno da indexação de objetos no modo estrito não era intuitiva. Eu esperava que o resultado de uma pesquisa fosse T | undefined , semelhante a Map.get . Enfim, acabei de encontrar isso recentemente e abri um problema em uma biblioteca:

screepers / typed-screeps # 107

Provavelmente vou fechá-lo agora, porque parece que não há uma boa solução. Acho que vou tentar "aceitar" T | undefined usando uma pequena função de utilidade:

export function lookup<T>(map: {[index: string]: T}, index: string): T|undefined {
  return map[index];
}

Houve algumas sugestões aqui para definir explicitamente T | undefined como o tipo de retorno da operação de índice de objeto, no entanto, isso não parece estar funcionando:

const obj: {[key: string]: number | undefined} = {
  "a": 1,
  "b": 2,
};

const test = obj["c"]; // const test: number

Este é o VSCode versão 1.31.1

@yawaramin Certifique-se de ter strictNullChecks habilitado em seu tsconfig.json (que também está habilitado pela bandeira strict )

Se o seu caso de uso requer indexação arbitrária em matrizes de comprimento desconhecido, acho que vale a pena adicionar o indefinido explicitamente (mesmo que apenas para "documentar" essa insegurança).

const words = ... // some string[] that could be empty
const x = words[0] as string | undefined
console.log(x.length) // TS error

Tuplas funcionam para pequenas matrizes de comprimentos conhecidos. Talvez pudéssemos ter algo como string[5] como uma abreviação para [string, string, string, string, string] ?

Muito a favor desta opção. É uma falha perceptível no sistema de tipos, particularmente quando o sinalizador strictNullChecks está habilitado. Objetos simples são usados ​​como mapas o tempo todo em JS, portanto, o TypeScript deve oferecer suporte a esse caso de uso.

Encontrei isso com a destruição da matriz de um parâmetro de função:

function foo([first]: string[]) { /* ... */ }

Aqui, eu esperaria que a fosse do tipo string | undefined mas é apenas string , a menos que eu o faça

function foo([first]: (string | undefined)[]) { /* ... */ }

Eu não acho que temos um único loop for em nossa base de código, então eu ficaria feliz em adicionar um sinalizador às opções do compilador do meu tsconfig (poderia ser chamado de strictIndexSignatures ) para alternar esse comportamento para o nosso projeto.

É assim que tenho trabalhado em torno desse problema: https://github.com/danielnixon/total-functions/

Boa solução alternativa. Realmente espero que isso seja bloqueado pela equipe do TypeScript.

Quando um programador faz suposições no código escrito e o compilador não pode deduzir que está salvo, isso deve resultar em um erro do compilador, a menos que seja silenciado o IMHO.

Esse comportamento também seria muito útil em combinação com o novo operador de encadeamento opcional.

Tive um problema ao usar | undefined com o mapa hoje ao usar Object.entries() .

Eu tenho um tipo de índice que é descrito muito bem por {[key: string]: string[]} , com a advertência óbvia de que nem todas as chaves de string possíveis são representadas no índice. No entanto, escrevi um bug que o TS não detectou ao tentar consumir um valor pesquisado no Índice; não lidou com o caso indefinido.

Portanto, mudei para {[key: string]: string[] | undefined} conforme recomendado, mas agora isso leva a problemas com o meu uso de Object.entries() . O TypeScript agora assume (razoavelmente, com base na especificação do tipo) que o Index pode ter chaves com um valor de undefined especificado e, portanto, assume que o resultado da chamada de Object.entries() nele pode conter valores indefinidos.

No entanto, sei que isso é impossível; a única vez que devo obter um resultado undefined é procurando uma chave que não existe e que não seria listada ao usar Object.entries() . Portanto, para deixar o TypeScript feliz, tenho que escrever um código que não tem uma razão real de existir ou substituir os avisos, o que prefiro não fazer.

@RyanCavanaugh , presumo que sua resposta original a esta ainda seja a posição atual da equipe de TS? Tanto como um choque para isso, porque ninguém lê o encadeamento e checando se mais alguns anos de experiência com primitivas de coleção JS mais poderosas, e introduzindo várias outras opções para aumentar a rigidez, uma vez que mudaram alguma coisa.

(Os exemplos lá ainda não são convincentes para mim, mas este tópico já apresentou todos os argumentos, outro comentário não vai mudar nada)

Se alguém quiser modificar seu lib.d.ts e corrigir todas as quebras de downstream em seu próprio código e mostrar como é a diferença geral para mostrar que isso tem alguma proposição de valor, estamos abertos a esses dados.

@RyanCavanaugh Aqui estão alguns dos casos da minha base de código em que alguns travamentos de tempo de execução estão adormecidos. Observe que já tive casos em que tive travamentos na produção por causa disso e precisei lançar um hotfix.

src={savedAdsItem.advertImageList.advertImage[0].mainImageUrl || undefined}
return advert.advertImageList.advertImage.length ? advert.advertImageList.advertImage[0].mainImageUrl : ''
birthYear: profileData.birthYear !== null ? profileData.birthYear : allowedYears[0].value,
upsellingsList.upsellingProducts[0].upsellingProducts[0].selected = true
const latitude = parseFloat(coordinates.split(',')[0])
const advert = Object.values(actionToConfirm.selectedItems)[0]
await dispatch(deactivateMyAd(advert))

Neste caso, seria irritante, pois ArticleIDs extends articleNames[] inclui undefined nos valores resultantes, embora deva apenas permitir subconjuntos completamente definidos. Facilmente corrigível usando ReadonlyArray<articleNames> vez de articleNames[] .

export enum articleNames {
    WEB_AGB = 'web_agb',
    TERMS_OF_USE = 'web_terms-of-use',
}
export const getMultipleArticles = async <ArticleIDs extends articleNames[], ArticleMap = { [key in ArticleIDs[number]]: CmsArticle }>(ids: ArticleIDs): Promise<ArticleMap> => {...}

No geral, eu realmente gostaria de ter esse tipo de segurança extra para evitar possíveis travamentos de tempo de execução.

Eu entrei no raciocínio um pouco mais neste comentário # 11238 (comentário)

Pense nos dois tipos de chaves no mundo: aquelas que você conhece _tem_ uma propriedade correspondente em algum objeto (seguro), aquelas que você _não_ sabe que possuem uma propriedade correspondente em algum objeto (perigoso).

Você obtém o primeiro tipo de chave, uma chave "segura", escrevendo o código correto como

for (let i = 0; i < arr.length; i++) {
  // arr[i] is T, not T | undefined

ou

for (const k of Object.keys(obj)) {
  // obj[k] is T, not T | undefined

Você obtém o segundo tipo de chave, o tipo "perigoso", de coisas como entradas do usuário ou arquivos JSON aleatórios do disco ou alguma lista de chaves que podem estar presentes, mas podem não estar.

Portanto, se você tiver uma chave do tipo perigoso e indexada por ela, seria bom ter | undefined aqui. Mas a proposta não é "Tratar as chaves _perigosas_ como perigosas", é "Tratar _todas_ as chaves, mesmo as seguras, como perigosas". E quando você começa a tratar as chaves seguras como perigosas, a vida é uma merda. Você escreve código como

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i].name);

e o TypeScript está reclamando com você que arr[i] pode ser undefined , embora _Eles olhem, eu apenas @ #% # ing testado para isso_. Agora você adquire o hábito de escrever código como este, e parece estúpido:

for (let i = 0; i < arr.length; i++) {
  // TypeScript makes me use ! with my arrays, sad.
  console.log(arr[i]!.name);

Ou talvez você escreva um código como este:

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k].name);
  }
}

e o TypeScript diz "Ei, essa expressão de índice pode ser | undefined , então você deve corrigi-la porque já viu este erro 800 vezes:

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k]!.name); // Shut up TypeScript I know what I'm doing
  }
}

Mas você não corrigiu o bug . Você pretendia escrever Object.keys(yourObj) , ou talvez myObj[k] . Esse é o pior tipo de erro do compilador, porque não está realmente ajudando em nenhum cenário - está apenas aplicando o mesmo ritual a todo tipo de expressão, independentemente de ser ou não realmente mais perigoso do que qualquer outra expressão da mesma forma.

Eu penso no antigo "Tem certeza de que deseja excluir este arquivo?" diálogo. Se essa caixa de diálogo aparecesse toda vez que você tentasse excluir um arquivo, você aprenderia muito rapidamente a pressionar del y quando costumava pressionar del , e suas chances de não excluir algo importante redefinir para o pré -dialog baseline. Se, em vez disso, a caixa de diálogo só apareceu quando você estava excluindo arquivos quando eles não estavam indo para a lixeira, agora você tem segurança significativa. Mas não temos ideia (nem _podemos_) se as chaves do seu objeto são seguras ou não, portanto, mostrar a mensagem "Tem certeza de que deseja indexar esse objeto?" diálogo toda vez que você fizer isso, provavelmente não encontrará erros em uma taxa melhor do que não mostrar tudo.

Eu concordo com a analogia da caixa de diálogo de exclusão de arquivo, no entanto, acho que essa analogia também pode ser estendida para forçar o usuário a verificar algo que é possivelmente indefinido ou nulo, então esta explicação realmente não faz sentido, porque se esta explicação for verdadeira, a opção strictNullChecks vai induzir o mesmo comportamento, por exemplo, obter algum elemento do DOM usando document.getElementById .

Mas esse não é o caso, muitos usuários do TypeScript desejam que o compilador sinalize esse código para que esses casos extremos possam ser tratados apropriadamente em vez de lançar o erro Cannot access property X of undefined que é muito difícil de rastrear.

No final, espero que esse tipo de recurso possa ser implementado como opções extras do compilador TypeScript, porque esse é o motivo pelo qual os usuários desejam usar o TypeScript, eles querem ser avisados ​​sobre códigos perigosos.

Falar sobre acessar array ou objetos erroneamente é improvável de acontecer, você tem algum dado para fazer o backup desta afirmação? Ou é apenas baseado em um pressentimento arbitrário?

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i].name);

A análise de tipo baseada em fluxo de controle do TypeScript pode ser aprimorada para reconhecer esse caso como seguro e não exigir o ! . Se um humano pode deduzir que é seguro, um compilador também pode.
Estou ciente de que isso pode não ser trivial para implementar no compilador.

A análise de tipo baseada em fluxo de controle do TypeScript pode ser aprimorada para reconhecer este caso como seguro

Realmente não pode.

declare function someFunc(arr: number[], i: number): void;
let arr = [1, 2, 3, 4];
for (let i = 0; i < arr.length; i++) {
  someFunc(arr, arr[i]);
}

Esta função passa um undefined para someFunc na segunda passagem pelo loop, ou não passa? Há muitas coisas que eu poderia escrever em someFunc que resultariam em undefined aparecendo mais tarde.

Que tal isso?

declare function someFunc(arr: number[], i: number): void;
let arr = [1, 2, 3, 4];
let alias = arr;
for (let i = 0; i < arr.length; i++) {
  someFunc(alias, arr[i]);
}

@fabb, deixe-me dar outro exemplo:

`` `
$ node

const arr = []
Indefinido
arr [7] = 7
7
arr
[<7 itens vazios>, 7]
para (seja i = 0; i <arr.length; i ++) {
... console.log (arr [i])
...}
Indefinido
Indefinido
Indefinido
Indefinido
Indefinido
Indefinido
Indefinido
7
undefined```

@RyanCavanaugh já que você está aqui, que tal inferir item :: T para arr :: T[] em for (const item of arr) ... e, de outra forma, inferir arr[i] :: T | undefined ao usar algum --strict-index ? E o caso que me interessa, obj[key] :: V | undefined mas Object.values(obj) :: V[] para obj :: { [key: string]: V } ?

@yawaramin Se você estiver usando matrizes esparsas, o Typescript já não faz a coisa certa. Uma bandeira --strict-index consertaria isso. Isso compila:

const arr = []
arr[7] = 7

for (let i = 0; i < arr.length; i++) {
    console.log(Math.sqrt(arr[i]));
}

@RyanCavanaugh Há um exemplo muito comum que posso mostrar onde o usuário está propenso a acessar o array incorretamente.

const getBlock = (unitNumber: string): string => unitNumber.split('-')[0]

O código acima não deve ser aprovado na verificação do compilador normalmente em strictNullChecks , porque alguns usos de getBlock retornarão indefinido, por exemplo getBlock('hello') , em tais casos eu quero seriamente o compilar o sinalizador de aumento para que eu possa lidar com os casos indefinidos normalmente sem explodir meu aplicativo.

E isso também se aplica a muitos idiomas comuns, como acessar o último elemento de uma matriz com arr.slice(-1)[0] , acessar o primeiro elemento arr[0] etc.

Em última análise, quero que o TypeScript me irrite por causa desses erros, em vez de ter que lidar com aplicativos explodidos.

Essa função passa um undefined para someFunc na segunda passagem pelo loop, ou não passa? Há muitas coisas que eu poderia escrever em algumFunc que resultariam em uma exibição indefinida mais tarde.

@RyanCavanaugh Sim, o JavaScript não se presta à imutabilidade. Neste caso, um ! deve ser necessário ou um ReadonlyArray array: someFunc(arr: ReadonlyArray<number>, i: number) .

@yawaramin Para matrizes esparsas, o tipo de elemento provavelmente precisa incluir undefined menos que o TypeScript possa deduzir que é usado como uma tupla. No código ao qual @danielnixon vinculou (https://github.com/microsoft/TypeScript/issues/13778#issuecomment-536248028), as tuplas também são tratadas como especiais e não incluem undefined no tipo de elemento retornado já que o compilador garante que apenas os índices definidos sejam acessados.

esta é uma decisão consciente. seria muito chato se este código fosse um erro:

var a = [];
for (var i =0; i< a.length; i++) {
    a[i]+=1; // a[i] is possibly undefined
}

Ei, eu reconheço essa sintaxe; eu escrevo loops como este talvez uma vez por ano!

Descobri que, na maioria dos casos em que realmente indexo em uma matriz, quero verificar undefined depois.

O medo de tornar este caso específico mais difícil de trabalhar parece exagerado. Se você deseja apenas iterar sobre os elementos, pode usar for .. of . Se você precisar do índice do elemento por algum motivo, use forEach ou itere sobre entries . Em geral, é extremamente raro você realmente precisar de um loop for baseado em índice.

Se houver melhores razões para alguém querer o status quo, eu gostaria de vê-los, mas independentemente: Esta é uma inconsistência no sistema e ter um sinalizador para corrigi-lo seria muito apreciado por muitos, ao que parece.

Olá a todos, sinto que grande parte da discussão aqui foi
teve. Os proprietários do pacote foram bastante claros sobre seu raciocínio e
já consideramos a maioria desses argumentos. Se eles planejam abordar
isso, tenho certeza que eles vão tornar isso conhecido. Fora isso, eu não acho que
thread é realmente produtivo.

Na sexta-feira, 25 de outubro de 2019 às 11h59, brunnerh [email protected] escreveu:

esta é uma decisão consciente. seria muito chato para este código
ser um erro:

var a = []; para (var i = 0; i <a.length; i ++) {
a [i] + = 1; // a [i] é possivelmente indefinido
}

Ei, eu reconheço essa sintaxe; eu escrevo loops como este talvez uma vez por ano!

Descobri que na maioria dos casos em que realmente faço o índice em uma matriz,
na verdade, deseja verificar se há indefinido posteriormente.

O medo de tornar este caso específico mais difícil de trabalhar parece exagerado.
Se você deseja apenas iterar os elementos que pode usar para .. de. Se
você precisa do índice do elemento por algum motivo, use forEach ou itere sobre
entradas. Em geral, é extremamente raro que você realmente precise de um
loop for baseado em índice.

Se houver melhores razões para alguém querer o status quo, eu
gostaria de vê-los, mas independentemente: Esta é uma inconsistência no sistema
e ter uma bandeira para consertar seria muito apreciado por muitos, ao que parece.

-
Você está recebendo isso porque comentou.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/microsoft/TypeScript/issues/13778?email_source=notifications&email_token=ACAJU3DQ7U6Y3MUUM26J4JDQQM62XA5CNFSM4C6KEKAKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOECJIE7Y#issuecomment-546472575 ,
ou cancelar
https://github.com/notifications/unsubscribe-auth/ACAJU3EWVM3CUFG25UF5PGDQQM62XANCNFSM4C6KEKAA
.

Eu sinto que, embora não haja movimento dos proprietários dos pacotes, o feedback contínuo da comunidade ainda é valioso, porque mostra que ainda há uma demanda por ferramentas melhores em torno deste problema.

@brunnerh eu concordo com você, hoje em dia eu nem preciso usar for loops a menos que seja para ajuste de performance, que acontece 0% das vezes no codebase da minha empresa, porque na maioria das vezes mudando de mapa / filtro / reduzir para o loop raramente melhora o desempenho, o verdadeiro culpado é sempre sobre lógica ineficiente, problema de rede e conexões de banco de dados.

Estou surpreso por ninguém ter falado sobre as const ainda.

const test = [1, 2, 3] as const;

(test[100]).toFixed(5);
// Tuple type 'readonly [1, 2, 3]' of length '3' has no element at index '100'.

De modo mais geral, e não exatamente relacionado à mensagem inicial deste problema, tenho usado os seguintes padrões de programação defensiva nos últimos meses e funcionou bem (para mim)

const xs: Array<number | undefined> = [1,2,3];

// for objects but kind of related as well
Record<string, User | undefined>

interface Something {
  [key: string]: User | undefined
}

Mesmo que não haja uma notação curta para ele (como ? ), sinto que está bem. Dizer ao compilador que você pode não ter certeza se o valor está definido é muito bom.

@martpie as const é ótimo se você puder usá-lo.

Existem pelo menos duas razões pelas quais eu prefiro não usar Array<T | undefined> :

  1. É mais uma coisa que você deve se lembrar de fazer sempre que usar um array. Além disso, você não pode simplesmente usar a digitação implícita, o que é bom ter.
  2. Ele muda as assinaturas de forEach , map e filter , que não passam undefined como argumento de elemento (a menos que o índice seja explicitamente definido dessa forma). Dependendo de quanto você usa essas funções que podem ser chatas de lidar.

Também quero alertar que isso causa muitos falsos positivos em eslint / typescript agora:

const a: string[] = [];
const foo = a[1000];
if (foo) { // eslint says this is an unnecessary conditional
  console.log(foo.length);
}

Eslint (corretamente) infere do tipo que esta é uma verificação desnecessária, porque foo nunca pode ser nulo. Portanto, agora não apenas tenho que lembrar conscientemente de fazer uma verificação nula nas coisas que recebo de um array, eu _também_ tenho que adicionar uma linha de desabilitar eslint! E acessar coisas fora de um loop for como este é virtualmente a única maneira de acessarmos arrays, já que (como provavelmente a maioria dos desenvolvedores de TS atualmente), usamos as funções forEach / map / filter / etc ao fazer loop em arrays.

Eu realmente acho que deveríamos ter apenas um novo sinalizador de compilador definido como verdadeiro por padrão se strict for verdadeiro, e se as pessoas não gostarem, podem cancelar. Nós adicionamos novas verificações de compilação estritas quebrando em versões recentes de qualquer maneira.

Esta é a origem dos _somente_ bugs prod em tempo de execução que vi na memória recente, que eram uma falha do sistema de tipo.

Considerando a ergonomia do operador de encadeamento opcional agora disponível, talvez seja hora de rever a decisão de _não_ tornar esse comportamento disponível por meio de um sinalizador?

Se eu obtiver uma matriz de Thing do servidor e precisar exibir o último Thing da matriz, algo que normalmente encontro é que a matriz está realmente vazia e acessando uma propriedade em que Thing trava o aplicativo.

Exemplo:

// `things` is `Thing[]`, but is empty, i.e., `[]`
const { things } = data; 

// We are accessing `things[-1]`, which is obviously `undefined`, 
// but TypeScript thinks `latestThing` is a `Thing`
const latestThing = things[things.length - 1];

// TypeError: Cannot read property 'foo' of undefined
return latestThing.foo; 

Talvez isso seja um problema com o design de nossa API, mas realmente parece que o TS deve entender que, quando você acessa algo no array, ele pode não estar realmente lá.

Edit: Só quero esclarecer que quis dizer "nossa API" como em "a API criada pela equipe em que estou trabalhando"

sim, todos concordam que é uma decisão de design muito ruim, contemporânea de uma época remota em que o TS era completamente inseguro.

Como solução alternativa, uso um pequeno hack sujo. Acabei de adicionar package.json o script simples de pós-instalação:

{
...
  "scripts": {
    "postinstall": "sed -i 's/\\[n: number\\]: T;/[n: number]: T | undefined;/g' node_modules/typescript/lib/lib.es5.d.ts",
    ...
  },
...
}

Claro, não funcionará no Windows, mas isso é melhor do que nada.

Todos os outros problemas que rodam adjacentes a este parecem ser redirecionados aqui, então presumo que este é o melhor lugar para perguntar: uma sintaxe abreviada para assinaturas de índice opcionais está fora da tabela? Como @martpie aponta, você deve escrever interface Foo { [k: string]: Bar | undefined; } que é menos ergonômico do que interface Foo { [k: string]?: Bar; } .

Podemos obter suporte de operador de ?: ? Se não, porque não? Ou seja, o benefício ergonômico foi suficiente para adicionar o recurso de definição de propriedade única - não é "útil o suficiente" para assinaturas de índice?

type Foo = { [_ in string]?: Bar } também funciona. Não tão bonito, mas bastante conciso. Você também pode fazer seu próprio tipo Dict se desejar. Não seria contra uma extensão ?: , embora

Isso está começando a parecer uma daquelas conversas de "Javascript: as partes ruins":

ts type Foo1 = { [_ in string]?: Bar } // Yup type Foo2 = { [_: string]?: Bar } // Nope interface Foo3 { k?: Bar } // Yup interface Foo4 { [_: string]?: Bar } // Nope

Usar T | undefined para a assinatura de tipo não é realmente útil. Precisamos de uma maneira de fazer com que o operador de índice [n] tenha um tipo T|undefined , mas, por exemplo, usar map em um array não deve nos dar T|undefined valores, porque nessa situação devemos saber que eles existem.

@radix Para as funções reais em Array, não acho que isso seja um problema porque todas elas têm suas próprias definições de tipo que geram o tipo certo: por exemplo, map : https://github.com/microsoft /TypeScript/blob/master/lib/lib.es5.d.ts#L1331

O único uso de código comum que a construção | undefined apresenta uma regressão real da experiência está em for ... of loops. Infelizmente (do ponto de vista deste problema), essas são construções bastante comuns.

@riggs , acho que você está falando sobre fazer interface Array<T> { } ter [index: number]: T | undefined , mas @radix provavelmente está falando sobre o que parece ser a recomendação atual, que é usar Array<T | undefined> em seu próprio código.

O último é ruim por várias razões, a menos delas é que você não controla os tipos de outros pacotes, mas o primeiro também tem alguns problemas, nomeadamente que você pode atribuir undefined ao array, e que ele dá undefined mesmo em casos sabidamente seguros. 🤷‍♂️

Ahh, sim, meu mal-entendido. Na verdade, eu estava apenas me referindo ao uso da definição [index: number]: T | undefined . Eu concordo totalmente que definir um tipo como Array<T | undefined> é uma solução terrível que causa mais problemas do que resolve.

Existe uma maneira elegante de substituir lib.es5.d.ts ou um script de pós-instalação é a melhor maneira de fazer isso?

@ nicu-chiciuc https://www.npmjs.com/package/patch-package Se encaixa totalmente na caixa de ferramentas de um herege ao lado de https://www.npmjs.com/package/yalc ✌️

Existe uma maneira elegante de substituir lib.es5.d.ts ou um script de pós-instalação é a melhor maneira?

Em nosso projeto, temos um arquivo global.d.ts que usamos para 1) adicionar definições de tipos para APIs integradas que ainda não estão nas definições de tipo padrão do typescript (por exemplo, APIs relacionadas a WebRTC que estão constantemente mudando e inconsistentes entre os navegadores ) e 2) sobrescrever algumas das definições de tipo padrão do typescript (por exemplo, sobrescrever o tipo de Object.entries para que ele retorne arrays contendo unknown vez de any ).

Não tenho certeza se essa abordagem poderia ser usada para substituir os tipos de array do typescript para resolver esse problema, mas pode valer a pena tentar.

O procedimento acima funciona quando a declaração de mesclagem dá o resultado desejado, mas para simplificar eles interceptam interfaces, mas aqui a opção menos restritiva é o que você deseja.

Em vez disso, você pode tentar copiar e colar todos os lib.*.d.ts arquivos que está usando em seu projeto, incluídos em tsconfig.json files ou include , em seguida, editá-los para o que você quiser. Como eles incluem /// <reference no-default-lib="true"/> , você não deve precisar de nenhuma outra mágica, mas não tenho certeza se você deve remover os /// <reference lib="..."/> eles têm de suas dependências. Isso é ruim por todas as razões óbvias de manutenção, é claro.

@butchler Também estamos usando essa abordagem, mas parecia que substituir o operador de indexação não era possível, embora tenhamos substituído algumas outras funções de Array ( filter com protetores de tipo) com êxito.

Estou tendo um caso estranho com a substituição do operador de indexação:

function test() {
  const arr: string[] = [];

  const [first] = arr;
  const zero = arr[0];

  const str1: string = first;
  const str2: string = zero;
}

Screenshot 2020-02-05 at 10 39 20 AM

A segunda atribuição erra (como deveria), mas a primeira não.
Ainda mais estranho é que pairar sobre first ao ser desestruturado mostra que tem um tipo de string | undefined mas pairando sobre ele enquanto está sendo atribuído mostra que tem um tipo de string .
Screenshot 2020-02-05 at 10 40 25 AM
Screenshot 2020-02-05 at 10 40 32 AM

Existe uma definição de tipo diferente para a desestruturação de array?

Procurando uma solução para isso, pois erros relacionados a índices ausentes têm sido uma fonte frequente de bugs em meu código.

Especificar um tipo como { [index: string]: string | undefined } não é uma solução, pois atrapalha a digitação de iteradores como Object.values(x).forEach(...) que nunca incluirá undefined valores.

Gostaria de ver o TypeScript gerar erros quando não verifico por indefinido depois de fazer someObject[someKey] , mas não ao fazer Object.values(someObject).forEach(...) .

@danielnixon Isso não é uma solução, é uma solução alternativa. Não há nada que impeça você ou outro desenvolvedor de usar erroneamente (se é que você pode chamar assim) as ferramentas integradas da linguagem para os mesmos objetivos. Quer dizer, eu uso fp-ts para essas coisas, mas esse problema ainda é válido e precisa ser consertado.

Embora eu possa seguir os contra-argumentos de @RyanCavanaugh com:

for (let i = 0; i < arr.length; i++) {
  // TypeScript makes me use ! with my arrays, sad.
  console.log(arr[i]!.name);

Eu faria algumas reflexões curtas sobre isso. Basicamente, o TypeScript visa tornar o desenvolvimento, bem ... mais seguro. Para lembrar o primeiro objetivo do TypeScript:

1. Identifique estaticamente as construções que provavelmente são erros.

Se olharmos para o compilador, já temos alguma mágica por trás da inferência de tipo. No caso particular, a verdadeira desculpa é: "não podemos inferir o tipo certo nesta construção" e está tudo bem. Com o tempo, temos cada vez menos casos em que compilar não é capaz de inferir os tipos. Em outras palavras, para mim é apenas uma questão de tempo (e despender tempo nisso) para obter uma inferência de tipo mais sofisticada.

Do ponto de vista técnico, construções como:

const x = ['a', 'b', 'c']
console.log(x[3]) // type: string, reality: undefined

quebra o primeiro objetivo do TypeScript. Mas isso não acontece se o TypeScript conhece os tipos mais precisos:

const x = ['a', 'b', 'c'] as const
console.log(x[3]) // compile error: Tuple type 'readonly ["a", "b", "c"]' of length '3' has no element at index '3'.ts(2493)

Do ponto de vista prático, é uma troca yet . Este problema e vários problemas fechados mostram que há uma alta demanda da comunidade por esta mudança: ATM 238 votos positivos vs. 2 votos negativos. É claro que é uma pena que o loop for acima não infere o tipo "certo", mas bem, tenho certeza de que a maioria dos upvoters pode viver com ! e os novos ? como sinal de atenção e forçá-lo em casos seguros. Mas, por outro lado, obtenha os tipos certos de acesso "perigoso".

Como concordo que é uma mudança muito sensível para grandes bases de código, voto em uma propriedade de configuração, como foi proposta aqui . Pelo menos para obter feedback da comunidade. Se não for possível, então, bem, o TS 4.0 deve entendê-lo.

Esta não é uma solução proposta, mas apenas um experimento: tentei modificar lib.es5.d.ts dentro de meu node_modules em um projeto existente que usa TypeScript apenas para ver como seria se _disse_ tivéssemos um compilador opção para isso. Modifiquei Array e ReadonlyArray :

interface ReadonlyArray<T> {
  ...
  [n: number]: T | undefined; // was just T
}

interface Array<T> {
  ...
  [n: number]: T | undefined; // was just T
}

Como este é um projeto muito grande, isso causou várias centenas de erros de tipo. Passei por apenas alguns deles para ter uma ideia de que tipo de problemas eu encontraria e como seria difícil contorná-los se houvesse uma opção de compilador para isso. Aqui estão alguns dos problemas que encontrei:

  1. Isso causou erros de tipo não apenas em nossa base de código, mas em uma de nossas dependências: io-ts. Visto que io-ts permite que você crie tipos que você usa em seu próprio código, não acho que seja possível aplicar essa opção apenas à sua própria base de código e não aplicá-la também aos tipos de io-ts. Isso significa que io-ts e provavelmente algumas outras bibliotecas ainda teriam que ser atualizadas para funcionar com esta opção, mesmo se ela fosse introduzida apenas como uma opção do compilador. A princípio pensei que tornar isso uma opção de compilador tornaria isso menos controverso, mas se as pessoas que escolherem usar a opção começarem a reclamar com um monte de autores de bibliotecas diferentes sobre incompatibilidades, pode ser ainda mais controverso do que simplesmente ser um TS 4.0 quebra de mudança.

  2. Às vezes, eu tinha que adicionar alguns protetores de tipo extras para eliminar a possibilidade de indefinido. Isso não é necessariamente uma coisa ruim e é o ponto principal desta proposta, mas estou apenas mencionando-o para ser completo.

  3. Eu tive um erro de tipo dentro de um loop for (let i = 0; i < array.length; i++) que estava passando sobre um Array<T> arbitrário. Eu não poderia simplesmente adicionar um protetor de tipo para verificar undefined , porque T si pode incluir undefined . As únicas soluções que consegui pensar foram A) usar uma asserção de tipo ou @ts-ignore para silenciar o erro de tipo ou B) usar um loop for-of. Pessoalmente, não acho isso tão ruim (provavelmente há poucos casos em que usar um loop for-of para iterar em um array não é melhor de qualquer maneira), mas pode ser controverso.

  4. Existem muitos casos em que o código existente já estava fazendo alguma afirmação sobre o valor de .length e, em seguida, acessando um elemento no array. Isso agora causava erros de tipo, apesar da verificação .length , então tive que alterar o código para não depender de uma verificação .length ou apenas adicionar uma verificação !== undefined redundante. Seria muito bom se o TypeScript pudesse, de alguma forma, permitir o uso de um cheque .length para evitar a necessidade do cheque !== undefined . Presumo que realmente implementar isso não seria trivial, no entanto.

  5. Algum código estava usando A[number] para obter o tipo dos elementos de um tipo de array genérico. No entanto, agora isso retornou T | undefined vez de apenas T , causando erros de tipo em outro lugar. Eu criei um ajudante para contornar isso:

    type ArrayValueType<A extends { [n: number]: unknown }> = (
      A extends Array<infer T> ? T :
      A extends ReadonlyArray<infer T> ? T :
      A[number] // Fall back to old way of getting array element type
    );
    

    mas mesmo com esse auxiliar para facilitar a transição, ainda é uma grande mudança significativa. Talvez o TypeScript pudesse ter algum tipo de caso especial para A[number] para evitar esse problema, mas seria bom evitar casos especiais estranhos como esse, se possível.

Eu só passei por um pequeno punhado das centenas de erros de tipo, então esta é provavelmente uma lista muito incompleta.

Para qualquer pessoa interessada em corrigir esse problema, pode ser útil tentar fazer a mesma coisa com alguns projetos existentes e compartilhar outros problemas que você encontrou. Esperançosamente, os exemplos específicos podem fornecer alguma orientação para qualquer pessoa que realmente tente implementar isso.

@butchler Também something[i] como something(i) , ou seja, não posso simplesmente usar algo como if (Meteor.user() && Meteor.user()._id) {...} , então não Não espero ter essa abordagem para indexação de array; Preciso primeiro obter o valor que desejo inspecionar da matriz. O que estou tentando dizer é que confiar no TS para entender que verificar a propriedade de comprimento e fazer alguma afirmação com base nisso pode colocar muito estresse no sistema de tipos. Uma coisa que me fez adiar a transição (além do fato de que algumas outras bibliotecas também têm erros, como você disse) é que a desestruturação do array ainda não está funcionando corretamente e o valor desestruturado não será T | undefined mas T (veja meu outro comentário).
Fora isso, acho que substituir lib.es5.d.ts é uma boa abordagem. Mesmo que alguma coisa mude no futuro, reverter a mudança não adicionará erros adicionais, mas irá garantir que alguns casos extremos já sejam cobertos.
Já começamos a usar patch-package para alterar algumas definições de tipo em react e essa abordagem parece funcionar bem.

Também analisei alguns desses problemas, comecei a ver algo [i] como algo (i), ou seja, não posso simplesmente usar algo como if (Meteor.user () && Meteor.user () ._ id) { ...}, então não espero ter essa abordagem para indexação de array; Preciso primeiro obter o valor que desejo inspecionar da matriz.

Sim, também acabei usando essa abordagem quando estava tentando meu experimento. Por exemplo, código que foi escrito anteriormente como:

if (array[i]) {
  array[i].doSomething(); // causes a type error with our modified Array types
}

teve que ser alterado para algo como:

const arrayValue = array[i]
if (arrayValue) {
  arrayValue.doSomething();
}

O que estou tentando dizer é que confiar no TS para entender que verificar a propriedade de comprimento e fazer alguma afirmação com base nisso pode colocar muito estresse no sistema de tipos.

Provavelmente verdade. Na verdade, pode ser mais fácil escrever um codemod para reescrever automaticamente o código que depende de afirmações sobre .length para usar alguma outra abordagem, do que tornar o TypeScript inteligente o suficiente para inferir mais sobre os tipos com base em afirmações sobre .length 😛

Mesmo que alguma coisa mude no futuro, reverter a mudança não adicionará erros adicionais, mas irá garantir que alguns casos extremos já sejam cobertos.

Este é um bom ponto. Embora incluir undefined em tipos de índice seja uma grande alteração significativa, ir na outra direção não é uma alteração significativa. O código que é capaz de lidar com a obtenção de T | undefined também deve ser capaz de lidar com T sem quaisquer alterações. Isso significa, por exemplo, que as bibliotecas podem ser atualizadas para lidar com o caso T | undefined se houver um sinalizador do compilador e ainda ser usadas por projetos que não têm esse sinalizador do compilador habilitado.

Ignorando as matrizes e focando em Record<string, T> por um momento, minha lista de desejos pessoal é que a escrita permite apenas T mas a leitura pode ser T|undefined .

declare const obj : Record<string, T>;
declare const t : T;
obj["k"] = t; //ok
obj["k"] = undefined; //error, undefined not assignable to T

//T|undefined inferred,
//since we don't know if "k2" is an "ownProperty" of obj
const v = obj["k2"];

A única maneira de o acima ser ergonômico seria algum tipo de digitação dependente e protetores de tipo dependente. Como não os temos, adicionar esse comportamento causaria todos os tipos de problemas.

//Shouldn't just be string[]
//Should also be something like (keyof valueof obj)[],
//A dependent type
const keys = Object.keys(obj);

Voltando aos arrays, o problema é que a assinatura do índice para os arrays não tem a mesma intenção de Record<number, T> .

Então, você exigiria protetores de tipo dependente totalmente diferente, como,

for (let i=0; i<arr.length; ++i) {
  //i is not just number
  //i should also be something like keyof valueof arr 
}

Portanto, a assinatura de índice para matrizes não é realmente Record<number, T> . É mais parecido com Record<(int & (0 <= i < this["length"]), T> (um intervalo e tipo inteiro apoiado por número)


Assim, enquanto a postagem original fala apenas sobre matrizes, o título parece sugerir assinaturas de índice de "apenas" string ou "apenas" number . E são duas discussões completamente diferentes.

TL; DR A postagem e o título originais falam sobre coisas diferentes, a implementação de qualquer um parece fortemente dependente (ha) da digitação dependente.

Dado que funções como forEach , map , filter , etc existem (e são IMHO muito mais preferíveis), eu não esperaria que a equipe de TS complicasse muito seu mecanismo de inferência para suporte looping sobre uma matriz com um loop for regular. Tenho a sensação de que há complexidade inesperada demais em tentar fazer isso. Por exemplo, uma vez que i não é uma constante, o que acontece se alguém alterar o valor de i dentro do loop? Claro, é um caso extremo, mas é algo que eles precisam lidar de uma forma (espero) intuitiva.

A correção de assinaturas de índice, no entanto, deve ser relativamente simples (ish), como os comentários acima mostraram.

4. Existem muitos casos em que o código existente já estava fazendo alguma afirmação sobre o valor de .length e, em seguida, acessando um elemento no array. Isso agora causava erros de tipo, apesar da verificação .length , então tive que alterar o código para não depender de uma verificação .length ou apenas adicionar uma verificação !== undefined redundante. Seria muito bom se o TypeScript pudesse, de alguma forma, permitir o uso de um cheque .length para evitar a necessidade do cheque !== undefined . Presumo que realmente implementar isso não seria trivial, no entanto.

@butchler
Por curiosidade, quantos deles estavam acessando com uma variável variável, vs acessando com um número fixo? Porque, no último caso, você provavelmente está usando o array como uma tupla, para a qual o TS mantém o controle do comprimento do array. Se você usa arrays com frequência como tuplas, gostaria de saber o que redigitá-los como tuplas reais afeta a quantidade de erros.

Se você usa arrays com frequência como tuplas, gostaria de saber o que redigitá-los como tuplas reais afeta a quantidade de erros.

Eu encontrei um caso em que consertei um erro de tipo apenas adicionando as const a um literal de array para transformar o tipo da variável em uma tupla. Não tenho absolutamente nenhuma ideia de como isso é comum em nossa base de código, já que só observei cerca de 10 erros em várias centenas.

Eu também encontrei um problema com isso hoje.
Estamos acessando de uma matriz por meio de um índice computado. Esse índice pode estar fora do intervalo.
Portanto, temos algo assim em nosso código:

const noNext = !items[currentIndex + 1];

Isso resulta em noNext sendo definido como false . O que está errado. Pode ser verdade.
Eu também não quero definir items como Array<Item | undefined> porque isso dá uma expectativa errada.
Se houver um índice, ele nunca deve ser undefined . Mas se você estiver usando o índice errado, _is_ undefined .
Claro, o acima provavelmente poderia ser resolvido usando um cheque .length ou definindo noNext explicitamente como boolean .
Mas no final, isso é algo que me incomoda desde que comecei a usar o TypeScript e nunca entendi por que | undefined não está incluído por padrão.

Uma vez que espero que a maioria use typescript-eslint, existe alguma regra que possa impor que os valores devam ser verificados após a indexação antes de poderem ser usados? Isso pode ser útil antes que o suporte do TS seja implementado.

Caso seja do interesse de alguém. Em um comentário anterior, mencionei que a abordagem de corrigir a definição de indexação Array em lib.es5.d.ts tem um comportamento estranho na desestruturação do array, uma vez que parecia que ela não foi influenciada pela mudança. Parece que depende da opção target em tsconfig.json e funciona corretamente quando é es5 (https://github.com/microsoft/TypeScript/issues/37045 )

Para o nosso projeto, isso não é um problema, pois estamos usando a opção noEmit e a transpilação é feita por meteor . Mas para projetos em que isso pode ser um problema, uma solução que pode funcionar é safe-array-destructuring (https://github.com/typescript-eslint/typescript-eslint/pull/1645).
Ainda é um rascunho e pode não ter valor quando todo o problema for corrigido no compilador TypeScript, mas se você acha que pode ser útil, sinta-se à vontade para abordar quaisquer preocupações / melhorias na regra de typescript-eslint PR

Infelizmente, a correção com eslint em seu PR só oferece suporte a tuplas e não a matrizes. O principal problema e o principal impacto que tenho com isso é a desestruturação do array.

const example = (args: string[]) => {
  const [userID, nickname] = args
}

Acho que toda essa questão foi para um lado em que eu discordo. Eu não acho que deva haver verificações obrigatórias no forEach, map etc ... Eu também tento evitar todos os usos de for quando possível, então os raciocínios para evitar isso fazem menos sentido para mim. No entanto, ainda acho que isso é crucial para dar suporte à desestruturação de array.

Bem, ele simplesmente erra para qualquer desestruturação de array, então você é forçado a fazer isso usando indexação.

Não é tão ruim assim? A desestruturação do array é um recurso incrível. O problema é que as digitações não são precisas. Desativar o recurso não é realmente uma boa solução. Usar índices é, na minha opinião, um código mais feio (mais difícil de ler e entender) e mais sujeito a erros.

É mais feio, mas força o desenvolvedor a verificar se o elemento está definido ou não. Tentei encontrar soluções mais elegantes, mas parece que essa é a que menos tem desvantagens. Pelo menos para nosso caso de uso.

@caseyhoward Conforme observado anteriormente neste problema, isso causa um comportamento indesejado com as várias funções Array.prototype:

x.forEach( (i: string) => { ... } )  // Error because i has type string | undefined

Isso não precisa ser corrigido nas funções array.prototype. Este é um grande problema para a desestruturação do array!

Corri para isso de novo hoje. Ainda acho que o meio-termo certo é esse , sobre o qual adoraria receber algum feedback.

Outro caso de uso para este problema — encontrei este problema ao usar typescript-eslint e habilitar no-unnecessary-condition . No passado, ao acessar uma matriz por índice para fazer alguma operação no elemento nesse índice, usamos encadeamento opcional para garantir que esse índice seja definido (no caso de o índice estar fora dos limites), como em array[i]?.doSomething() . Com o problema descrito neste problema, no entanto, no-unnecessary-condition sinaliza esse encadeamento opcional como desnecessário (já que o tipo não é anulável de acordo com o texto digitado) e o autofix remove o encadeamento opcional, levando a erros de tempo de execução quando o acesso ao array em o índice i na verdade é indefinido.

Sem esse recurso, meu aplicativo tornou-se muito problemático, pois estou lidando com array multidimensional, tenho que sempre me lembrar de acessar elementos usando xs[i]?.[j] vez de xs[i][j] , também tenho que lançar explicitamente o elemento acessado como const element = xs[i]?.[j] as Element | undefined para garantir a segurança do tipo.

Encontrar isso foi meu primeiro grande wtf em Typescript. A linguagem, de outra forma, eu descobri ser maravilhosamente confiável e isso me decepcionou, e é bastante complicado adicionar "as T | undefined" aos acessos de array. Adoraria ver uma das propostas implementadas.

Sim, o mesmo aqui. Isso nos forçou a lançar nossos próprios tipos que têm ( | undefined ) para ter segurança de tipo. Principalmente para acesso a objetos (provavelmente é um problema aberto separado), mas a mesma lógica se aplica ao acesso a índices de array.

Não tenho certeza do que você quer dizer, pode vincular o problema ou mostrar um exemplo?

Na quarta-feira, 29 de abril de 2020 às 2h41 Kirill Groshkov [email protected]
escreveu:

Sim, o mesmo aqui. Isso nos forçou a lançar nossos próprios tipos que têm (|
indefinido) para ter segurança de tipo. Principalmente para acesso a objetos (provavelmente é um
problema aberto separado), mas a mesma lógica se aplica ao acesso aos índices da matriz.

-
Você está recebendo isso porque comentou.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/microsoft/TypeScript/issues/13778#issuecomment-621096030 ,
ou cancelar
https://github.com/notifications/unsubscribe-auth/AAAGFJJT37N54I7EH2QLBYDRO7Y4NANCNFSM4C6KEKAA
.

@alangpierce

Uma ideia que parece ser promissora: e se você pudesse usar ?: ao definir uma assinatura de índice para indicar que você espera que os valores às vezes faltem sob o uso normal? Ele agiria como | undefined mencionado acima, mas sem as desvantagens estranhas. Seria necessário proibir os valores undefined explícitos, o que eu acho que é uma diferença dos ?: usuais.

Esta é uma ideia interessante que aponta na direção certa. No entanto, eu inverteria a lógica: o caso padrão deve ser que as assinaturas de índice incluam | undefined . Se quiser dizer ao compilador que o caso indefinido nunca pode ocorrer em seu código (para que você nunca acesse chaves inválidas), você pode adicionar !: à assinatura, assim:

type AlwaysDefined = {[key: string]!: string};
const t: AlwaysDefined = {};

t['foo'] // Has type string

e o caso padrão sem !: pareceria que o conhecemos, mas seria mais seguro:

type MaybeUndefined = {[key: string]: string};
const t: MaybeUndefined = {};

t['foo'] // Has type string | undefined

Dessa forma, você tem a segurança por padrão e, da mesma forma, não precisa incluir explicitamente undefined que permitiria escrever undefined acidentalmente.

Desculpe se isso já foi sugerido, não consegui ler todos os comentários neste tópico.

Em geral, eu realmente acho que o comportamento padrão que temos agora não é o que os usuários esperam ao usar o TypeScript. É melhor prevenir do que remediar, especialmente quando um erro pode ser detectado tão facilmente quanto este pelo compilador.

Não consegui fazer o index.d.ts funcionar com meu projeto e realmente não queria lidar com ele.

Eu criei este pequeno hack que força um tipo de guarda:

 const firstNode = +!undefined && nodes[0];

Embora o tipo termine como: 0 | T , ele ainda faz o trabalho.

const firstNode = nodes?.[0] funcionaria?

@ricklove algum motivo pelo qual você preferiu +!undefined a simplesmente 1 ?

const firstNode = nodes?.[0] funcionaria?

Não, como o texto datilografado (incorretamente, em minha opinião) não o tratará como opcional (ver # 35139).

De acordo com a documentação de Flow, o comportamento com assinaturas de índice é o mesmo que atualmente com TypeScript:

Quando um tipo de objeto tem uma propriedade de indexador, os acessos de propriedade são assumidos como tendo o tipo anotado, mesmo se o objeto não tiver um valor naquele slot em tempo de execução. É responsabilidade do programador garantir que o acesso seja seguro, como acontece com os arrays.

var obj: { [number]: string } = {};
obj[42].length; // No type error, but will throw at runtime

Portanto, neste caso, tanto o TS quanto o Fluxo exigem que o usuário pense em chaves indefinidas em vez de no compilador ajudando-os :(

Seria uma vantagem do TS se o compilador impedisse os usuários de criarem esses tipos de bugs. Lendo todos os comentários neste tópico, parece que a grande maioria aqui adoraria ter esse recurso. A equipe do TS ainda está 100% contra?

Parece bobo que TS está fazendo o melhor para impedir o acesso a membros indefinidos, exceto este. Este é de longe o bug mais comum que venho corrigindo em nossa base de código.

[movendo a discussão para cá de # 38470]
Aceitando todos os argumentos que explicam por que isso não seria prático para matrizes ..
Acho que isso definitivamente deve ser abordado para Tuplas:

// tuple 
var str = '';
var num = 100
var aa = [str, num] as const

// Awesome
var shouldBeString = aa[0] // string
var shouldBeNumber = aa[1] // number
var shouldError = aa[10000]; // type error

// Not so awesome 
var foo = aa[num] // string | number

por que não fazer foo string | number | undefined ?

Dar isso às tuplas não deve afetar a maioria dos usuários, e os usuários que isso causa precisam ter strictNullChecks habilitado e declarar seus arrays como constantes ...
Eles estão, obviamente, procurando por tipos de segurança mais rígidos.

também poderia ajudar com estes

var notString1 = aa[Infinity]; // no error, not undefined
var notString2 = aa[NaN]; // no error, not undefined

O que originalmente causou meu erro de tempo de execução quando number tornou-se NaN , em seguida, retornou undefined de uma tupla.
Todos os tipos eram seguros

Não tenho certeza se as tuplas são realmente tão diferentes. Eles têm que acomodar tipos de descanso (por exemplo, [str, ...num[]] ) e, tecnicamente, aa[Infinity] é um Javascript perfeitamente válido, então posso ver que está ficando complicado criar um caso especial para eles.

Sua postagem também me fez pensar sobre como seria se obtivéssemos algum suporte para tratar os retornos do índice como indefinidos. Se, como no seu exemplo, aa[num] realmente digitou como string | number | undefined , eu teria que escrever for (let i = 0; i < 2; i++) { foo(aa[i]!); } mesmo sabendo que o índice permanecerá dentro dos limites? Para mim, quando as verificações de nulo estritas sinalizam algo que escrevi, gosto de poder consertar digitando as coisas melhor em primeiro lugar ou usando guardas de tempo de execução - se eu tiver que recorrer a uma afirmação não nula, normalmente vejo isso como um fracasso da minha parte. Não vejo uma maneira de contornar isso neste caso, porém, e isso me incomoda.

Se tuple[number] sempre digitado como T | undefined , como você deseja lidar com o caso em que sabe que o índice está limitado corretamente?

@ thw0rted Não me lembro da última vez que usei um loop for "clássico" no texto datilografado ( .map e .reduce ftw). É por isso que uma opção de compilador para isso seria ótimo imo (que pode estar desativado por padrão). Muitos projetos nunca encontram nada parecido com o caso que você forneceu e só usam assinaturas de índice para desestruturação de array, etc.

(meu comentário original: https://github.com/microsoft/TypeScript/issues/13778#issuecomment-517759210)

Oh, com toda a honestidade, eu também raramente indexo com um loop for. Eu faço uso extensivo de Object.entries ou Array#map , mas de vez em quando preciso passar chaves para indexar em um objeto ou (provavelmente com menos frequência) índices em uma matriz ou tupla. O laço for foi um exemplo inventado, certamente, mas levanta a questão de que qualquer uma das opções ( undefined ou não) tem suas desvantagens.

mas levanta a questão de que qualquer uma das opções ( undefined ou não) tem desvantagens.

Sim, seria bom se o usuário do TypeScript pudesse escolher qual lado negativo ele prefere: wink:

Oh, com toda a honestidade, eu também raramente indexo com um loop for. Eu faço uso extensivo de Object.entries ou Array#map , mas de vez em quando preciso passar chaves para indexar em um objeto ou (provavelmente com menos frequência) índices em uma matriz ou tupla.

Nos casos em que for necessário, você pode usar Array#entries :

for (const [index, foo] of array.entries()) {
    bar(index, foo)
}

Eu pessoalmente não uso Array#forEach tanto assim, e nunca uso Array#map para iteração (no entanto, eu o uso o tempo todo para mapeamento). Prefiro manter meu código simples e agradeço a capacidade de sair de um loop for ... of.

Não tenho certeza se as tuplas são realmente tão diferentes. Eles têm que acomodar tipos de descanso (por exemplo, [str, ...num[]] ) e, tecnicamente, aa[Infinity] é um Javascript perfeitamente válido, então posso ver que está ficando complicado criar um caso especial para eles.

Sua postagem também me fez pensar sobre como seria se obtivéssemos algum suporte para tratar os retornos do índice como indefinidos. Se, como no seu exemplo, aa[num] realmente digitou como string | number | undefined , eu teria que escrever for (let i = 0; i < 2; i++) { foo(aa[i]!); } mesmo sabendo que o índice permanecerá dentro dos limites? Para mim, quando as verificações de nulo estritas sinalizam algo que escrevi, gosto de poder consertar digitando as coisas melhor em primeiro lugar ou usando guardas de tempo de execução - se eu tiver que recorrer a uma afirmação não nula, normalmente vejo isso como um fracasso da minha parte. Não vejo uma maneira de contornar isso neste caso, porém, e isso me incomoda.

Se tuple[number] sempre digitado como T | undefined , como você deseja lidar com o caso em que sabe que o índice está limitado corretamente?

Não tenho certeza se há tanto for(let i..) iterando nas tuplas ..
Se a tupla tiver um único tipo, o usuário provavelmente usará um Array,

Na minha experiência, as tuplas são ótimas para tipos mistos e, se esse for o caso geral, você está fazendo a verificação de tipo de qualquer maneira

var str = '';
var num = 100
var aa = [str, num] as const
for (let i = 0; i < aa.length; i++) {
 aa[i] // needs some type check anyway to determine if 'string' or 'number'
}

Além disso, supondo que essa mudança proposta aconteça, o que está impedindo as pessoas de fazer isso?

// this sucks
for (let i = 0; i < 2; i++) { 
   foo(aa[i]!); // ! required
}

// this would work
for (let i = 0 as 0 | 1; i < 2; i++) { 
   foo(aa[i]); //  ! not required 
}

você pode usar keyof typeof aa vez de 0 | 1 quando eles corrigem esse problema

Claro que não haveria qualquer tipo de segurança ao fazer i aritmética,
mas realmente permite que as pessoas escolham isso para segurança de tipo em vez do array padrão "Não é estritamente seguro de tipo, mas conveniente"

Acho que ter os operadores ?? e ?. significa que não é Array s.
Mas também, é provavelmente muito mais comum

  • iterar (por exemplo, com .map ou .forEach , onde não há necessidade de " T | undefined ", o retorno de chamada nunca é executado em índices fora dos limites) ou
  • atravesse Array em loops, alguns dos quais podem ser estaticamente determinados como seguros ( for...of .. quase, exceto para _arrays esparsos_, for...in eu acredito que é seguro).

E também, uma variável pode ser considerada um "índice seguro" se for verificada com <index> in <array> primeiro.


_Clarificação_: Estou falando sobre a razão original de Array<T> não ter o tipo de índice [number]: T | undefined (mas apenas [number]: T ), que era que seria muito complicado de usar.
Quero dizer que não é mais o caso, então undefined pode ser adicionado.

@ vp2177 O problema não são os recursos (sim, ?. funciona), o problema é que o compilador não nos avisa sobre isso e evita atirar no próprio pé.

Talvez uma regra de linting pudesse fazer isso, mas ainda assim.

sim, ?. funciona

Eu peço desculpa mas não concordo. ?. funcionará para acessar propriedades no valor indexado, mas o compilador ainda dirá que o valor está definido independentemente de ser ou não (consulte # 35139).

Para adicionar a isso, se você usar eslint e quiser usar no-unnecessary-condition , tais acessos serão sinalizados como desnecessários e removidos porque o compilador diz que nunca é indefinido e que tornaria ?. desnecessário.

@martpie Desculpe, acho que você entendeu mal meu comentário, acrescentou um esclarecimento.

Quer dizer ... parece uma boa ideia ter um --strictIndexChecks ou similar que permita às pessoas optar por este tipo de comportamento. Então, não será uma mudança radical para todos e proporciona um ambiente de tipo mais rígido.

Eu acredito que Flow funciona dessa maneira por padrão para índices e não tem os problemas mencionados acima, mas já faz um tempo que eu não o uso, então posso estar enganado.

@bradennapier Flow não usa T | undefined na assinatura do índice para Array s (então o mesmo que TypeScript, infelizmente.)

Eu já sugeri uma abordagem que poderia deixar todos felizes. Espero que não haja problema em repetir em outras palavras.

Basicamente, o comportamento padrão da assinatura do índice seria alterado para

  • Inclui | undefined ao ler de uma matriz ou objeto
  • Não inclua | undefined ao escrever (porque queremos apenas T valores em nosso objeto)

A definição seria assim:

type MaybeUndefined = {[key: string]: string};
const t: MaybeUndefined = {};

const x = t['foo'] // Has type string | undefined
t['foo'] = undefined // ERROR! 
t['foo'] = "test" // Ok

Para as pessoas que não desejam undefined incluídos no tipo, eles podem desativá-lo usando uma opção do compilador ou desativá-lo apenas para alguns tipos usando o operador ! :

type AlwaysDefined = {[key: string]!: string};
const t: AlwaysDefined = {};

const x = t['foo'] // Has type string
t['foo'] = undefined // ERROR! 
t['foo'] = "test" // Ok

Para não quebrar o código existente, também pode ser melhor introduzir uma nova opção de compilador que faça com que undefined seja incluído (como mostrado com o tipo MaybeUndefined acima). Se essa opção não for especificada, todos os tipos de assinatura de índice se comportam como se o operador ! fosse usado na declaração.

Outra ideia que surge: pode-se querer usar o mesmo tipo em alguns lugares do código com os dois comportamentos diferentes (inclua undefined ou não). Um novo mapeamento de tipo pode ser definido:

type MakeDefined<T> = {[K in keyof T]!: T[K]}

Seria uma boa extensão do TypeScript?

Eu gosto da ideia, mas suspeito que teríamos que obter https://github.com/microsoft/TypeScript/issues/2521 primeiro - o que, não me interpretem mal, ainda acredito que deveríamos absolutamente, mas eu ' não estou prendendo a respiração.

@bradennapier Flow não usa T | undefined na assinatura do índice para Array s (então o mesmo que TypeScript, infelizmente.)

Hmm e quanto aos índices de objetos? Nunca tive necessidade de matrizes, mas tenho quase certeza de que isso faz com que você verifique ao usar objetos.

@bradennapier Índices de objeto? Como em, o[4] onde o é o tipo object ? TypeScript não permite que você faça isso ...

a expressão do tipo '4' não pode ser usada para indexar o tipo '{}'
A propriedade '4' não existe no tipo '{}'. Ts (7053)

@RDGthree não ser o cavaleiro branco da Microsoft de todas as coisas, mas você aparentemente perdeu essa parte de mhegazy na primeira resposta:

Com exceção de strictNullChecks, não temos sinalizadores que alteram o comportamento do sistema de tipo. sinalizadores geralmente habilitam / desabilitam o relatório de erros.

e um pouco mais tarde, RyanCavanaugh tem:

Continuamos bastante céticos de que alguém obteria algum benefício com esta bandeira na prática. Mapas e coisas semelhantes a mapas já podem optar por | indefinido em seus sites de definição, e impor comportamento semelhante ao EULA no acesso ao array não parece uma vitória. Provavelmente precisaríamos melhorar substancialmente o CFA e os protetores de tipo para tornar isso palatável.

Então, basicamente - eles estão perfeitamente cientes do fato de que uma bandeira está sendo solicitada, e até agora eles não estão convencidos de que vale a pena trabalhar para eles pelo benefício um tanto duvidoso que foi sugerido que as pessoas obteriam com isso. Honestamente, não estou surpreso, já que todo mundo continua vindo aqui com exatamente as mesmas sugestões e exemplos que já foram abordados. Idealmente, você deve usar apenas for of ou métodos para arrays, e Map vez de objetos (ou menos efetivamente, valores de objeto 'T | undefined`).

Estou aqui porque mantenho um pacote DT popular e as pessoas continuam pedindo que |undefined seja adicionado a coisas como mapas de cabeçalho http, o que é totalmente razoável, exceto pela parte em que quebraria praticamente todos os usos existentes e o fato de que torna outros usos seguros, como Object.entries() muito piores de usar. Além de reclamar sobre como sua opinião é melhor do que a dos criadores do Typescript, qual é realmente a sua contribuição aqui?

Simon, talvez eu tenha interpretado mal o seu comentário, mas parece um argumento a favor da sugestão original. A capacidade que falta na "última milha" é tratar as propriedades como sendo | undefined às vezes , dependendo do contexto, mas não em outros casos. É por isso que fiz uma analogia com # 2521 upthread.

Em um cenário ideal, eu poderia declarar uma matriz ou objeto tal que, dado

ts const arr: Array<T>; const n: number; const obj: {[k: K]: V}; const k: K;

Eu posso de alguma forma terminar com

  • arr[n] digita como T | undefined
  • arr[n] = undefined é um erro
  • Eu tenho acesso a algum tipo de iteração em arr que me dá valores digitados como T , não T | undefined
  • obj[k] digita como V | undefined
  • obj[k] = undefined é um erro
  • Object.entries() por exemplo deve me dar tuplas de [K, V] e nada pode ser indefinido

É a natureza assimétrica do problema que define essa questão na minha opinião.

Eu diria que a afirmação de que é um problema para os desenvolvedores que usam loops for não leva em consideração os desenvolvedores que usam Destructuring de Array. Ele está corrompido no Typescript e também não será corrigido por causa desse problema. Ele fornece digitações imprecisas. A desestruturação do array IMO sendo efetuada é um problema muito maior do que aqueles que ainda usam loops for.

const example = (args: string[]) => {
  const [userID, duration, ...reason] = args
  // userID and duration is AUTOMATICALLy inferred to be a string here. 
 // However, if for whatever reason args is an empty array 
// userID is actually `undefined` and NOT a `string`. 

// This is valid but it should not be because userID could be undefined
userID.toUpperCase()
}

Não quero ir muito longe do ponto original do problema, mas você realmente deve declarar esse método de exemplo usando um tipo de tupla para o argumento da função. Se a função foi declarada como ([userId, duration, ...reason]: [string, number, ...string[]]) => {} então você não teria que se preocupar com isso em primeiro lugar.

Quero dizer, com certeza eu poderia apenas lançá-lo, mas isso não é realmente uma solução. Da mesma forma, qualquer um aqui poderia simplesmente lançar a indexação da matriz do loop for, bem como tornar todo esse problema mudo.

for (let i = 0; i < array.length; i++) {
  const value = array[i] as string | undefined
}

O problema é que o Typescript não avisa sobre esse comportamento. Como desenvolvedores, temos que nos lembrar de convertê-los manualmente como indefinidos, o que é um mau comportamento de uma ferramenta para causar mais trabalho para os desenvolvedores. Além disso, para ser justo, o elenco teria que ser algo assim:

const example = (args: [string | undefined, string | undefined, ...string[] | ...undefined[]]) => {

}

Isso não é nada legal. Mas como eu disse acima o problema principal não é nem precisar digitar assim, é que o TS não avisa sobre isso. Isso leva os desenvolvedores que esquecem disso a enviar o código por push. O CI legal não encontrou problemas na fusão e implantação do tsc. TS dá uma sensação de confiança no código e ter digitações incorretas sendo fornecidas dá uma falsa sensação de confiança. => Erros no tempo de execução!

Isso não é um "elenco", isso é digitar corretamente seus parâmetros formais. Meu ponto é que se você digitar seu método corretamente, então qualquer pessoa que o chamou de forma insegura com a assinatura antiga ( args: string[] ) agora obterá erros em tempo de compilação, como deveriam , quando não há garantia de que passe o número certo de argumentos. Se você deseja continuar deixando as pessoas escaparem sem passar por todos os argumentos de que você precisa, e verificando-os você mesmo em tempo de execução, é realmente muito fácil escrever como (args: [string?, number?, ...string[]]) . Isso já funciona muito bem e não é realmente pertinente a esse problema.

Isso apenas moveria o problema dessa função para outra função que está chamando esta função. Como você ainda precisa analisar a string fornecida pelos usuários em valores separados e se usar a desestruturação de array para analisá-la dentro do commandHandler que chama esta função, você terá o mesmo problema.

Não há como evitar que tipificações imprecisas sejam fornecidas para Destructuring de Array.

Não quero me afastar muito do ponto original do problema

Observação: embora eu concorde com você que eles devem ser tratados como problemas separados, sempre que alguém abre um problema mostrando os problemas com a desestruturação do array, ele está sendo fechado e os usuários encaminhados aqui. # 38259 # 36635 # 38287 etc ... Portanto, não acho que estejamos nos afastando do assunto desta edição.

Algumas das minhas frustrações com esse problema seriam resolvidas por alguma sintaxe simples para aplicar | undefined ao tipo que, de outra forma, seria inferido do acesso ao objeto. Por exemplo:

const complexObject: { [key: string]: ComplexType } = { ... };
const maybeKey = 'two';

// From:
const maybeValue = complexObject[maybeKey] as
  | (Some<Complex<Type>> & Pick<WithPlentyOf, 'Utility'> & Types)
  | undefined;

// To:
const maybeValue2 = complexObject[maybeKey] as ?;

Portanto, uma verificação de tipo mais rigorosa permanece opcional. O seguinte funciona, mas requer duplicação (e adiciona rapidamente uma quantidade semelhante de ruído visual):

const maybeValue2 = complexObject[maybeKey] as
  | typeof complexObject[typeof maybeKey]
  | undefined;

Eu imagino que esse tipo de sintaxe "tipo afirme talvez" ( as ? ) também tornaria mais fácil implementar uma regra require-safe-index-signature-access eslint que não é terrivelmente confusa para novos usuários. (Por exemplo, "Você pode consertar o erro de lint adicionando as ? no final.") A regra pode até incluir um autofixer seguro, uma vez que só pode causar falha na compilação, nunca problemas de tempo de execução.

function eh<T>(v: T): T | undefined {
    return v;
}

const arr = [0, 1, 2, 3, 4, 5];
const thing1 = arr[1]; // number
const thing2 = eh(arr[1]); // number | undefined

Eu não acho que haja uma única aplicação disso além desse recurso surpresa do TS, embora hehe

function eh<T>(v: T): T | undefined {
    return v;
}

const arr = [0, 1, 2, 3, 4, 5];
const thing1 = arr[1]; // number
const thing2 = eh(arr[1]); // number | undefined

Obrigado pela resposta. Embora uma função como esta funcione (e quase certamente será otimizada pela maioria dos mecanismos JS), prefiro aceitar o ruído de sintaxe extra mencionado ( as ComplexType | undefined ) do que fazer com que o arquivo JS compilado retenha este método "wrapper "em qualquer lugar em que seja usado.

Outra opção de sintaxe (que seria menos específica de caso de uso do que as ? acima) poderia ser permitir o uso da palavra-chave infer em asserções de tipo como um espaço reservado para o tipo inferido de outra forma, por exemplo:

const maybeValue = complexObject[maybeKey] as infer | undefined;

Atualmente infer declarações são permitidas apenas na cláusula extends de um tipo condicional ( ts(1338) ), dando assim à palavra-chave infer um significado no contexto de as [...] asserções do tipo não interfeririam.

Isso ainda pode ser razoável para aplicar usando infer | undefined por meio de uma regra eslint, embora seja geral o suficiente para outros casos, por exemplo, onde o tipo resultante pode ser refinado com tipos de utilitário:

// Strange example, maybe from an unusual compiler originally written in JS:
if(someRegularExpression.test(maybeKey)) {
  /**
  * If we are inside this code block and this `maybeKey` exists on `partialObject`, 
  * we know its `regexSuccess` must also be defined, though this knowledge 
  * cannot be encoded using TypeScript's type system.
  */
  const maybeValue = partialObject[maybeKey] as (Required<Pick<infer, 'regexSuccess'>> & infer) | undefined;
  // ...
}
// Here we don't know if `regexSuccess` is defined on `maybeValue2`:
const maybevalue2 = partialObject[maybeKey] as infer | undefined;
function eh<T>(v: T): T | undefined {
    return v;
}

const arr = [0, 1, 2, 3, 4, 5];
const thing1 = arr[1]; // number
const thing2 = eh(arr[1]); // number | undefined

É inútil se tivermos que continuar nos lembrando de usar um padrão diferente e mais seguro de qualquer maneira. O objetivo é que o TypeScript nos proteja, quer estejamos em um dia muito bom, cansados ​​ou novos em uma base de código.

Nosso comportamento atual é que a expressão de tipo Array<string>[number] é interpretada como "O tipo que ocorre quando você indexa uma matriz por um número". Você está autorizado a escrever

const t: Array<string>[number] = "hello";

como uma forma indireta de escrever const t: string .

Se essa bandeira existisse, o que deveria acontecer?

Se Array<string>[number] for string | undefined , então você tem um problema de solidez nas gravações:

function write<T extends Array<unknown>>(arr: T, v: T[number]) {
    arr[0] = v;
}
const arr = ["a", "b", "c"];
// Would be OK
write(arr, undefined);

Se Array<string>[number] for string , então você tem o mesmo problema que está sendo descrito nas leituras:

function read<T extends Array<unknown>>(arr: T): T[number] {
    return arr[14];
}
const arr = ["a", "b", "c"];
// Would be OK
const k: string = read(arr);

Parece muito complexo adicionar sintaxe de tipo para "O tipo de leitura do índice de" e "O tipo de gravação do índice de". Pensamentos?

Devo acrescentar que alterar o significado de Array<string>[number] é realmente problemático, pois isso implicaria que esse sinalizador altera a interpretação dos arquivos de declaração. Isso é estressante porque parece o tipo de sinalizador que um número substancial de pessoas ativaria, mas poderia causar erros que ocorressem / não ocorressem em coisas publicadas em DefinitelyTyped, então provavelmente teríamos que nos certificar de que tudo correria de forma limpa em ambas as direções. O número de sinalizadores que podemos adicionar com esse comportamento é obviamente extremamente pequeno (não podemos testar 64 configurações diferentes de cada tipo de pacote), então acho que teríamos que deixar Array<T>[number] como T apenas para evitar esse problema.

@RyanCavanaugh Em seu exemplo com write(arr, undefined) , a chamada para write seria aceita, mas a atribuição arr[0] = v; não seria mais compilada?

Se a função write acima veio de uma biblioteca e foi JS compilado sem o sinalizador, então com certeza seria incorreto. No entanto, isso já é um problema existente porque as bibliotecas podem ser compiladas com quaisquer sinalizadores que escolherem. Se uma biblioteca foi compilada sem verificações de nulos estritas, então uma função nela poderia retornar string , ao passo que ela realmente deveria retornar string|undefined . Mas a maioria das bibliotecas hoje em dia parece implementar verificações nulas, caso contrário, as pessoas reclamam. Da mesma forma, assim que este novo sinalizador existir, esperançosamente as bibliotecas começarão a implementá-lo e, eventualmente, a maioria das bibliotecas o terá definido.

Além disso, embora eu não consiga encontrar nenhum exemplo concreto de cabeça, certamente tive que definir skipLibCheck para fazer meu aplicativo compilar. É uma opção tsconfig padrão em nosso clichê sempre que criamos um novo repositório TS, porque tivemos muitos problemas com ele.

Pessoalmente (e talvez de forma egoísta), estou bem em lidar com casos extremos, se isso torna _meu_ código geralmente mais seguro. Ainda acredito que encontraria ponteiros nulos acessando arrays por índice com mais frequência do que em outros casos extremos, embora pudesse ser convencido do contrário.

If Array[número] é string | indefinido, então você tem um problema de solidez nas gravações:

Na minha opinião, Array<string>[number] deve ser sempre string|undefined . É a realidade, se eu indexar em uma matriz com qualquer número, obterei o tipo de item da matriz ou indefinido. Você não pode realmente ser mais específico lá, a menos que esteja codificando o comprimento do array como faria com as tuplas. Seu exemplo de escrita não digitaria check, porque você não pode atribuir string|undefined a um índice de array.

Parece muito complexo adicionar sintaxe de tipo para "O tipo de leitura do índice de" e "O tipo de gravação do índice de". Pensamentos?

Isso parece ser exatamente o que deveria ser, pois são duas coisas diferentes. Nenhum array terá um índice definido, então você sempre terá o potencial de ficar indefinido. O tipo de Array<string>[number] seria string|undefined . Para especificar o que você quer de T em um Array<T> , um tipo de utilitário pode ser usado (nomear não é ótimo): ArrayItemType<Array<string>> = string . Isso não ajuda com os tipos Record , que podem precisar de algo como RecordValue<Record<string, number>, string> = string .

Concordo que não há grandes soluções aqui, mas tenho quase certeza de que prefiro a solidez nas leituras de índice.

Eu não sinto muito a necessidade disso em arrays, uma vez que muitas outras linguagens (incluindo as "seguras" como Rust) deixam a responsabilidade das verificações de limites para o usuário, então eu e muitos desenvolvedores já estamos acostumados a fazer isso . Além disso, a sintaxe tende a torná-la bastante óbvia nesses casos, porque eles _sempre_ usam a notação de colchetes como foo[i] .

Dito isso, eu _do_ tenho muita vontade de adicionar isso a objetos indexados por strings, porque é muito difícil dizer se a assinatura de foo.bar está correta (devido ao campo bar ser explicitamente definido), ou possivelmente undefined (porque faz parte de uma assinatura de índice). Se este caso (que eu acho que tem um valor muito alto) for resolvido, o caso do array provavelmente se tornará trivial e provavelmente valerá a pena fazer também.


Parece muito complexo adicionar sintaxe de tipo para "O tipo de leitura do índice de" e "O tipo de gravação do índice de". Pensamentos?

A sintaxe getter e setter do javascript pode ser estendida para definições de tipo. Por exemplo, a seguinte sintaxe é totalmente compreendida pelo TypeScript:

const foo = {
  _bar: "",
  get bar(): string {
    return this._bar;
  },
  set bar(value: string) {
    this._bar = value;
  }
}

No entanto, o TS atualmente "mescla" estes em um único tipo, uma vez que não rastreia os tipos "get" e "set" de um campo separadamente. Essa abordagem funciona para a maioria dos casos comuns, mas tem suas próprias deficiências, como exigir que getters e setters compartilhem o mesmo tipo e atribuir incorretamente um tipo de getter a um campo que apenas define um setter.

Se o TS rastreasse "obter" e "definir" separadamente para todos os campos (o que provavelmente tem alguns custos reais de desempenho), isso resolveria essas peculiaridades e também forneceria um mecanismo para descrever o recurso descrito neste problema:

type foo = {
  [key: string]: string
}

seria essencialmente uma abreviação de:

type foo = {
  get [key: string](): string | undefined;
  set [key: string](string): string;
}

Devo acrescentar que mudar o significado de Array[número] é realmente problemático, pois implicaria que este sinalizador altera a interpretação dos arquivos de declaração.

Se os arquivos de declaração gerados sempre usassem a sintaxe completa getter + setter, isso seria _somente_ um problema para os escritos à mão. Aplicar as regras atuais à sintaxe abreviada restante _somente nos arquivos de definição_ pode ser uma solução aqui. Certamente resolveria os problemas de compatibilidade, mas também aumentaria o custo mental de ler .ts arquivos versus .d.ts arquivos.


Aparte:

Agora, é claro, isso não aborda _todos_ as advertências sutis com getters / setters:

foo.bar = "hello"

// TS assumes that bar is now a string, which technically isn't guaranteed when
// custom setters and getters are used.
const result: string = foo.bar

Este é mais um caso extremo e provavelmente vale a pena considerar se está dentro do escopo dos objetivos do TS ... mas de qualquer forma, provavelmente poderia ser resolvido separadamente, uma vez que minha proposta aqui é consistente com o comportamento atual do TypeScript.

Eu realmente sinto que tenho que aparecer a cada duas páginas de comentários e deixar um link para o número 2521 novamente.

Parece muito complexo adicionar sintaxe de tipo para "O tipo de leitura do índice de" e "O tipo de gravação do índice de". Pensamentos?

Não! E pelo menos (clica no link ...) 116 outros aqui concordam comigo. Eu ficaria muito feliz se, pelo menos, tivesse a opção de digitar leituras e gravações de maneira diferente. Este é apenas mais um ótimo caso de uso para esse recurso.

Eu ficaria _tão feliz_ se pelo menos tivesse a opção de digitar leituras e gravações de maneira diferente. Este é apenas _ainda_ um ótimo caso de uso para esse recurso.

Acredito que a intenção era questionar se deveria haver uma maneira de se referir aos tipos de leitura e gravação, por exemplo, deveria haver algo como Array<string>[number]= para se referir ao tipo de gravação?

Eu ficaria feliz com a resolução de ambos # 2521 e este problema se duas coisas acontecerem:

  • Primeiro, digite getters e setters de maneira diferente, conforme # 2521
  • Em seguida, crie um sinalizador conforme discutido nesta edição, de forma que o "tipo de gravação" de Array<string>[number] seja string enquanto o "tipo de leitura" seja string | undefined .

Eu (por engano?) Suponho que o primeiro estabeleceria as bases para o último, consulte https://github.com/microsoft/TypeScript/issues/13778#issuecomment -630770947. Não tenho nenhuma necessidade particular de uma sintaxe explícita para definir o "tipo de gravação" sozinho.

@RyanCavanaugh meta-pergunta rápida: seria mais útil ter minha sugestão bastante específica acima como uma questão separada com o propósito de debater mais claramente e rastrear seus pontos fortes / fracos / custos (especialmente porque os custos de desempenho não serão triviais)? Ou isso apenas aumentaria o ruído?

@RyanCavanaugh

const t: Array<string>[number] = "hello";

como uma forma indireta de escrever const t: string .

Eu acho que esse é o mesmo raciocínio para as tuplas?

const t : [string][number] = 'hello' // const t: string

no entanto, as tuplas estão cientes dos limites e retornam corretamente undefined para tipos de número mais específicos:

const t0: [string][0] = 'hello' // ok
const t1: [string][1] = 'hello' // Type '"hello"' is not assignable to type 'undefined'.

// therefore this is already true!
const t2: [string][0|1] // string | undefined 

por que o tipo number agiria de forma diferente?

Parece que você possui os mecanismos para fazer isso: https://github.com/microsoft/TypeScript/issues/38779

Isso atenderia às minhas necessidades sem adicionar um sinalizador adicional.

Caso ajude alguém, aqui está um plugin ESLint que sinaliza o acesso inseguro ao array e a desestruturação do array / objeto. Ele não sinaliza usos seguros como tuplas e objetos (sem registro).

https://github.com/danielnixon/eslint-plugin-total-functions

Felicidades, mas espero que esteja claro que isso ainda precisa ser adicionado no nível do idioma, pois é puramente dentro do domínio da verificação de digitação e alguns projetos não têm um linter ou as regras corretas.

@RyanCavanaugh

também testamos a aparência desse recurso na prática e posso dizer que não é bonito

Só por curiosidade, você pode nos dizer do que se trata a parte não bonita ? Talvez possamos resolver isso se você puder compartilhar mais sobre isso.

Isso não deveria ser uma opção, deveria ser o padrão .

Ou seja, se o sistema de tipos do TypeScript deve descrever em tempo de compilação os tipos que os valores JavaScript podem assumir em tempo de execução:

  • indexar um Array pode, é claro, retornar undefined em JavaScript (índice fora dos limites ou array esparso), então a assinatura de leitura correta seria T | undefined .
  • "fazer" com que um índice em Array seja undefined também é possível através da palavra-chave delete .

Como o TypeScript 4 impede

const a = [1, 2]
delete a[1]

há um bom caso para prevenir também a[1] = undefined .
Isso sugere que Array<T>[number] deve realmente ser diferente para leituras e gravações.

Pode ser "complexo", mas permitiria modelar possibilidades de tempo de execução em JavaScript com mais precisão;
É para isso que o TypeScript foi _feito_, certo?

Não há nenhum ponto em discutir a idéia de fazer | undefined retornar o comportamento padrão - eles nunca irão lançar uma versão do Typescript que apenas explode milhões de linhas de código legado devido a uma atualização do compilador. Uma mudança como essa seria a mudança mais significativa em qualquer projeto que já acompanhei.

Eu concordo com o resto da postagem.

Não há nenhum ponto em discutir a idéia de fazer | undefined retornar o comportamento padrão - eles _nunca_ lançarão uma versão do Typescript que apenas explode milhões de linhas de código legado devido a uma atualização do compilador. Uma mudança como essa seria a mudança mais significativa em qualquer projeto que já acompanhei.

Eu concordo com o resto da postagem.

Não se for uma opção de configuração.

Não se for uma opção de configuração.

Isso é justo, mas no mínimo eu esperaria um longo período de "entrada" em que está desativado por padrão, dando às pessoas muito tempo (6 meses? Um ano? Mais?) Para converter o código legado antes que ele se torne on-by -predefinição. Por exemplo, adicione o sinalizador na v4.0, defina-o como ativado por padrão na v5.0, esse tipo de coisa.

Mas isso é um futuro

Não há nenhum ponto em discutir a idéia de fazer | undefined retornar o comportamento padrão - eles _nunca_ lançarão uma versão do Typescript que apenas explode milhões de linhas de código legado devido a uma atualização do compilador. Uma mudança como essa seria a mudança mais significativa em qualquer projeto que já acompanhei.

Eu concordo com o resto da postagem.

Para seu ponto @ thw0rted , propus um meio-termo aqui: https://github.com/microsoft/TypeScript/issues/38779

Permite esta funcionalidade, numa funcionalidade que foi introduzida muito mais tarde, que ainda não é tão utilizada, e parece que já está "quase" aí e apenas necessita de pequenas alterações.

Para consistência de "strict" algo de Array<T> deve ser visto como "a list of type T that is infinitly long with no gaps" Sempre que isso não for desejado, você deve usar tuplas.

Sim, existem desvantagens para isso, mas mais uma vez eu sinto que é um "meio-termo" justo entre esse recurso nunca ser implementado e implementado exatamente como estamos pedindo aqui.

Se você concorda, por favor, vote positivamente

Eu discordo que isso precise de uma opção / configuração totalmente nova.

Eu não acho que deveria ser o comportamento padrão quando a opção estrita está desabilitada, mas quando eu habilito a opção estrita BE STRICT e aplico-a mesmo em projetos mais antigos. Habilitar estrito é o que as pessoas optam por digitações mais estritas / precisas possíveis. Se eu habilitar uma opção estrita, gostaria de confiar no meu código e essa é a melhor parte do TS. Mas essas digitações imprecisas, mesmo com estritamente habilitado, são apenas uma dor de cabeça.

No interesse de manter este tópico legível, escondi vários comentários tangenciais que não contribuíram de forma significativa para a conversa. Em geral, você pode supor que os comentários a seguir foram feitos antes nos 212 comentários anteriores e não precisam ser repetidos:

  • O TypeScript deve introduzir uma mudança invulgarmente grande no comportamento de verificação pela primeira vez - não, não fizemos isso antes e não pretendemos fazer isso no futuro, por favor, esteja mais envolvido com os objetivos de design de longo prazo do projeto antes de vir aqui para defender mudanças sem precedentes
  • Argumentos baseados na ideia de que ninguém considerou o fato de que o sinalizador de configuração existe - estamos cientes de que colocar as coisas atrás de um sinalizador significa que eles não precisam estar ativados por padrão, este não é o primeiro rodeio de ninguém
  • Qual é o problema, basta fazê-lo já! - bem, agora que você colocou dessa forma, estou convencido 😕

Existem desafios práticos e técnicos muito reais que precisam ser resolvidos aqui e é decepcionante que não possamos fazer um trabalho significativo neste tópico porque ele está repleto de pessoas que aparecem para sugerir que qualquer coisa por trás de uma bandeira não tem consequências reais ou que qualquer erro que possa be ts-ignore 'd não é um fardo de atualização.

@RyanCavanaugh

obrigado pelo seu resumo.

por favor, esteja mais envolvido com os objetivos de design de longo prazo do projeto antes de vir aqui para defender mudanças sem precedentes

Excluí meu comentário e, se desejar, posso excluí-lo também. Mas como entender o primeiro objetivo do projeto:

1. Identifique estaticamente as construções que provavelmente são erros.

(Fonte: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals)

Não entendo seu comentário citado, porque o acesso ao índice desmarcado pode levar a um erro de tempo de execução. Se no JavaScript você espera coisas assim, no TypeScript você obtém uma confiança perigosa e errada. Tipos errados são piores do que any .

bem, agora que você colocou dessa forma, estou convencido

Claro, você é um dos principais mantenedores e obrigado, a equipe e os contribuidores do ts. Mas não se trata mais apenas de você. Compare votos positivos: 365 com votos negativos: 6! Apenas 6! Mostra uma grande demanda por segurança de tipo.

Mas vamos falar sobre soluções. Existe alguma solução que a equipe faria ou possa pensar?

Você pode explicar um pouco mais o que há de errado com ts-ignore neste caso? Quer dizer, pode ser automatizado com ferramentas como o codemod e não altera o comportamento do código atual. Claro que não é uma bela solução alternativa, mas bem, é uma maneira possível de introduzir essa mudança sem sinalizadores.

O que você acha da publicação automática de uma versão corrigida do ts, por exemplo 4.0.0-breaking ? Ele introduz um certo (e muito?) Trabalho sobre conflitos, mas permite que todos testem as mudanças e preparem a base do código (não apenas para essa solicitação de recurso). Isso pode ser feito por um período de tempo limitado, como 3-6 meses. Seríamos os primeiros a utilizar esta versão.

mais. Compare votos positivos: 365 com votos negativos: 6! Apenas 6! Mostra uma grande demanda por segurança de tipo.
- @Bessonov

ha-ha.
Não que eu concorde com a postagem inteira de ..
há o que é um milhão? Usuários de TS por aí?!? 365 deseja este recurso o suficiente para comentar e votar positivamente neste tópico ...

Não há notificação do github, mas para todos que tentarem brincar com o índice indefinido desta edição, dê uma olhada no rascunho de PR acima do meu comentário.

@RyanCavanaugh muito obrigado por nos dar a oportunidade de brincar com ele. Eu o executei em uma base de código bem pequena (arquivos de ~ 105 ts (x)) e brinquei um pouco com ele. Não encontrei nenhum problema importante. Uma linha que foi alterada de:

const refHtml = useRef(useMemo(() => document.getElementsByTagName('html')[0], []))

para:

const refHtml = useRef(useMemo(() => document.getElementsByTagName('html')[0] ?? null, []))

Vou tentar em um projeto médio na próxima semana.

@ lonewarrior556 é 60 vezes mais e está na primeira página se você classificar por votos positivos :)

@Bessonov Eu acho que você deveria experimentá-lo na base de código do compilador Typescript, ele provavelmente causará uma quebra massiva devido ao uso não trivial de loops for.

Você pode até ter sinalizadores para ativar / desativar para fornecer compatibilidade com versões anteriores.

Cada sinalizador que você adiciona é outra matriz de configuração que precisa ser testada e suportada. Por esse motivo, o TypeScript deseja manter os sinalizadores no mínimo.

@MatthiasKunnen Se esse fosse um pequeno recurso de caso extremo, eu concordaria que essa é uma razão viável para não adicionar um sinalizador para isso. Mas o acesso direto ao array aparece no código com bastante frequência, e é tanto um buraco gritante no sistema de tipos quanto uma alteração importante para consertar, então acho que uma sinalização seria justificada.

Um sinalizador é a abordagem errada para isso, em parte por causa da combinação
problema. Devemos chamar isso de TypeScript v5 ... esse tipo de abordagem
transforma o número de combinações testáveis ​​de 2 ^ N para apenas N ...

No sábado, 15 de agosto de 2020 às 15:56, Michael Burkman [email protected]
escreveu:

@MatthiasKunnen https://github.com/MatthiasKunnen Se este fosse algum
pequeno recurso de caso extremo, então eu concordo que essa é uma razão viável para não
adicione uma bandeira para isso. Mas o acesso direto à matriz aparece no código bastante
frequentemente, e é um buraco gritante no sistema de tipos e também um
quebrar a mudança para consertar, então acho que uma bandeira seria justificada.

-
Você está recebendo isto porque está inscrito neste tópico.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/microsoft/TypeScript/issues/13778#issuecomment-674408067 ,
ou cancelar
https://github.com/notifications/unsubscribe-auth/AAADY5AXEY65S5HDGNGIPZDSA2O3FANCNFSM4C6KEKAA
.

Acaba de ocorrer um erro indefinido inesperado que derrubou meu aplicativo para centenas de usuários, um erro que teria sido detectado no momento da compilação se esse tipo de verificação estivesse em vigor. O TypeScript tem sido uma maneira incrível de fornecer software mais confiável, mas sua grande utilidade está sendo prejudicada por esta única omissão.

Que tal as próximas 10 pessoas que quiserem comentar aqui, apenas testando o rascunho de PR # 39560?

Isso é muito irritante se você quiser habilitar a regra @typescript-eslint/no-unnecessary-condition do ESLint porque ele reclama sobre todas as instâncias de

if (some_array[i] === undefined) {

Ele pensa que é uma condição desnecessária (porque Typescript diz que é!), Mas não é. Eu realmente não quero ter que adicionar // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition toda a minha base de código.

Se consertar isso adequadamente é muito trabalhoso para programadores de Javascript preguiçosos, talvez pudéssemos ter uma sintaxe de acesso de array alternativa que adicionaria undefined , por exemplo

if (some_array[?i] === undefined) {

(sugestão de sintaxe aleatória; alternativas são bem-vindas)

@Timmmm leia o comentário logo acima do que você fez. Há uma compilação que você pode experimentar que implementa uma versão desta sugestão - você poderia nos informar lá se isso resolver seu problema de eslint .

@ the0rted Eu li esse comentário. Não implementa uma versão da minha sugestão. Talvez você deva apenas ler novamente.

Desculpe, quando eu disse "esta" sugestão, quis dizer a capacidade do OP, ou seja, tratar array_of_T[i] como T | undefined . Eu vejo que você está perguntando sobre uma sintaxe para marcar um operador de índice específico como "talvez indefinido", mas se você usar a implementação no PR de Ryan, você não precisaria disso, porque todos os operadores de índice seriam "talvez indefinidos" . Isso não atenderia às suas necessidades?

Sim, seria - se / quando ele pousar, com certeza vou usá-lo. Mas tive a impressão, a partir desta discussão, que muitas pessoas resistiam a isso porque ele adiciona um sinalizador extra e torna o código que parece que deveria funcionar mais complicado (o exemplo simples de loop).

Então, eu queria oferecer uma sugestão alternativa, mas aparentemente as pessoas odeiam. : - /

Agradecemos todas as contribuições bem-intencionadas, não me leve a mal, @Timmmm. Acho que os votos negativos apenas transmitem que já é um terreno bem trilhado neste ponto.

Para mim, adicionar um sinalizador que melhora a segurança de tipo em toda a linha tem um custo muito mais baixo do que introduzir uma nova sintaxe opcional.

Não mencionei isso acima, mas já existe uma sintaxe para substituições únicas: if ((some_array[i] as MyType | undefined) === undefined) . Não é tão conciso quanto uma nova abreviação, mas espero que não seja uma construção que você precise usar com frequência.

_Originalmente postado por @osyrisrblx em https://github.com/microsoft/TypeScript/issues/40435#issuecomment -690017567_

Embora pareça uma opção muito mais segura, seria bom cancelar esse comportamento com uma sintaxe separada para as raras ocasiões em que você tem 100% de certeza de que está sempre definido.

Talvez !: pudesse afirmar sempre definido?

interface X {
    [index: string]!: number; // -> number
}

interface Y {
    [index: string]: number; // -> number | undefined
}

Essa sugestão descaracteriza o problema. O problema não está no entendimento do TypeScript do tipo em si. O problema vem do _acesso_ aos dados, que é onde a nova sintaxe já foi proposta anteriormente neste tópico.

Você pode tentar isso no TypeScript 4.1 beta, que sairá em breve ™ (ou hoje à noite, se precisar agora! 🙂)

Desculpe se isso foi oferecido antes, mas {[index: string]?: number} // -> number | undefined seria compatível? É consistente com a sintaxe de propriedades opcionais da interface - exigida por padrão, possivelmente indefinida com "?".

A opção de compilador na versão 4.1 é legal, mas ter um controle mais granular também seria bom.

Se você quiser experimentar em seu repo (demorei um pouco para descobrir):

  1. Instale o typescript no próximo yarn (add|upgrade) typescript@next
  2. Adicionar sinalizador (para mim em tsconfig.json) "noUncheckedIndexedAccess": true

No processo de ativação dessa regra em meu projeto, encontrei este erro interessante:

type MyRecord = { a: number; b: string };

declare const myRecord: MyRecord;

declare const key: 'a' | 'b';
const value = myRecord[key]; // string | number ✅

// ❌ Unexpected error
// Type 'MyRecord[Key] | undefined' is not assignable to type 'MyRecord[Key]'
const fn = <Key extends keyof MyRecord>(key: Key): MyRecord[Key] => myRecord[key];

Nesse caso, eu não esperava que myRecord[key] retornasse o tipo MyRecord[Key] | undefined , porque key está restrito a keyof MyRecord .

Atualização : problema arquivado https://github.com/microsoft/TypeScript/issues/40666

Isso provavelmente é um bug / descuido!

No processo de ativação dessa regra em meu projeto, encontrei este erro interessante:

type MyRecord = { a: number; b: string };

declare const myRecord: MyRecord;

declare const key: 'a' | 'b';
const value = myRecord[key]; // string | number ✅

// ❌ Unexpected error
// Type 'MyRecord[Key] | undefined' is not assignable to type 'MyRecord[Key]'
const fn = <Key extends keyof MyRecord>(key: Key): MyRecord[Key] => myRecord[key];

Nesse caso, eu não esperava que myRecord[key] retornasse o tipo MyRecord[Key] | undefined , porque key está restrito a keyof MyRecord .

Eu diria que é um bug. Basicamente, se keyof Type inclui apenas tipos literais de string / número (ao contrário de realmente incluir string ou number ), então Type[Key] onde Key extends keyof Type não deve incluir undefined , eu acho.

cross-linking to # 13195, que também analisa as diferenças e semelhanças entre "não há nenhuma propriedade aqui" e "há uma propriedade aqui, mas é undefined "

Rastreando alguns problemas relacionados às limitações de design aqui

  • # 41612

Para sua informação: Eu peguei dois bugs na minha base de código graças a este sinalizador, muito obrigado!
É um pouco irritante que verificar arr.length > 0 não seja suficiente para proteger arr[0] , mas isso é um pequeno inconveniente (posso reescrever o cheque para deixar o tsc feliz) em comparação com a segurança extra, IMO.

@rubenlg Eu concordo com arr.length . Por apenas verificar o primeiro elemento, reescrevi nosso código como

const firstEl = arr[0];
if (firstEl !== undefined) {
  ...
}

Mas existem alguns lugares onde fazemos if (arr.length > 2) ou mais, que são um pouco estranhos. No entanto, não acho que a verificação .length seria totalmente segura para o tipo, já que você pode apenas modificá-la:

const a: number[] = [];

a.length = 1;

if (a.length > 0) {
    const b: number = a[0];
    console.log(b);
}

Imprime undefined .

No entanto, não acho que a verificação .length seria totalmente segura para o tipo, já que você pode apenas modificá-la:

const a: number[] = [];

a.length = 1;

if (a.length > 0) {
    const b: number = a[0];
    console.log(b);
}

Imprime undefined .

Isso seria essencialmente um array esparso, que está fora do escopo para esse tipo de coisa. Sempre há coisas fora do padrão ou sorrateiras que podem ser feitas para contornar o verificador de tipo.

Sempre há uma maneira de invalidar a suposição anterior, e esse é o problema. Analisar todos os caminhos de execução possíveis seria muito caro.

const a: number[] = [1]
if (a.length > 0) {
    a.pop();
    console.log(a[0])
}
Esta página foi útil?
0 / 5 - 0 avaliações