Typescript: Sugerencia: opción para incluir indefinido en firmas de índice

Creado en 31 ene. 2017  ·  242Comentarios  ·  Fuente: microsoft/TypeScript

Actualización : para ver mi última propuesta, consulte el comentario https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -406316164

Con strictNullChecks habilitado, TypeScript no incluye undefined en firmas de índice (por ejemplo, en un objeto o matriz). Esta es una advertencia muy conocido y discutido en varias cuestiones, a saber https://github.com/Microsoft/TypeScript/issues/9235 , https://github.com/Microsoft/TypeScript/issues/13161 , https: // github .com / Microsoft / TypeScript / issues / 12287 y https://github.com/Microsoft/TypeScript/pull/7140#issuecomment -192606629.

Ejemplo:

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

Sin embargo, al leer los problemas anteriores, parece que muchos usuarios de TypeScript desearían que este no fuera el caso. Por supuesto, si las firmas de índice incluían undefined , es probable que el código requiera mucha más protección, pero, para algunos, esta es una compensación aceptable para una mayor seguridad de tipos.

Ejemplo de firmas de índice que incluyen undefined :

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

Me gustaría saber si este comportamiento podría considerarse como una opción de compilador adicional además de strictNullChecks . De esta manera, podemos satisfacer a todos los grupos de usuarios: aquellos que desean verificaciones nulas estrictas con o sin indefinido en sus firmas de índice.

Committed Suggestion

Comentario más útil

Seguimos siendo bastante escépticos de que alguien pueda beneficiarse de esta bandera en la práctica. Los mapas y las cosas similares a mapas ya pueden optar por | undefined en sus sitios de definición

¿No es uno de los objetivos de TypeScript permitir que los errores se detecten en el momento de la "compilación" en lugar de depender de que el usuario recuerde / sepa hacer algo específico? Esto parece ir en contra de ese objetivo; requiriendo que el usuario haga algo para evitar bloqueos. Lo mismo podría decirse de muchas otras funciones; no son necesarios si el desarrollador siempre hace x. El objetivo de TypeScript es (presumiblemente) facilitar el trabajo y eliminar estas cosas.

Encontré este error porque estaba habilitando strictNullChecks en el código existente y ya tenía una comparación, así que recibí el error. Si hubiera estado escribiendo un código nuevo, probablemente no me habría dado cuenta del problema aquí (el sistema de tipos me decía que siempre obtengo un valor) y terminé con una falla en el tiempo de ejecución. Confiar en que los desarrolladores de TS recuerden (o peor aún, incluso sepan) que se supone que deben declarar todos sus mapas con | undefined parece que TypeScript no está haciendo lo que la gente realmente quiere.

Todos 242 comentarios

Con la excepción de strictNullChecks, no tenemos indicadores que cambien el comportamiento del sistema de tipos. las banderas normalmente habilitan / deshabilitan la notificación de errores.
siempre puede tener una versión personalizada de la biblioteca que defina todos los indexadores con | undefined . debería funcionar como se esperaba.

@mhegazy Esa es una idea interesante. ¿Alguna guía sobre cómo anular las firmas de tipo para matriz / objeto?

Hay interface Array<T> en lib.d.ts . Busqué por la expresión regular \[\w+: (string|number)\] para encontrar también otras firmas de indexación.

Interesante, así que probé esto:

{
    // 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 :-(
}

¿Algunas ideas?

copie lib.d.ts localmente, diga lib.strict.d.ts , cambie la firma del índice a [n: number]: T | undefined; , incluya el archivo en su compilación. debería ver el efecto deseado.

Genial, gracias por eso.

El problema con la solución sugerida aquí es que requiere bifurcar y mantener un archivo lib separado.

Me pregunto si esta característica se exige lo suficiente como para garantizar algún tipo de opción lista para usar.

En una nota al margen, es interesante que la firma de tipo para el método get en las colecciones ES6 ( Map / Set ) devuelve T | undefined cuando Array firmas de índice Object no lo hacen.

esta es una decisión consciente. Sería muy molesto que este código fuera un error:

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

y no sería razonable pedir a todos los usuarios que usen ! . o escribir

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

Para el mapa, este no es el caso en general.

De manera similar, para sus tipos, puede especificar | undefined en todas sus firmas de índice y obtendrá el comportamiento esperado. pero por Array no es razonable. puede bifurcar la biblioteca y realizar los cambios que necesite, pero no tenemos planes de cambiar la declaración en la biblioteca estándar en este momento.

No creo que agregar una bandera para cambiar la forma de una declaración sea algo que haríamos.

@mhegazy, pero para matrices con agujeros a[i] posiblemente no esté definido:

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

La salida es:

undefined
undefined
undefined
undefined
0

Seguimos siendo bastante escépticos de que alguien pueda beneficiarse de esta bandera en la práctica. Los mapas y las cosas similares a mapas ya pueden optar por | undefined en sus sitios de definición, y hacer cumplir un comportamiento similar al EULA en el acceso a la matriz no parece una victoria. Es probable que necesitemos mejorar sustancialmente el CFA y los protectores de tipos para que esto sea agradable.

Si alguien quiere modificar sus lib.d.ts y corregir todas las interrupciones posteriores en su propio código y mostrar cómo se ve la diferencia general para mostrar que esto tiene alguna propuesta de valor, estamos abiertos a esa información. Alternativamente, si mucha gente está realmente emocionada de usar postfix ! más pero aún no tiene amplias oportunidades para hacerlo, esta bandera sería una opción.

Seguimos siendo bastante escépticos de que alguien pueda beneficiarse de esta bandera en la práctica. Los mapas y las cosas similares a mapas ya pueden optar por | undefined en sus sitios de definición

¿No es uno de los objetivos de TypeScript permitir que los errores se detecten en el momento de la "compilación" en lugar de depender de que el usuario recuerde / sepa hacer algo específico? Esto parece ir en contra de ese objetivo; requiriendo que el usuario haga algo para evitar bloqueos. Lo mismo podría decirse de muchas otras funciones; no son necesarios si el desarrollador siempre hace x. El objetivo de TypeScript es (presumiblemente) facilitar el trabajo y eliminar estas cosas.

Encontré este error porque estaba habilitando strictNullChecks en el código existente y ya tenía una comparación, así que recibí el error. Si hubiera estado escribiendo un código nuevo, probablemente no me habría dado cuenta del problema aquí (el sistema de tipos me decía que siempre obtengo un valor) y terminé con una falla en el tiempo de ejecución. Confiar en que los desarrolladores de TS recuerden (o peor aún, incluso sepan) que se supone que deben declarar todos sus mapas con | undefined parece que TypeScript no está haciendo lo que la gente realmente quiere.

Seguimos siendo bastante escépticos de que alguien pueda beneficiarse de esta bandera en la práctica. Mapas y cosas similares a mapas ya pueden optar por | indefinido en sus sitios de definición
¿No es uno de los objetivos de TypeScript permitir que los errores se detecten en el momento de la "compilación" en lugar de depender de que el usuario recuerde / sepa hacer algo específico?

En realidad, el objetivo es:

1) Identifique estáticamente los constructos que probablemente sean errores.

Lo que se está discutiendo aquí es la probabilidad de un error (bajo en opinión del equipo de TypeScript) y la usabilidad productiva común del lenguaje. Algunos de los primeros cambios a CFA han sido ser menos alarmistas o mejorar el análisis de CFA para determinar estas cosas de manera más inteligente.

Creo que la pregunta del equipo de TypeScript es que, en lugar de argumentar que es estrictamente correcto, proporcionar ejemplos de dónde este tipo de rigor, en el uso común, identificaría realmente un error del que se debe evitar.

Entré en el razonamiento un poco más en este comentario https://github.com/Microsoft/TypeScript/issues/11238#issuecomment -250562397

Piense en los dos tipos de claves en el mundo: Aquellas que sabe que tienen una propiedad correspondiente en algún objeto (seguro), aquellas que no sabe que tienen una propiedad correspondiente en algún objeto (peligroso).

Obtiene el primer tipo de clave, una clave "segura", escribiendo código correcto como

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

o

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

Obtiene el segundo tipo de clave, el tipo "peligroso", de cosas como entradas de usuario, o archivos JSON aleatorios del disco, o alguna lista de claves que pueden estar presentes pero no lo están.

Entonces, si tiene una clave del tipo peligroso y la indexa, sería bueno tener | undefined aquí. Pero la propuesta no es "Tratar las llaves todas las llaves, incluso las seguras, como peligrosas". Y una vez que comienzas a tratar las llaves seguras como peligrosas, la vida realmente apesta. Escribes código como

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

y TypeScript se está quejando de que arr[i] podría ser undefined a pesar de que mira, yo solo @ #% # lo probé . Ahora tienes el hábito de escribir código como este, y se siente estúpido:

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

O tal vez escriba un código como este:

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

y TypeScript dice "Oye, esa expresión de índice podría ser | undefined , por lo que debes solucionarlo debidamente porque ya has visto este error 800 veces:

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
  }
}

Pero no solucionaste el error . Querías escribir Object.keys(yourObj) , o tal vez myObj[k] . Ese es el peor tipo de error del compilador, porque en realidad no lo está ayudando en ningún escenario, solo está aplicando el mismo ritual a cada tipo de expresión, sin tener en cuenta si es o no más peligroso que cualquier otra expresión de la misma forma.

Pienso en el antiguo "¿Está seguro de que desea eliminar este archivo?" diálogo. Si ese cuadro de diálogo apareciera cada vez que intentara eliminar un archivo, aprendería muy rápidamente a presionar del y cuando solía presionar del , y sus posibilidades de no eliminar algo importante se restablecerían a la configuración previa. -Línea de base de diálogo. Si, en cambio, el cuadro de diálogo solo apareció cuando estaba eliminando archivos cuando no iban a la papelera de reciclaje, ahora tiene una seguridad significativa. Pero no tenemos idea (ni podríamos ) si sus claves de objeto son seguras o no, por lo que muestra el mensaje "¿Está seguro de que desea indexar ese objeto?" cada vez que lo hace, no es probable que encuentre errores a un ritmo mejor que no mostrarlo todo.

Identifique estáticamente los constructos que probablemente sean errores.

Quizás esto deba enmendarse para decir "Identificar estáticamente los constructos que tienen más probabilidades que otros de ser errores". : guiño : "Usé * cuando quería usar / , ¿puedes usar make * como advertencia?"

Entiendo, pero el índice fuera de rango es un problema real y común; obligar a las personas a enumerar matrices de una manera que no puedan hacer esto no sería algo malo.

La solución con ! mí también me disgusta realmente: ¿qué pasa si alguien viene y hace un cambio tal que la suposición ahora no es válida? Ha vuelto al punto de partida (una posible falla en tiempo de ejecución para algo que el compilador debería detectar). Debe haber formas seguras de enumerar matrices que no dependan de mentir sobre los tipos o de usar ! (por ejemplo, ¿no puede hacer algo como array.forEach(i => console.log(i.name) ?).

Ya ha reducido los tipos según el código, por lo que, en teoría, ¿no podría detectar patrones seguros para reducir el tipo para eliminar | undefined en esos casos, dando lo mejor de ambos mundos? Yo diría que si no puede transmitir fácilmente al compilador que no está accediendo a un elemento válido, entonces tal vez su garantía no sea válida o podría romperse accidentalmente en el futuro.

Dicho esto, solo uso TS en un proyecto y, en última instancia, se migrará a Dart, por lo que es poco probable que suponga una diferencia real para mí. Me entristece que la calidad general del software sea mala y que exista la oportunidad de ayudar a eliminar errores aquí que aparentemente se están ignorando por conveniencia. Estoy seguro de que el sistema de tipos podría hacerse sólido y las molestias comunes se abordarán de una manera que no introduzca estos agujeros.

De todos modos, son solo mis dos centavos ... No quiero alargar esto, estoy seguro de que entiendes de dónde venimos y estás mucho mejor ubicado para tomar decisiones que yo :-)

Creo que hay algunas cosas a considerar. Hay muchos patrones para iterar sobre matrices de uso común que dan cuenta de la cantidad de elementos. Si bien es un patrón posible acceder de forma aleatoria a índices en matrices, en la naturaleza es un patrón muy poco común y no es probable que sea un error estático. Si bien existen formas modernas de iterar, la más común sería algo como:

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

Si asume que las matrices de repuesto son poco comunes (lo son), es de poca ayuda que value sea | undefined . Si hay un patrón común, en la naturaleza, donde esto es arriesgado (y probablemente un error), entonces creo que TypeScript escucharía para considerar esto, pero los patrones que son de uso general, teniendo que volver a contrarrestar todos los valores de un El acceso al índice no está definido es claramente algo que afecta la productividad y, como se señaló, se puede optar por si se encuentra en una situación en la que es potencialmente útil.

Creo que ha habido una conversación antes sobre la mejora de CFA para que haya una manera de expresar la co-dependencia de los valores (por ejemplo, Array.prototype.length se relaciona con el valor del índice) para que cosas como el índice fuera de límites puedan analizarse estáticamente. Obviamente, ese es un trabajo importante, elaborado con todo tipo de casos extremos y consideraciones que no me gustaría comprender (aunque es probable que Anders se despierte con un sudor frío por algunas cosas como esta).

Entonces se convierte en una compensación ... Sin mejoras de CFA, complique el 90% del código con pistas falsas para detectar potencialmente un 10% de código incorrecto . De lo contrario, está invirtiendo en importantes mejoras de CFA, que podrían tener sus propias consecuencias de estabilidad y problemas en contra, encontrando lo que sería un código inseguro.

TypeScript no puede hacer mucho para salvarnos de nosotros mismos.

Todo este enfoque está en las matrices y estoy de acuerdo en que es menos probable que haya un problema allí, pero la mayoría de los problemas originales planteados (como el mío) fueron sobre mapas en los que no creo que el caso común sean las claves siempre existentes.

Todo este enfoque está en las matrices y estoy de acuerdo en que es menos probable que haya un problema allí, pero la mayoría de los problemas originales planteados (como el mío) fueron sobre mapas en los que no creo que el caso común sean las claves siempre existentes.

Si este es su tipo, agregue | undefined a la firma del índice. Ya es un error indexar en un tipo sin firma de índice debajo de --noImplicitAny .
ES6 Map ya está definido con get como get(key: K): V | undefined; .

reescribí todas las definiciones de matrices y mapas para hacer firmas de índice que devuelven | undefined , nunca me arrepiento desde eso, encontré algunos errores, no causa ninguna molestia porque trabajo con matrices indirectamente a través de una biblioteca hecha a mano que mantiene los controles por indefinido o ! dentro de él

Sería genial si TypeScript pudiera controlar el flujo de las comprobaciones como lo hace C # (para eliminar las comprobaciones de rango de índice para ahorrar algo de tiempo de procesador), por ejemplo:

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 aquellos que usan matrices dispersas, mátense con fuego ardiente)

en cuanto a Object.keys , se necesita un tipo especial, digamos allkeysof T para permitir que el análisis de flujo de control haga reducciones seguras

Creo que esta sería una buena opción, porque en este momento básicamente estamos mintiendo sobre el tipo de operación de indexación, y puede ser fácil olvidar agregar | undefined a mis tipos de objetos. Creo que agregar ! en los casos en los que sabemos que queremos ignorar undefined s sería una buena forma de lidiar con las operaciones de indexación cuando esta opción está habilitada.

Hay (al menos) otros dos problemas al poner |undefined en las definiciones de tipo de objeto:

  • significa que puede asignar indefinido en estos objetos, lo que definitivamente no está destinado
  • otras operaciones, como Object.values (o _.values ) requerirán que maneje undefined en los resultados

tslint informa una advertencia de falso positivo de condición constante, porque el mecanografiado devuelve información de tipo incorrecta (= falta | undefined ).
https://github.com/palantir/tslint/issues/2944

Uno de los errores que se omiten regularmente con la ausencia de | undefined en la indexación de la matriz es este patrón cuando se usa en 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.

Definitivamente usaría esta bandera. Sí, será molesto trabajar con los viejos bucles for , pero tenemos el operador ! para decirle al compilador cuando sabemos que está definido:

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

Además, este problema no es un problema con los bucles for of más nuevos y mucho mejores e incluso hay una regla TSLint prefer-for-of que le dice que no use bucles for estilo antiguo ya no.

Actualmente siento que el sistema de tipos es inconsistente para el desarrollador. array.pop() requiere un cheque if o una afirmación ! , pero el acceso a través de [array.length - 1] no lo requiere. ES6 map.get() requiere un cheque if o una aserción ! , pero un hash de objeto no lo hace. El ejemplo de @sompylasar también es bueno.

Otro ejemplo es la desestructuración:

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

Hubiera preferido que el compilador me obligara a hacer esto:

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')

Estos son errores reales que he visto que podrían haber sido prevenidos por el compilador.

Imo, esto no debería pertenecer a los archivos de mecanografía, sino que debería ser una mecánica del sistema de tipos: al acceder a cualquier cosa con una firma de índice, puede ser undefined . Si su lógica aseguró que no es así, simplemente use ! . De lo contrario, agregue if y estará bien.

Creo que mucha gente preferiría que el compilador fuera estricto con algunas afirmaciones necesarias que estar suelto con errores no detectados.

Realmente me gustaría ver esta bandera agregada. En la base de código de mi empresa, el acceso aleatorio a matrices es la rara excepción y los bucles for son olores de código que normalmente querríamos reescribir con funciones de orden superior.

@pelotom, ¿cuál es tu preocupación entonces (ya que parece que la mayoría de las

@ aleksey-bykov principalmente firmas de índices de objetos, que se encuentran ampliamente en bibliotecas de terceros. Me gustaría acceder a una propiedad en { [k: string]: A } para advertirme que el resultado posiblemente no esté definido. Solo mencioné la indexación de matrices porque se planteó como un caso de por qué la bandera sería demasiado molesta para trabajar.

¿Sabes que puedes reescribirlos exactamente como quieras? (dado un poco de trabajo extra)

Sí, podría reescribir las mecanografías de todos para ellos, o podría activar un indicador de compilador 😜

sigue jugando al capitán O ...: puedes reescribir tu lib.d.ts hoy y ser un feliz propietario de más código base de sonido o puedes esperar la bandera durante los próximos N años

@ aleksey-bykov ¿cómo se puede hacer reescribiendo lib.d.ts ?

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

luego, en la definición Array en lib.es2015.core.d.ts , reemplace

[n: number]: T;

con

[n: number]: T | undefined;

@ aleksey-bykov tal vez te perdiste la parte en la que dije que no me importan las matrices. Me importa dónde las bibliotecas de terceros han declarado que algo es del tipo { [k: string]: T } , y quiero acceder a dicho objeto para devolver algo posiblemente indefinido. No hay forma de lograrlo simplemente editando lib.d.ts ; requiere cambiar las firmas de la biblioteca en cuestión.

¿Tiene control sobre los archivos de definición de terceros? si es así puedes arreglarlos

Y ahora volvemos a

Sí, podría reescribir las mecanografías de todos para ellos, o podría activar un indicador de compilador 😜

El tiempo es un círculo plano.

no seas tonto, no usas "mecanografía de todos", ¿verdad? es literalmente un día de trabajo máximo para un proyecto típico, ya lo he hecho

Sí, tengo que editar las mecanografías de los demás todo el tiempo y me gustaría hacerlo menos.

y lo harás en N años, tal vez, por ahora puedes sufrir o hombre

Gracias por su aportación increíblemente constructiva 👍

La entrada constructiva para esto es la siguiente, este problema debe cerrarse, porque:
una. o la decisión sobre si [x] puede ser undefined o no queda en manos de los desarrolladores

  • Dejándolos tenerlo todo en la cabeza como siempre lo hacían antes.
  • o alterando lib.d.ts y 3rd-party.d.ts como se sugirió

B. o se necesitan sintaxis / tipos / análisis de flujo / N años especiales para mitigar algo que se puede hacer fácilmente con las manos en #a

El problema es una propuesta para (b), excepto que no se propone una nueva sintaxis, es solo una bandera del compilador.

Todo se reduce a que el tipo { [x: string]: {} } casi siempre es una mentira; salvo el uso de Proxy , no hay ningún objeto que pueda tener un número infinito de propiedades, mucho menos todas las cadenas posibles. La propuesta es tener una bandera de compilador que reconozca esto. Puede ser que sea demasiado difícil implementar esto por lo que se gana; Dejaré esa llamada a los implementadores.

el punto es que ni

  • T | undefined
  • ni T

es adecuado para el caso general

Para que sea correcto para el caso general , debe codificar la información sobre la prerrensión de los valores en los tipos de sus contenedores, lo que requiere un sistema de tipos dependiente ... que en sí mismo no es algo malo :) pero podría ser tan complejo como todos los sistemas de tipografía mecanografiados actuales realizados hasta el día de hoy, por el simple hecho de ... ¿ahorrarle algunas ediciones?

T | undefined es correcto para el caso general, por las razones que acabo de dar. Voy a ignorar tus divagaciones sin sentido sobre los tipos dependientes, que tengas un buen día.

puedes ignorarme tanto como quieras, pero T | undefined es un exceso de

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

Preferiría tener T | undefined allí de forma predeterminada y decirle al compilador que index es un rango de índice numérico de items lo que no sale si los límites cuando se aplican a items ; en los casos simples, como un conjunto de formas de bucle for / while de uso frecuente, el compilador podría inferir eso automáticamente; en casos complejos, lo siento, puede haber undefined s. Y sí, los tipos basados ​​en valores encajarían bien aquí; Los tipos de cadenas literales son tan útiles, ¿por qué no tener tipos literales booleanos y numéricos y de rango / conjunto de rangos? En lo que respecta a TypeScript, intenta cubrir todo lo que se puede expresar con JavaScript (a diferencia de, por ejemplo, Elm que lo limita).

es literalmente un día de trabajo máximo para un proyecto típico, ya lo he hecho

@ aleksey-bykov, ¿cuál fue tu experiencia después de ese cambio? ¿Con qué frecuencia tienes que usar ! ? y ¿con qué frecuencia encuentra el compilador marcando errores reales?

@mhegazy, honestamente, no noté mucha diferencia al pasar de T a T | undefined , tampoco encontré ningún error, supongo que mi problema es que trabajo con matrices a través de funciones de utilidad que mantienen ! en ellos, por lo que literalmente no hubo efecto para el código externo:

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

¿En qué archivo lib puedo encontrar la definición del tipo de índice para los objetos? He localizado y actualizado Array de [n: number]: T a [n: number]: T | undefined . Ahora me gustaría hacer lo mismo con los objetos.

no hay una interfaz estándar (como Array para matrices) para objetos con la firma de índice, debe buscar definiciones exactas para cada caso en su código y corregirlas

debe buscar definiciones exactas para cada caso en su código y corregirlas

¿Qué tal una búsqueda de clave directa? P.ej

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

¿Hay alguna forma de hacer cumplir T | undefined lugar de T aquí? Actualmente utilizo estos ayudantes en mi base de código en todas partes, como alternativas seguras de tipos para las búsquedas de índices en matrices y 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 Mientras escribo esto, estoy solucionando un error en producción en https://unsplash.com que podría haberse detectado con tipos de firmas de índice más estrictas.

Ya veo, considere el 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

Si una bandera como --strictArrayIndex no es una opción porque las banderas no están diseñadas para cambiar los archivos lib.d.ts . Tal vez ustedes puedan lanzar versiones estrictas de archivos lib.d.ts como " lib": ['strict-es6'] ?

Podría contener múltiples mejoras, no solo un estricto índice de matriz. Por ejemplo, Object.keys :

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

Podría ser:

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

Actualización de la SBS de hoy: Nos gritamos durante 30 minutos y no pasó nada. 🤷‍♂️

@RyanCavanaugh ¿Qué es un SBS, por curiosidad?

@radix "

eso es intrigante, porque la solución es obvia:
tanto T como T | undefined son incorrectos, la única forma correcta es hacer que la variable de índice sea consciente de la capacidad de su contenedor, ya sea eligiéndolo de un conjunto o encerrándolo en un rango numérico conocido

@RyanCavanaugh he estado pensando en esto, parece que el siguiente truco cubriría el 87% de todos los casos:

  • values[index] da T si el índice se declara en for (HERE; ...)
  • values[somethingElse] da T | undefined para todas las variables declaradas fuera de for

@ aleksey-bykov discutimos algo aún más inteligente: que podría haber un mecanismo de protección de tipo real para " arr[index] fue probado por index < arr.length . Pero eso no ayudaría en el caso en el que pop 'd en el medio de un ciclo, o pasó su matriz a una función que eliminó elementos de ella. Realmente no parece que la gente esté buscando un mecanismo que evite errores OOB el 82,9% de las veces, después de todo , ya es cierto que aproximadamente esa fracción del código de indexación de matrices es correcta de todos modos. Agregar ceremonia al código correcto sin detectar errores en los casos incorrectos es un mal resultado.

para no dar a entender que Aleksey alguna vez mutaría una matriz

Recientemente porté una aplicación de Elm a Typescript y las operaciones de indexación que se escribieron incorrectamente es probablemente una de las mayores fuentes de errores con las que me he encontrado, con todas las configuraciones más estrictas de TS habilitadas (también cosas como this sin vincular ).

  • no puedes rastrear mutaciones en el contenedor
  • no puedes rastrear manipulaciones de índice

con esto dicho, poco puede garantizar, si es así, ¿por qué intentaría si un caso de uso típico es una iteración tonta a través de todos los elementos dentro de < array.length

como dije normalmente

  • si hay un error, es probable que se origine en alguna parte de las cláusulas de la instrucción for : inicialización, incremento, condición de parada y, por lo tanto, no es algo que pueda verificar, porque requiere realizar una verificación de tipo completa
  • fuera de las cláusulas for no suele haber lugar para el error

por lo tanto, siempre que el índice se declare de alguna manera (nada que podamos decir al respecto con confianza) es razonablemente justo creer que el índice es correcto dentro del cuerpo del bucle

Pero eso no ayudaría en el caso en el que apareciera en el medio un bucle o pasara su matriz a una función que eliminó elementos de ella

Este parece un argumento realmente débil en contra de esto. Esos casos ya están resueltos; usarlos como excusa para no arreglar otros casos no tiene sentido. Nadie aquí pide que ninguno de esos casos se maneje correctamente ni que sea 100% correcto. Solo queremos tipos precisos en un caso realmente común. Hay muchos casos en TypeScript que no se manejan perfectamente; si está haciendo algo extraño, los tipos pueden estar equivocados. En este caso, no estamos haciendo nada extraño y los tipos son incorrectos.

Agregar ceremonia al código correcto

Tengo curiosidad por ver un ejemplo de código donde esto es "agregar ceremonia al código correcto". Según tengo entendido, un bucle for básico sobre una matriz es fácil de detectar / estrechar. ¿Cuál es el código del mundo real que no es un bucle for simple donde esto se vuelve un inconveniente que no es potencialmente un error? (ya sea ahora, o podría ser de un cambio trivial en el futuro). No digo que no haya ninguno; Simplemente no puedo imaginarlo y no he visto ningún ejemplo (he visto muchos ejemplos que usan bucles for, pero a menos que digas que no se pueden reducir, parecen irrelevantes).

sin atrapar insectos

Ha habido ejemplos tanto de código de trabajo que no se puede compilar debido a esto como de código que se lanza en tiempo de ejecución porque los tipos engañan al desarrollador. Decir que hay valor cero en apoyar esto es una tontería.

¿Por qué no podemos mantenerlo simple y no agregar _cualquier_ comportamiento mágico alrededor de los bucles for de estilo antiguo? Siempre puedes usar ! para hacer que las cosas funcionen. Si su base de código está llena de bucles for estilo antiguo, no use la bandera. Todas las bases de código modernas con las que he trabajado usan forEach o for of para iterar matrices y esas bases de código se beneficiarían de la seguridad de tipos adicional.

no estamos haciendo nada extraño y los tipos están mal.

Este párrafo, para mí, parece una buena razón para no tener esta función. Acceder a una matriz fuera de los límites es algo extraño; los tipos solo son "incorrectos" si estás haciendo algo poco común (OOBing). La gran mayoría de la lectura de código de una matriz lo hace dentro de los límites; sería "incorrecto" incluir undefined en esos casos por este argumento.

... código de trabajo que no se puede compilar

No tengo conocimiento de ninguno, ¿puede señalarlos específicamente?

¿Por qué no podemos simplemente mantenerlo simple y no agregar ningún comportamiento mágico alrededor de los bucles for de estilo antiguo? ¡Siempre puedes usar! para hacer que las cosas funcionen.

¿Cuál es la diferencia útil entre esto y una regla de TSLint que dice "Todas las expresiones de acceso a la matriz deben tener un ! final"?

@RyanCavanaugh mi suposición es que la regla TSLint no podría restringir tipos o usar análisis de tipo de flujo de control (por ejemplo, envolviendo el acceso en un if , lanzando una excepción, return ing o continue ing si no está configurado, etc.). Tampoco se trata solo de expresiones de acceso a matrices, también se trata de desestructuración. ¿Cómo se vería la implementación para el ejemplo en https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -336265143? Para hacer cumplir el ! , esencialmente tendría que crear una tabla para rastrear los tipos de estas variables como posiblemente undefined . Lo que para mí suena como algo que debería hacer el verificador de tipos, no un linter.

@RyanCavanaugh

Este párrafo, para mí, parece una buena razón para no tener esta función. Acceder a una matriz fuera de los límites es algo extraño; los tipos solo son "incorrectos" si estás haciendo algo poco común (OOBing).

... código de trabajo que no se puede compilar

No tengo conocimiento de ninguno, ¿puede señalarlos específicamente?

En mi caso, no era una matriz, pero se cerró como un engaño, así que supongo que se supone que este problema también los cubre. Consulte el n. ° 11186 para ver mi número original. Estaba analizando un archivo en un mapa y luego comparándolo con undefined . IIRC Recibí el error en la comparación de que no pueden estar indefinidos, aunque podrían (y lo estaban).

Siempre está permitido comparar algo con undefined

que es una pena

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

Siempre está permitido comparar algo con undefined

Ha pasado tanto tiempo que me temo que no recuerdo exactamente cuál fue el error. Definitivamente tuve un error para el código que funcionaba bien (sin strictNullChecks ) y me llevó a la prueba que resultó en el caso anterior.

Si tengo algo de tiempo, veré si puedo averiguar exactamente qué era de nuevo. Definitivamente estaba relacionado con esta escritura.

La mayor parte de esta discusión se ha centrado en matrices, pero, en mi experiencia, el acceso a objetos es donde esta característica sería más útil. Por ejemplo, este ejemplo (simplificado) de mi base de código parece razonable y se compila bien, pero definitivamente es un error:

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
}

Con respecto a los objetos y simplemente cambiando la firma para incluir | undefined , @types/node hace eso por process.env :

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

pero no permite limitar el tipo en absoluto:

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

da

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

@felixfbecker

Creo que está relacionado con este error: https://github.com/Microsoft/TypeScript/issues/17960

Puede solucionarlo asignando la var env a una variable y luego protegiéndolo:

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

La adición de | undefined en algunos tipos de nodos se realizó para permitir el aumento de esta interfaz por conveniencia con propiedades de uso frecuente para completar el código. ea

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

Sin el | undefined en la firma del índice, TSC se queja con Property 'foo' of type 'string | undefined' is not assignable to string index type 'string'. .

Sé que esto tiene un costo si tiene que trabajar con tipos que tienen | undefined adicionales y otros que no lo tienen.

Sería muy bueno si pudiéramos deshacernos de esto.

Estoy tratando de ordenar mis pensamientos sobre este tema.

Los objetos son una mezcla en JavaScript. Se pueden utilizar para dos propósitos :

  • diccionarios también conocidos como mapas, donde se desconocen las claves
  • registros, donde se conocen las claves

Para los objetos utilizados como registros, no es necesario utilizar firmas de índice. En cambio, las claves conocidas se definen en el tipo de interfaz. Las búsquedas de claves válidas devuelven el tipo correspondiente, las búsquedas de claves no válidas recurrirán a la firma del índice. Si no hay una firma de índice definida (que no debería haber para los objetos usados ​​como registros), y noImplicitAny está habilitado, se producirá el error deseado.

Para los objetos que se utilizan como diccionarios (también conocidos como mapas) y matrices, se utilizan firmas de índice y podemos elegir incluir | undefined en el tipo de valor. Por ejemplo, { [key: index]: string | undefined } . Todas las búsquedas de claves son válidas (porque las claves no se conocen en el momento de la compilación) y todas las claves devuelven el mismo tipo (en este ejemplo, T | undefined ).

Dado que las firmas de índice solo deben usarse para el patrón y las matrices de objetos de diccionario, se desea que TypeScript aplique | undefined en el tipo de valor de firma de índice: si las claves no se conocen, y las búsquedas de claves posiblemente devuelvan undefined .

Hay buenos ejemplos de errores que pueden parecer invisibles sin esto, como Array.prototype.find devuelven undefined , o búsquedas clave como array[0] devuelven undefined . (La desestructuración es solo una sintaxis de azúcar para búsquedas de claves). Es posible escribir funciones como getKey para corregir el tipo de retorno, pero tenemos que confiar en la disciplina para imponer el uso de estas funciones en una base de código.

Si lo entiendo correctamente, el problema se centra en la reflexión sobre los objetos y las matrices del diccionario, de modo que al mapear las claves de un objeto o matriz del diccionario, se sabe que las búsquedas de claves son válidas, es decir, no devolverán undefined . En este caso, no sería deseable que los tipos de valor incluyan undefined . Puede ser posible utilizar el análisis de flujo de control para solucionar este problema.

¿Es este el problema pendiente o lo entiendo mal?

¿Qué casos de uso y problemas no he mencionado?

Hay (al menos) otros dos problemas al poner | undefined en las definiciones de tipo de objeto:

  • significa que puede asignar indefinido en estos objetos, lo que definitivamente no está destinado
  • otras operaciones, como Object.values ​​(o _.values) requerirán que maneje undefined en los resultados

Creo que este es un punto muy importante.

Por el momento, estoy experimentando con el siguiente enfoque:

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

Luego acceda a propiedades como safelyAccessProperty(myObject, myKey) , en lugar de myObject[myKey] .

@plul Buena captura. La discusión se centra actualmente en operaciones de lectura, pero la definición del tipo de indexador es de hecho doble, y agregar | undefined permitiría escribir valores undefined .

La función safelyAccessProperty que está experimentando ( mencionada anteriormente como getKey por @OliverJAsh) requiere disciplina y / o una regla linter para prohibir las operaciones de indexación en todas las matrices y objetos.

Esto se puede hacer escalable si la función se proporciona en todas las instancias de matriz y objeto (cada tipo que proporciona operaciones de indexación), como en C ++ std::vector tiene .at() que arroja una excepción en tiempo de ejecución para el acceso OOB , y un operador [] sin marcar que, en el mejor de los casos, se bloquea con SEGFAULT en el acceso OOB, en el peor de los casos, corrompe la memoria.

Creo que el problema de acceso OOB no se puede resolver en TypeScript / JavaScript solo en el nivel de definición de tipo, y requiere soporte de lenguaje para restringir las operaciones de indexación potencialmente peligrosas si esta característica de rigor está habilitada.

La naturaleza doble del indexador podría modelarse como una propiedad con operaciones get y set como funciones, pero eso sería un cambio radical para todas las definiciones de tipos de indexador existentes.

Una idea que parece prometedora: ¿qué pasaría si pudiera usar ?: al definir una firma de índice para indicar que espera que falten valores a veces con un uso normal? Actuaría como | undefined mencionado anteriormente, pero sin las incómodas desventajas. Tendría que rechazar los valores explícitos de undefined , que supongo que es una diferencia de los ?: habituales.

Se vería así:

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'];

En comparación con el enfoque 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'];

Vine aquí por rechazar agregar | undefined en el DT PR anterior, ya que rompería a todos los usuarios existentes de esa API; ¿podría considerarse mejor que esto le permita al usuario elegir qué tan quisquilloso quiere ser? que la biblioteca?


Notaré que las propiedades opcionales también agregan | undefined , y eso me ha mordido varias veces; esencialmente, TS no distingue entre una propiedad faltante y una propiedad establecida como indefinida. Solo me gustaría que { foo?: T, bar?: T } se tratara de la misma manera que { [name: 'foo' | 'bar']: T } , de cualquier manera (consulte también los comentarios de process.env anteriores)


¿Está TS en contra de romper la simetría aquí en los indexadores de números y cadenas?

foo[bar] && foo[bar].baz() es un patrón JS muy común, se siente torpe cuando no es compatible con TS (en el sentido de recordarte que debes hacerlo si no agregas | undefined , y advierte cuando es obviamente no es necesario si lo hace).


Con respecto a las matrices mutantes durante la iteración que rompen la garantía de expresión de guardia, eso también es posible con los otros guardias:

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
        }
    }
}

pero supongo que es mucho menos probable en código real que los bucles antiguos muten el iteratee.

Está claro que existe una demanda para esta función. Realmente espero que el equipo de TS encuentre alguna solución. No solo agregando | undefined al indexador porque tiene sus propios problemas (ya mencionados) sino de una manera más "inteligente" (la lectura devuelve T|undefined , la escritura requiere T , buen compilador comprobando for bucle, etc., también se mencionó una buena propuesta).

Estamos bien con el error de tiempo de ejecución cuando mutamos y trabajamos con matrices de manera no trivial, difícil de verificar mediante el compilador. Solo queremos la verificación de errores en la mayoría de los casos y estamos bien con el uso de ! veces.

Dicho esto, si se implementara este manejo más estricto de las matrices, ahora con # 24897 sería posible implementar una reducción de tipo agradable al verificar la longitud de la matriz con constante. Podríamos limitar la matriz a la tupla con el 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?]
  }
}

Sería útil cuando indexa por constante o desestructura una matriz.

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'.",
}

Otra pregunta es qué haríamos con números grandes como:

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

No podemos mostrar el tipo de esta enorme tupla en el IDE o en los mensajes del compilador.

Supongo que podríamos tener alguna sintaxis para representar "tuplas de cierta longitud con el mismo tipo para todos los elementos". Entonces, por ejemplo, la tupla de 1000 cadenas es string[10000] y el tipo de matriz reducida del ejemplo anterior podría ser [...string[99999], ...string[]] .

La otra preocupación es si la infraestructura del compilador puede soportar tuplas tan grandes ahora, y si no, qué tan difícil sería hacerlo.

Objetos

Siempre quiero un tipo de índice de [key: string (or number, symbol)]: V | undefined , solo que a veces me olvido del caso undefined . Siempre que un desarrollador tiene que decirle explícitamente al compilador "confía en mí, este es el tipo de cosas de verdad", sabes que no es seguro.
Tiene muy poco sentido escribir Map.get correctamente (estrictamente) pero de alguna manera los objetos simples obtienen un pase libre.
Aún así, esto se puede arreglar fácilmente en la tierra de los usuarios, por lo que no es tan malo. No tengo una solución, de todos modos.

Matrices

Quizás me estoy perdiendo algo, pero parece que el argumento de que "casi nunca se accede a una matriz de forma insegura" puede ir en ambos sentidos, especialmente con una nueva bandera de compilador.

Tiendo a pensar que cada vez más personas siguen estas dos mejores prácticas:

  • Utilice bibliotecas o métodos nativos funcionales para iterar o transformar matrices. No hay acceso al soporte aquí.
  • No mute las matrices en su lugar

Con esto en mente, los únicos casos restantes y raros en los que necesita una lógica de acceso de soporte de bajo nivel realmente se beneficiarían de la seguridad de tipos.

Este parece una obviedad y no creo que copiar y pegar todo el lib.d.ts localmente sea una solución aceptable.

Cuando indexamos explícitamente en una matriz / objeto, por ejemplo, para obtener el primer elemento de una matriz ( array[0] ), queremos que el resultado incluya undefined .

Esto es posible agregando undefined a la firma del índice.

Sin embargo, si lo entiendo correctamente, el problema de incluir undefined en la firma del índice es que, al mapear la matriz u objeto, los valores incluirán undefined aunque se sabe que no be undefined (de lo contrario, no estaríamos mapeando sobre ellos).

Los tipos / firmas de índice se utilizan tanto para búsquedas de índice (por ejemplo, array[0] ) como para mapeo (por ejemplo, for bucles y Array.prototype.map ), pero requerimos tipos diferentes para cada uno de estos casos.

Esto no es un problema con Map porque Map.get es una función y, por lo tanto, su tipo de retorno puede estar separado del tipo de valor interno, a diferencia de la indexación en una matriz / objeto que no es una función, y por lo tanto usa la firma del índice directamente.

Entonces, la pregunta es: ¿cómo podemos satisfacer ambos 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! :-(
})

Propuesta

Una opción del compilador que trata las búsquedas de índices (por ejemplo, array[0] ) de manera similar a cómo se escriben Set.get y Map.get , al incluir undefined en el tipo de valor de índice (no la propia firma del índice). La firma de índice real en sí misma no incluiría undefined , por lo que las funciones de mapeo no se ven afectadas.

Ejemplo:

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 :-)
})

Sin embargo, esto no resolverá el caso de bucle sobre matrices / objetos usando un bucle for , ya que esa técnica usa búsquedas de índices.

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

Para mí, y sospecho que para muchos otros, esto es aceptable porque los bucles for no se utilizan, sino que prefieren usar un estilo funcional, por ejemplo, Array.prototype.map . Si tuviéramos que usarlos, estaríamos encantados de usar la opción del compilador sugerida aquí junto con los operadores de aserción no nulos.

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

También podríamos proporcionar una forma de optar por participar o no participar, por ejemplo, con alguna sintaxis para decorar la firma del índice (por favor, perdone la sintaxis ambigua que se me ocurrió para el ejemplo). Esta sintaxis solo sería una forma de señalar qué comportamiento queremos para las búsquedas de índices.

Exclusión voluntaria (la opción del compilador habilita de forma predeterminada, exclusión voluntaria cuando sea necesario):

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

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

Opt-in (sin opción de compilador, solo opt-in donde sea necesario):

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

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

No he leído sobre este problema o los pros y los contras, varias propuestas, etc. (lo acabo de encontrar en Google después de sorprenderme repetidamente de que el acceso a la matriz / objeto no se maneja de manera consistente con controles nulos estrictos), pero tengo un sugerencia relacionada: una opción para hacer que la inferencia de tipo de matriz sea lo más estricta posible a menos que se anule específicamente.

Por ejemplo:

const balls = [1, 2 ,3];

De forma predeterminada, balls trataría como [number, number, number] . Esto podría anularse escribiendo:

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

Además, el acceso a elementos de tupla se manejaría de forma coherente con estrictas comprobaciones nulas. Me sorprende que en el siguiente ejemplo n se infiera actualmente como number incluso con las comprobaciones nulas estrictas habilitadas.

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

También esperaría que los métodos de mutación de matriz como .push no existieran en la definición del tipo de tupla, ya que tales métodos cambian el tipo de tiempo de ejecución para que sea inconsistente con el tipo de tiempo de compilación.

¡Bonito! Bueno, ese es un momento interesante. Tenía abierto el anuncio de lanzamiento, pero aún no lo había leído; solo llegué aquí después de encontrarme con una situación en la que necesitaba hacer un casting extraño ( (<(T|undefined)[]> arr).slice(-1)[0] ) para hacer que TypeScript (2.9) hiciera lo que quería que hiciera.

Solo quería devolver las cosas a esta sugerencia: https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -383072468

Esto solucionaría los problemas con los tipos indexados que he experimentado. Sería genial si fuera el predeterminado, pero entiendo que eso rompería muchas cosas en el mundo real.

@mhegazy @RyanCavanaugh ¿ Alguna idea sobre mi propuesta? https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -406316164

Para mí, hay una solución simple. Habilite una bandera que verifique esto. Entonces, en lugar de:

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

Tú haces:

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

Luego, al realizar accesos aleatorios al índice, debe verificar si el resultado no está definido, pero no mientras se itera una colección enumerada.

Sigo pensando que esto justifica tener un problema abierto.

Como programador nuevo en TypeScript, encontré que la situación en torno a la indexación de objetos en modo estricto no es intuitiva. Hubiera esperado que el resultado de una búsqueda fuera T | undefined , de manera similar a Map.get . De todos modos, me encontré con esto recientemente y abrí un problema en una biblioteca:

screepers / screeps-mecanografiados # 107

Probablemente lo voy a cerrar ahora, porque parece que no hay una buena solución. Creo que voy a intentar "optar por participar" en T | undefined usando una pequeña función de utilidad:

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

Aquí hubo algunas sugerencias para establecer explícitamente T | undefined como el tipo de retorno de la operación de índice de objeto, sin embargo, eso no parece estar funcionando:

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

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

Esta es la versión 1.31.1 de VSCode

@yawaramin Asegúrese de tener strictNullChecks habilitado en su tsconfig.json (que también está habilitado por la bandera strict )

Si su caso de uso requiere indexación arbitraria en matrices de longitud desconocida, creo que vale la pena agregar el indefinido explícitamente (aunque sólo sea para "documentar" esa inseguridad).

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

Las tuplas funcionan para arreglos pequeños de longitudes conocidas. ¿Quizás podríamos tener algo como string[5] como abreviatura de [string, string, string, string, string] ?

Muy a favor de esto como opción. Es un agujero notable en el sistema de tipos, particularmente cuando la bandera strictNullChecks está habilitada. Los objetos simples se utilizan como mapas todo el tiempo en JS, por lo que TypeScript debería admitir ese caso de uso.

Me encontré con esto con la destrucción de matriz de un parámetro de función:

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

Aquí esperaría que a sea ​​del tipo string | undefined pero es solo string , a menos que yo lo haga

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

No creo que tengamos un solo bucle for en nuestra base de código, así que felizmente solo agregaría una bandera a las opciones del compilador de mi tsconfig (podría llamarse strictIndexSignatures ) para alternar este comportamiento para nuestro proyecto.

Así es como he estado solucionando este problema: https://github.com/danielnixon/total-functions/

Buena solución, realmente espero que el equipo de TypeScript lo solucione.

Cuando un programador hace suposiciones en el código escrito, y el compilador no puede deducir que está guardado, esto debería resultar en un error del compilador a menos que sea silenciado en mi humilde opinión.

Este comportamiento también sería muy útil en combinación con el nuevo operador de encadenamiento opcional.

Me encontré con un problema al usar | undefined con el mapa hoy mientras usaba Object.entries() .

Tengo un tipo de índice que se describe bastante bien por {[key: string]: string[]} , con la salvedad obvia de que no todas las claves de cadena posibles están representadas en el índice. Sin embargo, escribí un error que TS no detectó al intentar consumir un valor buscado en el Índice; no manejó el caso indefinido.

Así que lo cambié a {[key: string]: string[] | undefined} como se recomendó, pero ahora esto genera problemas con mi uso de Object.entries() . TypeScript ahora asume (razonablemente, basado en la especificación de tipo) que el Índice puede tener claves que tienen un valor de indefinido especificado, por lo que asume que el resultado de llamar a Object.entries() en él puede contener valores indefinidos.

Sin embargo, sé que esto es imposible; la única vez que debería obtener un resultado undefined es buscar una clave que no existe, y que no estaría en la lista al usar Object.entries() . Entonces, para hacer feliz a TypeScript, tengo que escribir código que no tenga una razón real para existir, o anular las advertencias, lo que preferiría no hacer.

@RyanCavanaugh , supongo que tu respuesta original a esto sigue siendo la posición actual del equipo de TS. Tanto como un obstáculo para eso porque nadie lee el hilo y verificando en caso de un par de años más de experiencia con primitivas de colección JS más poderosas, e introduciendo varias otras opciones para aumentar el rigor, ya que han cambiado algo.

(Los ejemplos allí todavía son algo poco convincentes para mí, pero este hilo ya ha hecho todos los argumentos, otro comentario no va a cambiar nada)

Si alguien quiere modificar sus lib.d.ts y corregir todas las interrupciones posteriores en su propio código y mostrar cómo se ve la diferencia general para mostrar que esto tiene alguna propuesta de valor, estamos abiertos a esa información.

@RyanCavanaugh Estos son algunos de los casos de mi base de código donde algunos bloqueos de tiempo de ejecución están dormidos. Tenga en cuenta que ya tuve casos en los que tuve fallas en la producción debido a esto y necesitaba lanzar una revisión.

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))

En este caso, sería molesto ya que ArticleIDs extends articleNames[] incluye undefined en los valores resultantes, mientras que solo debería permitir subconjuntos completamente definidos. Se puede arreglar fácilmente usando ReadonlyArray<articleNames> lugar 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> => {...}

En general, me gustaría tener este tipo de seguridad adicional para prevenir posibles bloqueos en tiempo de ejecución.

Entré en el razonamiento un poco más en este comentario # 11238 (comentario)

Piense en los dos tipos de claves en el mundo: Aquellas que sabe que _tienen_ una propiedad correspondiente en algún objeto (seguro), aquellas que _no_ sabe que tienen una propiedad correspondiente en algún objeto (peligroso).

Obtiene el primer tipo de clave, una clave "segura", escribiendo código correcto como

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

o

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

Obtiene el segundo tipo de clave, el tipo "peligroso", de cosas como entradas de usuario, o archivos JSON aleatorios del disco, o alguna lista de claves que pueden estar presentes pero no lo están.

Entonces, si tiene una clave del tipo peligroso y la indexa, sería bueno tener | undefined aquí. Pero la propuesta no es "Tratar a las llaves _peligrosas_ como peligrosas", sino "Tratar a todas las llaves, incluso las seguras, como peligrosas". Y una vez que comienzas a tratar las llaves seguras como peligrosas, la vida realmente apesta. Escribes código como

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

y TypeScript se está quejando de que arr[i] podría ser undefined a pesar de que _ parece que yo solo @ #% # lo probé_. Ahora tienes el hábito de escribir código como este, y se siente estúpido:

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

O tal vez escriba un código como este:

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

y TypeScript dice "Oye, esa expresión de índice podría ser | undefined , por lo que debes solucionarlo debidamente porque ya has visto este error 800 veces:

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
  }
}

Pero no solucionaste el error . Querías escribir Object.keys(yourObj) , o tal vez myObj[k] . Ese es el peor tipo de error del compilador, porque en realidad no lo está ayudando en ningún escenario, solo está aplicando el mismo ritual a cada tipo de expresión, sin tener en cuenta si es o no más peligroso que cualquier otra expresión de la misma forma.

Pienso en el antiguo "¿Está seguro de que desea eliminar este archivo?" diálogo. Si ese cuadro de diálogo apareciera cada vez que intentara eliminar un archivo, aprendería muy rápidamente a presionar del y cuando solía presionar del , y sus posibilidades de no eliminar algo importante se restablecerían a la configuración previa. -Línea de base de diálogo. Si, en cambio, el cuadro de diálogo solo apareció cuando estaba eliminando archivos cuando no iban a la papelera de reciclaje, ahora tiene una seguridad significativa. Pero no tenemos idea (ni _podríamos_ nosotros) si sus claves de objeto son seguras o no, por lo que se muestra el mensaje "¿Está seguro de que desea indexar ese objeto?" cada vez que lo hace, no es probable que encuentre errores a un ritmo mejor que no mostrarlo todo.

Estoy de acuerdo con la analogía del cuadro de diálogo de eliminación de archivos, sin embargo, creo que esta analogía también se puede extender para obligar al usuario a verificar algo que posiblemente no esté definido o sea nulo, por lo que esta explicación no tiene sentido, porque si esta explicación es cierta, la opción strictNullChecks inducirá el mismo comportamiento, por ejemplo, obtener algún elemento del DOM usando document.getElementById .

Pero ese no es el caso, muchos usuarios de TypeScript quieren que el compilador active la bandera sobre dicho código para que esos casos extremos puedan manejarse adecuadamente en lugar de lanzar el error Cannot access property X of undefined que es muy, muy difícil de rastrear.

Al final, espero que este tipo de características se puedan implementar como opciones adicionales del compilador de TypeScript, porque esa es la razón por la que los usuarios quieren usar TypeScript, quieren ser advertidos sobre código peligroso.

Es poco probable que suceda hablar sobre el acceso incorrecto a matrices u objetos, ¿tiene algún dato para respaldar esta afirmación? ¿O se basa simplemente en un instinto arbitrario?

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

El análisis de tipo basado en el flujo de control de TypeScript podría mejorarse para reconocer que este caso es seguro y no requiere el ! . Si un humano puede deducir que es seguro, un compilador también puede hacerlo.
Sin embargo, soy consciente de que esto podría no ser trivial de implementar en el compilador.

El análisis de tipo basado en el flujo de control de TypeScript podría mejorarse para reconocer que este caso es seguro

Realmente no puede.

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 función pasa un undefined a someFunc en el segundo paso a través del ciclo, o no? Hay muchas cosas que podría escribir en someFunc que resultarían en que apareciera un undefined más tarde.

¿Qué pasa con esto?

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 déjame darte otro ejemplo:

''
$ nodo

const arr = []
indefinido
arr [7] = 7
7
arr
[<7 elementos vacíos>, 7]
para (sea i = 0; i <longitud de arr.; i ++) {
... console.log (arr [i])
...}
indefinido
indefinido
indefinido
indefinido
indefinido
indefinido
indefinido
7
indefinido

@RyanCavanaugh ya que está aquí, ¿qué tal inferir item :: T por arr :: T[] en for (const item of arr) ... , y de lo contrario inferir arr[i] :: T | undefined al usar algunos --strict-index ? ¿Qué tal el caso que me importa, obj[key] :: V | undefined pero Object.values(obj) :: V[] por obj :: { [key: string]: V } ?

@yawaramin Si está utilizando matrices dispersas, Typecript ya no hace lo correcto. Una bandera --strict-index arreglaría eso. Esto compila:

const arr = []
arr[7] = 7

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

@RyanCavanaugh Hay un ejemplo muy común en el que puedo mostrarte dónde el usuario es propenso a acceder a la matriz de forma incorrecta.

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

El código anterior no debe pasar la verificación del compilador con gracia por debajo de strictNullChecks , porque algunos usos de getBlock devolverán indefinidos, por ejemplo getBlock('hello') , en tales casos, quiero seriamente el compila la bandera de aumento para poder manejar los casos indefinidos con elegancia sin explotar mi aplicación.

Y esto también se aplica a muchos modismos comunes, como acceder al último elemento de una matriz con arr.slice(-1)[0] , acceder al primer elemento arr[0] etc.

En última instancia, quiero que TypeScript me moleste por tales errores en lugar de tener que lidiar con aplicaciones explotadas.

¿Esta función pasa un indefinido a someFunc en el segundo paso a través del bucle, o no? Hay muchas cosas que podría escribir en someFunc que resultarían en una aparición indefinida más tarde.

@RyanCavanaugh Sí, JavaScript no se presta a la inmutabilidad. En este caso, debería ser necesario ! , o una matriz ReadonlyArray : someFunc(arr: ReadonlyArray<number>, i: number) .

@yawaramin Para matrices dispersas, el tipo de elemento probablemente deba incluir undefined menos que TypeScript pueda deducir que se usa como una tupla. En el código al que @danielnixon se vinculó (https://github.com/microsoft/TypeScript/issues/13778#issuecomment-536248028), las tuplas también se tratan de manera especial y no incluyen undefined en el tipo de elemento devuelto ya que el compilador garantiza que solo se acceda a índices establecidos.

esta es una decisión consciente. Sería muy molesto que este código fuera un error:

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

Oye, reconozco esa sintaxis; ¡Escribo bucles como este tal vez una vez al año!

Descubrí que en la mayoría de los casos en los que indexo en una matriz, en realidad quiero verificar undefined después.

Los temores de hacer que este caso en particular sea más difícil de trabajar parecen exagerados. Si solo desea iterar sobre los elementos, puede usar for .. of . Si necesita el índice del elemento por alguna razón, use forEach o repita sobre entries . En general, es extremadamente raro que realmente necesite un bucle for basado en índices.

Si hay mejores razones por las que uno querría el status quo, me gustaría verlas, pero independientemente: esta es una inconsistencia en el sistema y parece que muchos apreciarían mucho tener una bandera para solucionarlo.

Hola a todos, siento que gran parte de la discusión que se ha tenido aquí ha sido
tenía. Los propietarios del paquete han sido bastante claros sobre su razonamiento y
Ya he considerado la mayoría de estos argumentos. Si planean abordar
esto, estoy seguro de que lo darán a conocer. Aparte de eso, no creo que esto
el hilo es realmente productivo.

El viernes, 25 de octubre de 2019 a las 11:59 a.m., brunnerh [email protected] escribió:

esta es una decisión consciente. Sería muy molesto que este código
ser un error:

var a = []; for (var i = 0; i <a.length; i ++) {
a [i] + = 1; // a [i] posiblemente no esté definido
}

Oye, reconozco esa sintaxis; ¡Escribo bucles como este tal vez una vez al año!

Descubrí que en la mayoría de los casos en los que indexo en una matriz,
en realidad quiero comprobar si no está definido después.

Los temores de hacer que este caso en particular sea más difícil de trabajar parecen exagerados.
Si solo desea iterar sobre los elementos que puede usar para .. de. Si
necesita el índice del elemento por alguna razón, use forEach o repita
entradas. En general, es extremadamente raro que realmente necesite un
bucle for basado en índice.

Si hay mejores razones por las que uno querría el status quo, lo haría
me gusta verlos, pero independientemente: esto es una inconsistencia en el sistema
y parece que muchos apreciarían mucho tener una bandera para arreglarlo.

-
Estás recibiendo esto porque hiciste un comentario.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/microsoft/TypeScript/issues/13778?email_source=notifications&email_token=ACAJU3DQ7U6Y3MUUM26J4JDQQM62XA5CNFSM4C6KEKAKYY3PNVWWK3TUL52HS4DFVDVREXG43V2
o darse de baja
https://github.com/notifications/unsubscribe-auth/ACAJU3EWVM3CUFG25UF5PGDQQM62XANCNFSM4C6KEKAA
.

Siento que, si bien puede que no haya movimiento por parte de los propietarios de los paquetes, la retroalimentación continua de la comunidad sigue siendo valiosa, porque muestra que todavía hay una demanda de mejores herramientas en torno a este problema.

@brunnerh Estoy de acuerdo con usted, hoy en día ni siquiera necesito usar for bucles a menos que sea para ajustar el rendimiento, lo que ocurre el 0% del tiempo en el código base de mi empresa, porque la mayoría de las veces cambia el mapa / filter / reduce to for loop rara vez mejora el rendimiento, el verdadero culpable siempre es la lógica ineficiente, los problemas de red y las conexiones de la base de datos.

Me sorprende que nadie haya hablado todavía de as const .

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 manera más general, y no exactamente relacionado con el mensaje inicial de este problema, he estado usando los siguientes patrones de programación defensiva durante los últimos meses, y funcionó bien (para mí)

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
}

Aunque no hay una notación corta para ello (como ? ), creo que está bien. Decirle al compilador usted mismo que puede que no esté seguro de que el valor está definido está bien.

@martpie as const es genial si puedes usarlo, claro .

Hay al menos dos razones por las que prefiero no usar Array<T | undefined> :

  1. Es una cosa más que debe recordar hacer cada vez que use una matriz. Además, ya no puede usar la escritura implícita, lo cual es bueno tenerlo.
  2. Cambia las firmas de forEach , map y filter , que no pasan undefined como argumento de elemento (a menos que ese índice se establezca explícitamente de esa manera). Dependiendo de cuánto use esas funciones que podrían ser molestas de manejar.

También quiero comentar que esto causa muchos falsos positivos en eslint / mecanografiado ahora:

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

Eslint (correctamente) infiere del tipo que se trata de una comprobación innecesaria, porque foo nunca puede ser nulo. Entonces, ahora no solo tengo que recordar conscientemente hacer una verificación nula en las cosas que obtengo de una matriz, ¡también tengo que agregar una línea de desactivación de eslint! Y acceder a cosas fuera de un bucle for como este es prácticamente la única forma en que accedemos a los arreglos, ya que (como probablemente la mayoría de los desarrolladores de TS en estos días), usamos las funciones forEach / map / filter / etc cuando hacemos un bucle sobre los arreglos.

Realmente creo que deberíamos tener una nueva marca de compilador que esté configurada como verdadera por defecto si strict es verdadera, y si a la gente no le gusta, pueden optar por no participar. De todos modos, hemos agregado nuevas comprobaciones estrictas del compilador en versiones recientes.

Esta es la fuente de los _únicos_ errores de producción en tiempo de ejecución que he visto en la memoria reciente que fueron una falla del sistema de tipos.

Teniendo en cuenta la ergonomía del operador de encadenamiento opcional ahora disponible, tal vez sea el momento de revisar la decisión de _no_ hacer que este comportamiento esté disponible a través de una bandera.

Si obtengo una matriz de Thing del servidor, y necesito mostrar el último Thing en la matriz, algo con lo que me encuentro comúnmente es que la matriz está realmente vacía y accediendo a una propiedad en ese Thing bloquea la aplicación.

Ejemplo:

// `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; 

Tal vez este sea un problema con el diseño de nuestra API, pero realmente parece que TS debería entender que cuando accede a algo en la matriz, es posible que en realidad no esté allí.

Editar: Solo quiero aclarar que me refería a "nuestra API" como en "la API creada por el equipo en el que estoy trabajando".

sí, todo el mundo está de acuerdo en que es una decisión de diseño muy mala, contemporánea a una época remota en la que TS era completamente inseguro.

Como solución temporal, utilizo un pequeño truco sucio. Solo agrego package.json el simple script postinstall:

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

Por supuesto, no funcionará en Windows, pero eso es mejor que nada.

Todos los demás problemas que se ejecutan junto a este parecen ser redirigidos aquí, así que asumiré que este es el mejor lugar para preguntar: ¿está fuera de la mesa una sintaxis abreviada para firmas de índice opcionales? Como señala @martpie , debes escribir interface Foo { [k: string]: Bar | undefined; } que es menos ergonómico que interface Foo { [k: string]?: Bar; } .

¿Podemos obtener soporte del operador ?: ? ¿Si no, porque no? Es decir, el beneficio ergonómico fue suficiente para agregar la función para la definición de propiedad única - ¿no es "lo suficientemente útil" para las firmas de índice?

type Foo = { [_ in string]?: Bar } también funciona. No tan bonito, pero bastante conciso. También podría hacer su propio tipo Dict si lo desea. Sin embargo, no discutiría en contra de una extensión ?:

Esto empieza a parecer una de esas charlas sobre "Javascript: las partes malas":

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 la firma de tipo no es realmente útil. Necesitamos una forma de hacerlo de modo que el operador de índice [n] tenga un tipo T|undefined , pero, por ejemplo, usar map en una matriz no debería darnos T|undefined valores, porque en esa situación debemos saber que existen.

@radix Para las funciones reales en Array, no creo que esto sea un problema porque todas tienen sus propias definiciones de tipo que generan el tipo correcto: por ejemplo, map : https://github.com/microsoft /TypeScript/blob/master/lib/lib.es5.d.ts#L1331

El único uso de código común en el que la construcción | undefined plantea una regresión real de la experiencia es en los bucles for ... of . Desafortunadamente (desde la perspectiva de este problema), esos son constructos bastante comunes.

@riggs Creo que estás hablando de hacer que interface Array<T> { } tenga [index: number]: T | undefined , pero @radix probablemente esté hablando de lo que parece ser la recomendación actual, que es usar Array<T | undefined> en su propio código.

Este último es malo por varias razones, una de las cuales es que no controla los tipos de otros paquetes, pero el primero también tiene algunos problemas, a saber, que puede asignar undefined a la matriz, y que da undefined incluso en casos conocidos por ser seguros. 🤷‍♂️

Ahh, sí, mi malentendido. De hecho, solo me estaba refiriendo al uso de la definición [index: number]: T | undefined . Estoy completamente de acuerdo en que definir un tipo como Array<T | undefined> es una terrible solución que causa más problemas de los que resuelve.

¿Existe una manera elegante de anular lib.es5.d.ts o es un script posterior a la instalación la mejor manera de hacerlo?

@ nicu-chiciuc https://www.npmjs.com/package/patch-package Cabe totalmente en la caja de herramientas de un hereje junto con https://www.npmjs.com/package/yalc ✌️

¿Existe una forma elegante de anular lib.es5.d.ts o es un script postinstall la mejor forma de hacerlo?

En nuestro proyecto tenemos un archivo global.d.ts que usamos para 1) agregar definiciones de tipos para API integradas que aún no están en las definiciones de tipo predeterminadas de mecanografiado (por ejemplo, API relacionadas con WebRTC que cambian constantemente y son inconsistentes entre los navegadores ) y 2) anular algunas de las definiciones de tipo predeterminadas de mecanografiado (por ejemplo, anular el tipo de Object.entries para que devuelva matrices que contengan unknown lugar de any ).

No estoy seguro de si este enfoque podría usarse para anular los tipos de matriz de mecanografiado para resolver este problema, pero podría valer la pena intentarlo.

Lo anterior funciona cuando la combinación de declaraciones da el resultado que desea, pero para simplificar las interfaces se cruzan, pero aquí la opción menos restrictiva es la que desea.

En su lugar, podría intentar copiar y pegar todos los archivos lib.*.d.ts que está usando en su proyecto, incluidos en tsconfig.json 's files o include , luego edítelos a lo que quieras. Dado que incluyen /// <reference no-default-lib="true"/> , no debería necesitar ninguna otra magia, pero no estoy seguro de si tiene que eliminar los /// <reference lib="..."/> que tienen en sus dependencias. Esto es malo por todas las razones obvias de mantenibilidad, por supuesto.

@butchler También estamos usando este enfoque, pero parecía que no era posible anular el operador de indexación, aunque hemos anulado algunas otras funciones de matriz ( filter con guardias de tipo) con éxito.

Tengo un caso extraño al anular el operador de indexación:

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

La segunda asignación se equivoca (como debería), pero la primera no.
Aún más extraño es que al pasar el cursor sobre first mientras se desestructura muestra que tiene un tipo de string | undefined pero al pasar el cursor sobre él mientras se le asigna muestra que tiene un tipo de string .
Screenshot 2020-02-05 at 10 40 25 AM
Screenshot 2020-02-05 at 10 40 32 AM

¿Existe una definición de tipo diferente para la desestructuración de matrices?

Buscar una solución a esto, ya que los errores relacionados con índices faltantes han sido una fuente frecuente de errores en mi código.

Especificar un tipo como { [index: string]: string | undefined } no es una solución, ya que estropea la escritura para iteradores como Object.values(x).forEach(...) que nunca incluirán valores undefined .

Me gustaría ver que TypeScript genera errores cuando no busco indefinido después de hacer someObject[someKey] , pero no cuando hago Object.values(someObject).forEach(...) .

@danielnixon Eso no es una solución, es una solución. No hay nada que le impida a usted oa otro desarrollador por error (si es que puede llamarlo así) utilizar las herramientas integradas del lenguaje para los mismos objetivos. Quiero decir, utilizo fp-ts para estas cosas, pero este problema sigue siendo válido y debe solucionarse.

Si bien puedo seguir los argumentos en contra de @RyanCavanaugh con:

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

Dejaría algunos pensamientos breves al respecto. En esencia, TypeScript tiene como objetivo hacer que el desarrollo, bueno ... sea más seguro. Para recordar el primer objetivo de TypeScript:

1. Identifique estáticamente los constructos que probablemente sean errores.

Si miramos el compilador, ya tenemos algo de magia detrás de la inferencia de tipos. En el caso particular, la verdadera excusa es: "no podemos inferir el tipo correcto en esta construcción" y está totalmente bien. Con el tiempo, tenemos cada vez menos casos en los que la compilación no puede inferir los tipos. En otras palabras, para mí es solo cuestión de tiempo (y dedicar tiempo a esto) para obtener una inferencia de tipos más sofisticada.

Desde el punto de vista técnico, construye como:

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

rompe el primer objetivo de TypeScript. Pero esto no sucede si TypeScript conoce los tipos más 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)

Desde el punto de vista práctico, es una compensación yet . Este problema y varios problemas cerrados muestran que hay una gran demanda de la comunidad para este cambio: ATM 238 upvoters vs. 2 downvoters. Por supuesto, es una lástima que el bucle for anterior no inferencia del tipo "correcto", pero bueno, estoy bastante seguro de que la mayoría de los votantes positivos pueden vivir con ! y el nuevo ? como signo de atención y forzarlo en estuches seguros. Pero, por otro lado, obtenga los tipos correctos de acceso "peligroso".

Como estoy de acuerdo en que es un cambio muy sensible para bases de código grandes, voto por una propiedad de configuración, como se propuso aquí . Al menos para recibir comentarios de la comunidad. Si no es posible, entonces, bueno, TS 4.0 debería conseguirlo.

Esta no es una solución propuesta, sino solo un experimento: intenté modificar lib.es5.d.ts dentro de mi node_modules en un proyecto existente que usa TypeScript solo para ver cómo sería si _ tuviéramos un compilador opción para esto. Modifiqué Array y ReadonlyArray :

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

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

Debido a que este es un proyecto muy grande, esto provocó varios cientos de errores de tipo. Revisé solo algunos de ellos para tener una idea de con qué tipo de problemas me encontraría y qué tan difícil sería solucionarlos si hubiera una opción de compilador para esto. Estos son algunos de los problemas que encontré:

  1. Esto provocó errores de tipo no solo en nuestro código base, sino en una de nuestras dependencias: io-ts. Dado que io-ts le permite crear tipos que usa en su propio código, no creo que sea posible aplicar esta opción solo a su propia base de código y no aplicarla también a los tipos de io-ts. Esto significa que io-ts y probablemente algunas otras bibliotecas aún tendrían que actualizarse para funcionar con esta opción, incluso si solo se introdujera como una opción del compilador. Al principio pensé que hacer de esto una opción de compilador lo haría menos controvertido, pero si las personas que eligen usar la opción comienzan a quejarse con un grupo de autores de bibliotecas diferentes sobre incompatibilidades, puede ser aún más controvertido que simplemente ser un TS 4.0 cambio de ruptura.

  2. A veces tuve que agregar algunos protectores de tipo extra para eliminar la posibilidad de indefinido. Esto no es necesariamente algo malo, y es el punto principal de esta propuesta, pero solo lo menciono para completar.

  3. Tuve un error de tipo dentro de un bucle for (let i = 0; i < array.length; i++) que se repetía sobre un Array<T> arbitrario. No podría simplemente agregar un tipo de protección para verificar undefined , porque T sí mismo podría incluir undefined . Las únicas soluciones que se me ocurrieron fueron A) usar una aserción de tipo o @ts-ignore para silenciar el error de tipo o B) usar un bucle for-of en su lugar. Personalmente, no encuentro que esto sea tan malo (probablemente hay algunos casos en los que usar un bucle for-of para iterar sobre una matriz no es mejor de todos modos), pero puede ser controvertido.

  4. Hay muchos casos en los que el código existente ya estaba haciendo alguna afirmación sobre el valor de .length y luego accediendo a un elemento en la matriz. Estos ahora causaron errores de tipo a pesar del cheque .length , así que tuve que cambiar el código para no depender de un cheque .length o simplemente agregar un cheque !== undefined redundante. Sería realmente bueno si TypeScript pudiera de alguna manera permitir el uso de un cheque .length para evitar la necesidad del cheque !== undefined . Sin embargo, supongo que implementar eso no sería trivial.

  5. Algún código usaba A[number] para obtener el tipo de elementos de un tipo de matriz genérico. Sin embargo, esto ahora devuelve T | undefined lugar de solo T , lo que provoca errores de tipo en otros lugares. Hice un ayudante para solucionar esto:

    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
    );
    

    pero incluso con este ayudante para facilitar la transición, este sigue siendo un gran cambio radical. Quizás TypeScript podría tener algún tipo de caso especial por A[number] para evitar este problema, pero sería bueno evitar casos especiales extraños como ese si es posible.

Solo revisé un pequeño puñado de los cientos de errores de tipo, por lo que esta es probablemente una lista muy incompleta.

Para cualquier otra persona interesada en solucionar este problema, podría ser útil intentar hacer lo mismo con algunos proyectos existentes y compartir con qué otros problemas se encuentra. Con suerte, los ejemplos específicos pueden proporcionar alguna orientación a cualquiera que realmente intente implementar esto.

@butchler También me encontré con algunos de estos problemas, comencé a ver something[i] como something(i) , es decir, no puedo usar algo como if (Meteor.user() && Meteor.user()._id) {...} , así que no ' Espero tener este enfoque para la indexación de matrices; Primero tengo que obtener el valor que quiero inspeccionar de la matriz. Lo que estoy tratando de decir es que confiar en TS para comprender que verificar la propiedad de longitud y hacer alguna afirmación basada en eso podría poner demasiado estrés en el sistema de tipos. Una cosa que me hizo posponer la transición (además del hecho de que algunas otras bibliotecas también tienen errores, como ha dicho) es que la desestructuración de matrices aún no funciona correctamente y el valor desestructurado no será T | undefined sino T (ver mi otro comentario).
Aparte de eso, creo que anular lib.es5.d.ts es un buen enfoque. Incluso si algo cambia en el futuro, revertir el cambio no agregará errores adicionales, pero asegurará que algunos casos extremos ya estén cubiertos.
Ya comenzamos a usar patch-package para cambiar algunas definiciones de tipo en react y este enfoque parece funcionar bien.

También me encontré con algunos de estos problemas, comencé a ver algo [i] como algo (i), es decir, no puedo usar algo como if (Meteor.user () && Meteor.user () ._ id) { ...}, por lo que no espero tener este enfoque para la indexación de matrices; Primero tengo que obtener el valor que quiero inspeccionar de la matriz.

Sí, también terminé usando este enfoque cuando estaba probando mi experimento. Por ejemplo, código que se escribió anteriormente como:

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

tuvo que cambiarse a algo como:

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

Lo que estoy tratando de decir es que confiar en TS para comprender que verificar la propiedad de longitud y hacer alguna afirmación basada en eso podría poner demasiado estrés en el sistema de tipos.

Probablemente cierto. En realidad, podría ser más fácil escribir un código codificado para reescribir automáticamente el código que depende de las afirmaciones sobre .length para usar algún otro enfoque, que hacer que TypeScript sea lo suficientemente inteligente como para inferir más sobre los tipos basados ​​en afirmaciones sobre .length 😛

Incluso si algo cambia en el futuro, revertir el cambio no agregará errores adicionales, pero asegurará que algunos casos extremos ya estén cubiertos.

Este es un buen punto. Si bien incluir undefined en tipos de índices es un gran cambio radical, ir en la otra dirección no es un cambio radical. El código que puede manejar obtener T | undefined también debería poder manejar T sin ningún cambio. Esto significa, por ejemplo, que las bibliotecas podrían actualizarse para manejar el caso T | undefined si hubiera una marca de compilador y aún ser utilizadas por proyectos que no tienen esa marca de compilador habilitada.

Ignorando las matrices y centrándome en Record<string, T> por un momento, mi lista de deseos personal es que escribir solo permite T pero la lectura puede 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"];

La única forma de que lo anterior sea ergonómico sería algún tipo de mecanografía dependiente y protectores de tipo dependiente. Como no tenemos esos, agregar este comportamiento causaría todo tipo de problemas.

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

Volviendo a las matrices, el problema es que la firma de índice de las matrices no tiene la misma intención que Record<number, T> .

Por lo tanto, necesitaría guardias de tipo dependiente completamente diferentes como,

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

Entonces, la firma de índice para matrices no es realmente Record<number, T> . Es más como Record<(int & (0 <= i < this["length"]), T> (un tipo de número entero respaldado por rango y número)


Entonces, mientras que la publicación original solo habla de matrices, el título parece sugerir firmas de índice de "solo" string o "solo" number . Y son dos discusiones completamente diferentes.

TL; DR La publicación original y el título hablan sobre diferentes cosas, la implementación de cualquiera de las dos parece muy dependiente (ha) de la escritura dependiente.

Dado que existen funciones como forEach , map , filter , etc. (y en mi humilde opinión son mucho más preferibles), no esperaría que el equipo de TS complique mucho su motor de inferencia admite el bucle sobre una matriz con un bucle for regular. Tengo la sensación de que hay demasiada complejidad inesperada al intentar lograr eso. Por ejemplo, dado que i no es una constante, ¿qué sucede si alguien cambia el valor de i dentro del ciclo? Claro, es un caso marginal, pero es algo que deben manejar de una manera (con suerte) intuitiva.

Sin embargo, arreglar las firmas de índice debería ser relativamente simple (más o menos), como lo han demostrado los comentarios anteriores.

4. Hay muchos casos en los que el código existente ya estaba haciendo alguna afirmación sobre el valor de .length y luego accediendo a un elemento en la matriz. Estos ahora causaban errores de tipo a pesar del cheque .length , así que tuve que cambiar el código para no depender de un cheque .length o simplemente agregar un cheque !== undefined redundante. Sería muy bueno si TypeScript pudiera de alguna manera permitir el uso de un cheque .length para evitar la necesidad del cheque !== undefined . Sin embargo, supongo que implementar eso no sería trivial.

@butchler
Por curiosidad, ¿cuántos de estos estaban accediendo con una variable cambiante, vs accediendo con un número fijo? Porque, en el último caso, presumiblemente está utilizando la matriz como una tupla, para la cual TS realiza un seguimiento de la longitud de la matriz. Si utiliza con frecuencia matrices como tuplas, me gustaría saber qué significa volver a escribirlas como tuplas reales con la cantidad de errores.

Si utiliza con frecuencia matrices como tuplas, me gustaría saber qué significa volver a escribirlas como tuplas reales con la cantidad de errores.

Me encontré con un caso en el que solucioné un error de tipo simplemente agregando un as const a un literal de matriz para convertir el tipo de la variable en una tupla. Sin embargo, no tengo ni idea de lo común que es eso en nuestra base de código, ya que solo observé alrededor de 10 errores de varios cientos.

También encontré un problema con esto hoy.
Estamos accediendo desde una matriz a través de un índice calculado. Ese índice podría estar fuera de rango.
Entonces tenemos algo como esto en nuestro código:

const noNext = !items[currentIndex + 1];

Esto da como resultado que noNext se defina como false . Cuál está mal. Puede ser verdad.
Tampoco quiero definir items como Array<Item | undefined> porque eso da una expectativa incorrecta.
Si hay un índice, nunca debería ser undefined . Pero si está utilizando el índice incorrecto, es _es_ undefined .
Claro, lo anterior probablemente podría resolverse usando una verificación .length lugar o definiendo noNext explícitamente como boolean .
Pero al final esto es algo que me molesta desde que comencé a usar TypeScript y nunca entendí por qué | undefined no está incluido por defecto.

Dado que espero que la mayoría use mecanografiado-eslint, ¿existe alguna regla que pueda hacer cumplir que los valores deben verificarse después de la indexación antes de que puedan usarse? Esto puede resultar útil antes de que se implemente el soporte de TS.

Por si puede ser de interés para alguien. En un comentario anterior mencioné que el enfoque de parchear la definición de indexación Array en lib.es5.d.ts tiene un comportamiento extraño en la desestructuración de matrices, ya que parecía que no estaba influenciado por el cambio. Parece que depende de la opción target en tsconfig.json y funciona correctamente cuando es es5 (https://github.com/microsoft/TypeScript/issues/37045 ).

Para nuestro proyecto eso no es un problema ya que estamos usando la opción noEmit y la transpilación es manejada por meteor . Pero para proyectos en los que eso podría ser un problema, una solución que podría funcionar es safe-array-destructuring (https://github.com/typescript-eslint/typescript-eslint/pull/1645).
Todavía es un borrador y puede que no tenga valor cuando todo el problema se solucione en el compilador de TypeScript, pero si cree que podría ser útil, no dude en abordar cualquier inquietud / mejora en la regla typescript-eslint PR.

Lamentablemente, la solución con eslint en su PR solo admite tuplas y no matrices. El problema principal y el impacto principal que tengo con esto es la desestructuración de matrices.

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

Creo que todo este problema se inclinó por un lado en el que no estoy de acuerdo. No creo que deba haber controles forEach dentro de forEach, map, etc. También trato de evitar todos los usos de for cuando sea posible, por lo que los razonamientos para evitar esto tienen menos sentido para mí. No obstante, sigo pensando que esto es crucial para admitir la desestructuración de matrices.

Bueno, simplemente se equivoca para cualquier desestructuración de matriz, por lo que está obligado a hacerlo mediante la indexación.

¿No es eso malo? La desestructuración de matrices es una característica asombrosa. El problema es que las mecanografías no son precisas. Desactivar la función no es realmente una buena solución. El uso de índices es, en mi opinión, un código más feo (más difícil de leer y comprender) y más propenso a errores.

Es más feo, pero obliga al desarrollador a comprobar si el elemento está definido o no. He intentado encontrar soluciones más elegantes, pero parece que esta es la que tiene menos inconvenientes. Al menos para nuestro caso de uso.

@caseyhoward Como se señaló anteriormente en este número, esto provoca un comportamiento no deseado con las diversas funciones Array.prototype:

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

Esto no necesita arreglarse en las funciones array.prototype. ¡Este es un problema enorme para la desestructuración de matrices!

Me encontré con esto de nuevo hoy. Sigo pensando que el compromiso correcto es este , sobre el que me encantaría recibir comentarios.

Otro caso de uso para este problema: me encontré con este problema al usar typescript-eslint y habilitar no-unnecessary-condition . En el pasado, al acceder a una matriz por índice para realizar alguna operación en el elemento en ese índice, usamos el encadenamiento opcional para asegurarnos de que ese índice esté definido (en caso de que el índice esté fuera de los límites), como en array[i]?.doSomething() . Sin embargo, con el problema descrito en este número, no-unnecessary-condition marca ese encadenamiento opcional como innecesario (ya que el tipo no admite nulos según el mecanografiado) y la reparación automática elimina el encadenamiento opcional, lo que genera errores de tiempo de ejecución cuando la matriz accede a el índice i realidad no está definido.

Sin esta función, mi aplicación se volvió muy defectuosa ya que estoy lidiando con una matriz multidimensional, siempre tengo que recordarme a mí mismo que debo acceder a los elementos usando xs[i]?.[j] lugar de xs[i][j] , también tengo que emitir explícitamente el elemento al que se accede como const element = xs[i]?.[j] as Element | undefined para garantizar la seguridad de los tipos.

Encontrar este fue mi primer gran wtf en Typescript. De lo contrario, he encontrado que el lenguaje es maravillosamente confiable y esto me decepcionó, y es bastante engorroso agregar "como T | indefinido" a los accesos a la matriz. Me encantaría ver implementada una de las propuestas.

Sí, lo mismo aquí. Nos obligó a implementar nuestros propios tipos que tienen ( | undefined ) para tener seguridad de tipos. Principalmente para el acceso a objetos (probablemente sea un problema abierto separado), pero la misma lógica se aplica para acceder a los índices de la matriz.

No está seguro de lo que quiere decir, ¿puede vincular el problema o mostrar un ejemplo?

El miércoles 29 de abril de 2020 a las 2:41 a.m. Kirill Groshkov [email protected]
escribió:

Sí, lo mismo aquí. Nos obligó a implementar nuestros propios tipos que tienen (|
undefined) para tener seguridad de tipo. Principalmente para el acceso a objetos (probablemente sea un
problema abierto por separado), pero la misma lógica se aplica al acceso a los índices de matriz.

-
Estás recibiendo esto porque hiciste un comentario.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/microsoft/TypeScript/issues/13778#issuecomment-621096030 ,
o darse de baja
https://github.com/notifications/unsubscribe-auth/AAAGFJJT37N54I7EH2QLBYDRO7Y4NANCNFSM4C6KEKAA
.

@alangpierce

Una idea que parece prometedora: ¿qué pasaría si pudiera usar ?: al definir una firma de índice para indicar que espera que falten valores a veces con un uso normal? Actuaría como | undefined mencionado anteriormente, pero sin las incómodas desventajas. Tendría que rechazar los valores explícitos de undefined , que supongo que es una diferencia de los ?: habituales.

Esta es una idea interesante que apunta en la dirección correcta. Sin embargo, invertiría la lógica: el caso predeterminado debería ser que las firmas de índice incluyan | undefined . Si desea decirle al compilador que el caso indefinido nunca puede ocurrir en su código (para que nunca acceda a claves inválidas), puede agregar !: a la firma, así:

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

t['foo'] // Has type string

y el caso predeterminado sin !: se vería como lo conocemos, pero sería más seguro:

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

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

De esa manera, tiene la seguridad por defecto y, de la misma manera, no tiene que incluir explícitamente undefined lo que permitiría escribir undefined por accidente.

Lo siento si eso ya fue sugerido, no pude leer todos los comentarios en este hilo.

En general, realmente creo que el comportamiento predeterminado que tenemos ahora no es el que esperan los usuarios al usar TypeScript. Es mejor prevenir que curar, especialmente cuando el compilador puede detectar un error tan fácilmente como éste.

No pude hacer que index.d.ts funcionara con mi proyecto y realmente no quería lidiar con él.

Creé este pequeño truco que obliga a un tipo de protección:

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

Aunque el tipo termina como: 0 | T , todavía funciona.

¿No funcionaría const firstNode = nodes?.[0] ?

@ricklove ¿ alguna razón por la que prefieres +!undefined en lugar de simplemente 1 ?

¿No funcionaría const firstNode = nodes?.[0] ?

No, ya que el mecanografiado (incorrectamente, en mi opinión) no lo tratará como opcional (ver # 35139).

Según la documentación de Flow, el comportamiento con firmas de índice es el mismo que actualmente con TypeScript:

Cuando un tipo de objeto tiene una propiedad de indexador, se supone que los accesos a la propiedad tienen el tipo anotado, incluso si el objeto no tiene un valor en ese espacio en tiempo de ejecución. Es responsabilidad del programador garantizar que el acceso sea seguro, como ocurre con las matrices.

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

Entonces, en esta preocupación, tanto TS como Flow requieren que el usuario piense en claves indefinidas en lugar de que el compilador los ayude :(

Sería una ventaja de TS si el compilador evitara que los usuarios cometan este tipo de errores. Al leer todos los comentarios en este hilo, parece que a la gran mayoría aquí le encantaría tener esta función. ¿Sigue el equipo de TS al 100% en contra?

Parece una tontería que TS esté haciendo todo lo posible para evitar el acceso a miembros indefinidos, excepto esto. Este es, con mucho, el error más común que he estado solucionando en nuestro código base.

[moviendo la discusión aquí desde # 38470]
Aceptando todos los argumentos de por qué esto no sería práctico para matrices ...
Creo que esto definitivamente debería abordarse para Tuples:

// 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 qué no hacer foo string | number | undefined ?

Dar esto a las tuplas no debería afectar a la mayoría de los usuarios, y los usuarios a los que afecta deben tener strictNullChecks habilitado y declarar sus matrices como constantes ...
Obviamente, están buscando un tipo de seguridad más estricto.

también podría ayudar con estos

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

Lo que originalmente causó mi error de tiempo de ejecución como number convirtió en NaN , luego devolvió un undefined de una tupla.
Todo lo cual era de tipo seguro

No estoy seguro de que las tuplas sean realmente tan diferentes. Tienen que adaptarse a tipos de descanso (por ejemplo, [str, ...num[]] ), y técnicamente aa[Infinity] es un Javascript perfectamente válido, por lo que pude ver que se complicaría crear un caso especial para ellos.

Su publicación también me hizo pensar en cómo se vería si obtuviéramos algún apoyo para tratar los retornos de índice como indefinidos. Si, como en su ejemplo, aa[num] realmente escribió como string | number | undefined , ¿tendría que escribir for (let i = 0; i < 2; i++) { foo(aa[i]!); } aunque sepa que el índice se mantendrá dentro de los límites? Para mí, cuando las verificaciones nulas estrictas marcan algo que escribí, me gusta poder solucionarlo escribiendo mejor las cosas en primer lugar o usando guardias de tiempo de ejecución; si tengo que recurrir a una aserción no nula, normalmente lo veo como un fracaso de mi parte. Sin embargo, no veo una forma de evitarlo en este caso, y eso me molesta.

Si tuple[number] siempre se escribe en T | undefined , ¿cómo le gustaría manejar el caso en el que sabe que el índice está acotado correctamente?

@ thw0rted No recuerdo la última vez que usé un bucle "clásico" for en Typescript ( .map y .reduce ftw). Es por eso que una opción de compilador para esto sería genial en mi opinión (que puede estar desactivada de forma predeterminada). Muchos proyectos nunca encuentran nada como el caso que proporcionó, y solo usan firmas de índice para la desestructuración de matrices, etc.

(mi comentario original: https://github.com/microsoft/TypeScript/issues/13778#issuecomment-517759210)

Oh, con toda honestidad, rara vez indexo con un bucle for. Hago un uso extensivo de Object.entries o Array#map , pero de vez en cuando necesito pasar claves para indexar en un objeto o (probablemente con menos frecuencia aún) índices en una matriz o tupla. El bucle for fue un ejemplo artificial, sin duda, pero plantea el punto de que cualquiera de las opciones ( undefined o no) tiene desventajas.

pero plantea el punto de que cualquiera de las opciones ( undefined o no) tiene desventajas.

Sí, y sería bueno si el usuario de TypeScript pudiera elegir qué desventaja prefiere: guiño:

Oh, con toda honestidad, rara vez indexo con un bucle for. Hago un uso extensivo de Object.entries o Array#map , pero de vez en cuando necesito pasar claves para indexar en un objeto o (probablemente con menos frecuencia aún) índices en una matriz o tupla.

En aquellos casos en los que lo necesite, puede usar Array#entries :

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

Personalmente, no uso tanto Array#forEach , y nunca uso Array#map para la iteración (aunque lo uso todo el tiempo para el mapeo). Prefiero mantener mi código plano, y agradezco la capacidad de salir de un bucle for ... of.

No estoy seguro de que las tuplas sean realmente tan diferentes. Tienen que adaptarse a tipos de descanso (por ejemplo, [str, ...num[]] ), y técnicamente aa[Infinity] es un Javascript perfectamente válido, por lo que pude ver que se complicaría crear un caso especial para ellos.

Su publicación también me hizo pensar en cómo se vería si obtuviéramos algún apoyo para tratar los retornos de índice como indefinidos. Si, como en su ejemplo, aa[num] realmente escribió como string | number | undefined , ¿tendría que escribir for (let i = 0; i < 2; i++) { foo(aa[i]!); } aunque sepa que el índice se mantendrá dentro de los límites? Para mí, cuando las verificaciones nulas estrictas marcan algo que escribí, me gusta poder solucionarlo escribiendo mejor las cosas en primer lugar o usando guardias de tiempo de ejecución; si tengo que recurrir a una aserción no nula, normalmente lo veo como un fracaso de mi parte. Sin embargo, no veo una forma de evitarlo en este caso, y eso me molesta.

Si tuple[number] siempre se escribe en T | undefined , ¿cómo le gustaría manejar el caso en el que sabe que el índice está acotado correctamente?

No estoy seguro de que haya tanto for(let i..) iterando sobre tuplas ...
Si la tupla tiene un solo tipo, el usuario probablemente usaría una matriz,

En mi experiencia, las tuplas son excelentes para tipos mixtos y, si ese es el caso general, de todos modos está comprobando el tipo

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'
}

Además, suponiendo que suceda este cambio propuesto, ¿qué está impidiendo que la gente haga esto?

// 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 
}

podría usar keyof typeof aa lugar de 0 | 1 cuando solucionen este problema

Seguro que no habría ningún tipo de seguridad al hacer i aritmética,
pero realmente permite a las personas elegir esto por seguridad de tipos en lugar de la matriz predeterminada "No es estrictamente seguro pero conveniente"

Creo que tener los operadores ?? y ?. significa que ya no es demasiado engorroso hacer acceso aleatorio a Array s.
Pero también, probablemente sea mucho más común

  • iterar (por ejemplo, con .map o .forEach , donde no hay necesidad de " T | undefined ", la devolución de llamada nunca se ejecuta en índices fuera de límites) o
  • atraviesa Array en bucles, algunos de los cuales podrían determinarse estáticamente como seguros ( for...of .. casi, excepto por _ matrices dispersas_, for...in creo que es seguro).

Y también, una variable podría considerarse un "índice seguro" si se marca con <index> in <array> primero.


_Clarificación_: Estoy hablando de la razón original por la que Array<T> no tiene el tipo de índice [number]: T | undefined (sino solo [number]: T ), que era demasiado engorroso de usar.
Quiero decir que ya no es el caso, por lo que se podría agregar undefined .

@ vp2177 El problema no son las características (sí, ?. funciona), el problema es que el compilador no nos avisa y nos impide dispararnos en el pie.

Tal vez una regla general podría hacerlo, pero aún así.

sí, ?. funciona

Siento disentir. ?. funcionará para acceder a propiedades en el valor indexado, pero el compilador aún dirá que el valor está definido independientemente de si lo está o no (vea # 35139).

Para agregar a eso, si usa eslint y desea usar no-unnecessary-condition , dichos accesos se marcarán como innecesarios y se eliminarán porque el compilador dice que nunca está indefinido y eso haría innecesario ?. .

@martpie Lo siento, creo que

Quiero decir ... parece una buena idea tener un --strictIndexChecks o similar que permita a las personas optar por este tipo de comportamiento. Entonces no será un cambio importante para todos y brindará un entorno de tipo más estricto.

Creo que Flow funciona de esta manera de forma predeterminada para los índices y no tiene los problemas antes mencionados, pero ha pasado un tiempo desde que lo usé, por lo que podría estar equivocado.

@bradennapier Flow no usa T | undefined en la firma del índice para Array s (lo mismo que TypeScript, desafortunadamente).

Anteriormente sugerí un enfoque que podría hacer felices a todos. Espero que esté bien repetirlo en otras palabras.

Básicamente, el comportamiento predeterminado de la firma de índice se cambiaría a

  • Incluya | undefined al leer de una matriz u objeto
  • No incluya | undefined al escribir (porque solo queremos valores T en nuestro objeto)

La definición sería así:

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 las personas que no quieren undefined incluido en el tipo, pueden deshabilitarlo usando una opción del compilador o deshabilitarlo solo para algunos tipos usando el 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 no romper el código existente, también podría ser mejor introducir una nueva opción de compilador que haga que se incluya undefined (como se muestra con el tipo MaybeUndefined arriba). Si no se especifica esa opción, todos los tipos de firmas de índice se comportan como si se usara el operador ! dentro de la declaración.

Otra idea que surge: uno podría querer usar el mismo tipo en algunos lugares del código con los dos comportamientos diferentes (incluya undefined o no). Se podría definir un nuevo mapeo de tipos:

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

¿Sería una buena extensión de TypeScript?

Me gusta la idea, pero sospecho que primero tendríamos que obtener https://github.com/microsoft/TypeScript/issues/2521 , lo cual, no me malinterpretes, todavía creo que deberíamos hacerlo , pero yo ' No estoy conteniendo la respiración.

@bradennapier Flow no usa T | undefined en la firma del índice para Array s (lo mismo que TypeScript, desafortunadamente).

Hmm, ¿qué pasa con los índices de objetos? Nunca me he encontrado con la necesidad de matrices, pero estoy bastante seguro de que te hace verificar cuando usas objetos.

@bradennapier ¿Índices de objetos? Como en, o[4] donde o es el tipo object ? TypeScript no te permite hacer eso .....

la expresión de tipo '4' no se puede usar para indexar el tipo '{}'
La propiedad '4' no existe en el tipo '{}'. Ts (7053)

@RDGthree no quiere ser un caballero blanco para Microsoft de todas las cosas, pero aparentemente te perdiste esta parte de mhegazy en la primera respuesta:

Con la excepción de strictNullChecks, no tenemos indicadores que cambien el comportamiento del sistema de tipos. las banderas normalmente habilitan / deshabilitan la notificación de errores.

y un poco más tarde RyanCavanaugh tiene:

Seguimos siendo bastante escépticos de que alguien pueda beneficiarse de esta bandera en la práctica. Mapas y cosas similares a mapas ya pueden optar por | undefined en sus sitios de definición, y hacer cumplir un comportamiento similar al EULA en el acceso a la matriz no parece una victoria. Es probable que necesitemos mejorar sustancialmente el CFA y los protectores de tipos para que esto sea agradable.

Básicamente, son perfectamente conscientes del hecho de que se está pidiendo una bandera, y hasta ahora no han estado convencidos de que valga la pena el trabajo para ellos por el dudoso beneficio que se ha sugerido que la gente obtendría de esto. Honestamente, no me sorprende, ya que todos siguen viniendo aquí con casi exactamente las mismas sugerencias y ejemplos que ya se abordaron. Idealmente, debería usar for of o métodos para matrices, y Map lugar de objetos (o menos eficazmente, valores de objeto 'T | undefined`).

Estoy aquí porque mantengo un paquete DT popular y la gente sigue pidiendo que se agreguen |undefined a cosas como mapas de encabezado http, lo cual es totalmente razonable, excepto por el bit en el que rompería casi todos los usos existentes. , y el hecho de que hace que otros usos seguros como Object.entries() mucho peores de usar. Aparte de quejarse de que su opinión es mejor que la de los creadores de Typecript, ¿cuál es realmente su contribución aquí?

Simon, tal vez leí mal tu comentario, pero parece un argumento a favor de la sugerencia original. La capacidad que falta en la "última milla" es tratar las propiedades como | undefined veces , según el contexto, pero no en otros casos. Es por eso que hice una analogía con el # 2521 upthread.

En un escenario ideal, podría declarar una matriz u objeto tal que, dado

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

De alguna manera puedo terminar con

  • arr[n] tipos como T | undefined
  • arr[n] = undefined es un error
  • Tengo acceso a algún tipo de iteración en arr que me da valores escritos como T , no T | undefined
  • obj[k] tipos como V | undefined
  • obj[k] = undefined es un error
  • Object.entries() por ejemplo debería darme tuplas de [K, V] y nada puede ser indefinido

Es la naturaleza asimétrica del problema lo que define este tema en mi opinión.

Yo diría que la afirmación de que es un problema para los desarrolladores que utilizan bucles for no tiene en cuenta a los desarrolladores que utilizan la Desestructuración de matrices. Está roto en Typecript y tampoco se solucionará debido a este problema. Proporciona mecanografía inexacta. La desestructuración de matrices de IMO que se está efectuando es un problema mucho mayor que aquellos que todavía usan bucles 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()
}

No quiero alejarme demasiado del punto original del problema, pero realmente debería declarar ese método de ejemplo usando un tipo de tupla para el argumento de la función. Si la función se declaró como ([userId, duration, ...reason]: [string, number, ...string[]]) => {} entonces no tendría que preocuparse por esto en primer lugar.

Quiero decir, seguro que podría lanzarlo, pero esa no es realmente una solución. De manera similar, cualquiera aquí podría simplemente lanzar la indexación de la matriz de bucle for y silenciar todo este problema.

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

El problema es que Typecript no advierte sobre este comportamiento. Como desarrollador, debemos recordar convertirlos manualmente como indefinidos, lo cual es un mal comportamiento para que una herramienta cause más trabajo a los desarrolladores. Además, para ser justos, el elenco debería verse así:

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

}

Eso no es nada agradable. Pero como dije anteriormente, el problema principal ni siquiera es necesario escribirlo así, es que TS no advierte sobre esto en absoluto. Eso lleva a los desarrolladores que olvidan esto para empujar el código, genial CI no encontró problemas en la fusión e implementación de tsc. TS da una sensación de confianza en el código y el hecho de que se proporcionen mecanografiados inexactos da una falsa sensación de confianza. => ¡Errores en tiempo de ejecución!

Eso no es un "elenco", es escribir correctamente sus parámetros formales. Mi punto fue que si escribe su método correctamente, entonces cualquiera que lo haya llamado de manera insegura con la firma anterior ( args: string[] ) ahora obtendrá errores en tiempo de compilación, como deberían , cuando no están garantizados para pasar el número correcto de argumentos. Si desea seguir permitiendo que las personas se salgan con la suya al no aprobar todos los argumentos que necesita y verificarlos usted mismo en tiempo de ejecución, en realidad es muy fácil escribir como (args: [string?, number?, ...string[]]) . Eso ya funciona muy bien y no es realmente pertinente para este tema.

Eso solo movería el problema de esa función a otra función que llame a esta función. Como todavía necesita analizar la cadena proporcionada por los usuarios en valores separados y si usa la desestructuración de matriz para analizarla dentro del commandHandler que llama a esta función, terminará con el mismo problema.

No hay forma de evitar que se proporcionen tipos inexactos para la desestructuración de matrices.

No quiero alejarme demasiado del punto original del problema.

Nota: Aunque estoy de acuerdo con usted, deben tratarse como problemas separados, cada vez que alguien abre un problema que muestra los problemas con la desestructuración de la matriz, se cierra y los usuarios se reenvían aquí. # 38259 # 36635 # 38287 etc ... Así que no creo que nos estemos desviando del tema de este número.

Algunas de mis frustraciones con este problema se resolverían con una sintaxis simple para aplicar | undefined al tipo que de otro modo se inferiría del acceso al objeto. P.ej:

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 ?;

Por lo tanto, la verificación de tipos más estricta sigue siendo la opción de inclusión. Lo siguiente funciona, pero requiere duplicación (y agrega rápidamente una cantidad similar de ruido visual):

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

Me imagino que este tipo de sintaxis de "tipo afirmar a tal vez" ( as ? ) también facilitaría la implementación de una regla require-safe-index-signature-access eslint que no es terriblemente confusa para los nuevos usuarios. (Por ejemplo, "Puede corregir el error de pelusa agregando as ? al final.") La regla podría incluso incluir un autofixer seguro, ya que solo podría causar fallas en la compilación, nunca problemas de tiempo de ejecución.

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

No creo que haya una sola aplicación de esto aparte de esta característica sorpresa de TS, aunque jeje.

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

Gracias por la respuesta. Si bien una función como esta funcionaría (y casi con certeza será optimizada por la mayoría de los motores JS), prefiero aceptar el ruido de sintaxis adicional mencionado ( as ComplexType | undefined ) que hacer que el archivo JS compilado conserve este método "envoltorio "en cualquier lugar donde se utilice.

Otra opción de sintaxis (que sería menos específica de un caso de uso que as ? anterior) podría ser permitir el uso de la palabra clave infer en las afirmaciones de tipo como marcador de posición para el tipo inferido de otro modo, p.ej:

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

Actualmente, las declaraciones infer solo están permitidas en la cláusula extends de un tipo condicional ( ts(1338) ), por lo que darle a la palabra clave infer un significado en el contexto de as [...] Las afirmaciones de tipo no interferirían.

Eso aún podría ser razonable para hacer cumplir usando infer | undefined través de una regla eslint mientras es lo suficientemente general para otros casos, por ejemplo, cuando el tipo resultante se puede refinar con tipos de utilidad:

// 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

Es inútil si tenemos que seguir recordando usar un patrón diferente y más seguro de todos modos. El punto es que TypeScript nos respalde, ya sea que estemos en un muy buen día, cansados ​​o nuevos en una base de código.

Nuestro comportamiento actual es que la expresión de tipo Array<string>[number] se interpreta como "El tipo que ocurre cuando indexa una matriz por un número". Tienes permitido escribir

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

como una forma indirecta de escribir const t: string .

Si existiera esta bandera, ¿qué debería pasar?

Si Array<string>[number] es string | undefined , entonces tiene un problema de solidez en las escrituras:

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);

Si Array<string>[number] es string , entonces tiene el mismo problema que se describe en las lecturas:

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 demasiado complejo agregar una sintaxis de tipos para "El tipo de lectura de índice de" y "El tipo de escritura de índice de". ¿Pensamientos?

Debo agregar que cambiar el significado de Array<string>[number] es realmente problemático ya que implicaría que esta bandera cambia la interpretación de los archivos de declaración. Esto es estresante porque parece el tipo de bandera que una cantidad sustancial de personas habilitaría, pero podría causar que ocurran / no ocurran errores en las cosas publicadas desde DefinitelyTyped, por lo que probablemente tendríamos que asegurarnos de que todo se construya limpiamente en ambas direcciones. La cantidad de indicadores que podemos agregar con este comportamiento es obviamente extremadamente pequeña (no podemos probar CI 64 configuraciones diferentes de cada tipo de paquete), así que creo que tendríamos que dejar Array<T>[number] como T solo para evitar ese problema.

@RyanCavanaugh En su ejemplo con write(arr, undefined) , la llamada a write sería aceptada, pero ¿la asignación arr[0] = v; ya no se compilaría?

Si la función write anterior proviene de una biblioteca, y fue JS que se compiló sin la bandera, entonces seguro que no sería sólido.Sin embargo, eso ya es un problema existente porque las bibliotecas se pueden compilar con las banderas que elijan. Si una biblioteca se compiló sin comprobaciones nulas estrictas, entonces una función en ella podría devolver string , mientras que en realidad debería devolver string|undefined . Pero la mayoría de las bibliotecas en estos días parecen implementar comprobaciones nulas, de lo contrario, la gente se queja. Del mismo modo, una vez que exista este nuevo indicador, es de esperar que las bibliotecas comiencen a implementarlo y, finalmente, la mayoría de las bibliotecas lo tendrán configurado.

Además, aunque no puedo encontrar ningún ejemplo concreto en la parte superior de mi cabeza, ciertamente tuve que configurar skipLibCheck para que mi aplicación se compilara. Es una opción bastante estándar de tsconfig en nuestra plantilla cada vez que se crea un nuevo repositorio de TS, porque hemos tenido muchos problemas con él.

Personalmente (y tal vez egoístamente), estoy de acuerdo con los casos extremos, si eso hace que _mi_ código sea generalmente más seguro. Todavía creo que me encontraría con punteros nulos al acceder a las matrices por índice con más frecuencia de lo que me encontraría con otros casos extremos, aunque podría estar convencido de lo contrario.

If Array[número] es una cadena | undefined, entonces tienes un problema de solidez en las escrituras:

En mi opinión, Array<string>[number] siempre debería ser string|undefined . Es la realidad, si indexo en una matriz con cualquier número, obtendré el tipo de elemento de la matriz o indefinido. Realmente no puede ser más específico allí a menos que esté codificando la longitud de la matriz como podría hacerlo con tuplas. Su ejemplo de escritura no escribiría check, porque no puede asignar string|undefined a un índice de matriz.

Parece demasiado complejo agregar una sintaxis de tipos para "El tipo de lectura de índice de" y "El tipo de escritura de índice de". ¿Pensamientos?

Esto parece ser exactamente lo que debería ser, ya que son dos cosas diferentes. Ninguna matriz tendrá nunca un índice definido, por lo que siempre tendrá el potencial de quedar indefinido. El tipo de Array<string>[number] sería string|undefined . Para especificar lo que quiere T en un Array<T> , se podría usar un tipo de utilidad (nombrar no muy bien): ArrayItemType<Array<string>> = string . Esto no ayuda con los tipos Record , que pueden necesitar algo como RecordValue<Record<string, number>, string> = string .

Estoy de acuerdo en que no hay grandes soluciones aquí, pero estoy bastante seguro de que preferiría la solidez en las lecturas del índice.

No me siento particularmente fuerte sobre la necesidad de esto en las matrices, ya que muchos otros lenguajes (incluidos los "seguros" como Rust) dejan la responsabilidad de las verificaciones de límites al usuario, por lo que yo y muchos desarrolladores ya estamos acostumbrados a hacerlo. . Además, la sintaxis tiende a hacerlo bastante obvio en estos casos, porque _siempre_ usan notación entre corchetes como foo[i] .

Dicho esto, me siento muy convencido de agregar esto a los objetos indexados en cadenas, porque es muy difícil saber si la firma de foo.bar es correcta (debido a que el campo bar está definido explícitamente), o posiblemente undefined (porque es parte de una firma de índice). Si este caso (que creo que tiene un valor bastante alto) se resuelve, entonces el caso de la matriz probablemente se vuelva trivial y probablemente valga la pena hacerlo también.


Parece demasiado complejo agregar una sintaxis de tipos para "El tipo de lectura de índice de" y "El tipo de escritura de índice de". ¿Pensamientos?

La sintaxis de getter y setter de javascript podría extenderse a definiciones de tipos. Por ejemplo, TypeScript comprende completamente la siguiente sintaxis:

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

Sin embargo, TS actualmente "fusiona" estos en un solo tipo, ya que no rastrea los tipos "get" y "set" de un campo por separado. Este enfoque funciona para la mayoría de los casos comunes, pero tiene sus propias deficiencias, como requerir que los captadores y los definidores compartan el mismo tipo y asignar incorrectamente un tipo de captador a un campo que solo define un definidor.

Si TS hiciera un seguimiento de "get" y "set" por separado para todos los campos (lo que probablemente tiene algunos costos de rendimiento reales), resolvería estas peculiaridades y también proporcionaría un mecanismo para describir la característica descrita en este número:

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

esencialmente se convertiría en una abreviatura de:

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

Debo agregar que cambiando el significado de Array[número] es realmente problemático ya que implicaría que esta bandera cambia la interpretación de los archivos de declaración.

Si los archivos de declaración generados siempre usaran la sintaxis completa de getter + setter, esto _sólo_ sería un problema para los escritos a mano. Aplicar las reglas actuales a la sintaxis abreviada restante _en archivos de definición solamente_ podría ser una solución aquí. Ciertamente resolvería los problemas de compatibilidad, pero también aumentaría el costo mental de leer archivos .ts frente a archivos .d.ts .


Aparte:

Ahora, por supuesto, esto no aborda _todas_ las sutiles advertencias con 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 es otro caso marginal, y probablemente valga la pena considerarlo si está dentro del alcance de los objetivos de TS ... pero de cualquier manera probablemente podría resolverse por separado, ya que mi propuesta aquí es consistente con el comportamiento actual de TypeScript.

Realmente siento que solo tengo que abrir cada par de páginas de comentarios y soltar un enlace al # 2521 nuevamente.

Parece demasiado complejo agregar una sintaxis de tipos para "El tipo de lectura de índice de" y "El tipo de escritura de índice de". ¿Pensamientos?

¡No! Y al menos (hace clic en el enlace ...) 116 personas más aquí están de acuerdo conmigo. Estaría muy feliz de tener al menos la opción de escribir lecturas y escrituras de manera diferente. Este es solo otro gran caso de uso para esa función.

Estaría _muy feliz_ de tener al menos la opción de escribir lecturas y escrituras de manera diferente. Este es solo otro gran caso de uso para esa función.

Creo que la intención era cuestionar si debería haber una forma de referirse a los tipos de lectura y escritura, por ejemplo, ¿debería haber algo como Array<string>[number]= para referirse al tipo de escritura?

Estaría contento con la resolución de # 2521 y este problema si suceden dos cosas:

  • Primero, escriba getters y setters de manera diferente, según # 2521
  • Luego, cree una bandera como se discutió en este número, de modo que el "tipo de escritura" de Array<string>[number] sea string mientras que el "tipo de lectura" sea string | undefined .

Supongo (¿erróneamente?) Que el primero sentaría las bases para el segundo, consulte https://github.com/microsoft/TypeScript/issues/13778#issuecomment -630770947. No tengo ninguna necesidad particular de una sintaxis explícita para definir el "tipo de escritura" yo mismo.

@RyanCavanaugh metapregunta rápida: ¿sería más útil tener mi sugerencia bastante específica anterior como un tema separado con el propósito de debatir y rastrear más claramente sus fortalezas / debilidades / costos (especialmente porque los costos de rendimiento no serán triviales)? ¿O esto solo aumentaría el ruido?

@RyanCavanaugh

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

como una forma indirecta de escribir const t: string .

Supongo que este es el mismo razonamiento para las tuplas.

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

sin embargo, las tuplas conocen los límites y devuelven correctamente undefined para tipos de números más 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 qué el tipo number actuaría de manera diferente?

Parece que tiene los mecanismos para hacer esto: https://github.com/microsoft/TypeScript/issues/38779

Esto satisfaría mis necesidades sin agregar una bandera adicional.

En caso de que ayude a alguien, aquí hay un complemento de ESLint que marca el acceso inseguro a la matriz y la desestructuración de matriz / objeto. No marca usos seguros como tuplas y objetos (sin registro).

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

Saludos, pero espero que quede claro que esto aún debe agregarse a nivel de lenguaje, ya que está puramente dentro del ámbito de la verificación de tipos y algunos proyectos no tienen un linter o las reglas correctas.

@RyanCavanaugh

también hemos probado cómo se ve esta función en la práctica y puedo decirles que no es agradable

Solo por curiosidad, ¿puedes decirnos de qué se trata la parte no bonita ? Quizás podamos solucionarlo si puede compartir más sobre esto.

Esta no debería ser una opción, debería ser la predeterminada .

Es decir, si el sistema de tipos de TypeScript debe describir en tiempo de compilación los tipos que los valores de JavaScript pueden adoptar en tiempo de ejecución:

  • indexar un Array puede, por supuesto, devolver undefined en JavaScript (índice fuera de los límites o matriz dispersa), por lo que la firma de lectura correcta sería T | undefined .
  • "hacer" que un índice en un Array sea undefined también es posible mediante la palabra clave delete .

Como TypeScript 4 previene

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

hay un buen caso para prevenir también a[1] = undefined .
Esto sugiere que Array<T>[number] debería ser diferente para lecturas y escrituras.

Podría ser "complejo" pero permitiría modelar las posibilidades de tiempo de ejecución en JavaScript con mayor precisión;
¿Para qué se ha creado TypeScript, verdad?

No tiene sentido discutir la idea de hacer que | undefined devuelva el comportamiento predeterminado; nunca lanzarán una versión de Typecript que explote millones de líneas de código heredado debido a una actualización del compilador. Un cambio como ese sería el cambio más importante en cualquier proyecto que haya seguido.

Sin embargo, estoy de acuerdo con el resto de la publicación.

No tiene sentido discutir la idea de hacer que | undefined devuelva el comportamiento predeterminado: _nunca_ lanzarán una versión de Typecript que explote millones de líneas de código heredado debido a una actualización del compilador. Un cambio como ese sería el cambio más importante en cualquier proyecto que haya seguido.

Sin embargo, estoy de acuerdo con el resto de la publicación.

No si es una opción de configuración.

No si es una opción de configuración.

Eso es justo, pero al menos esperaría un largo período de "entrada" en el que está desactivado de forma predeterminada, lo que da a las personas mucho tiempo (¿6 meses? ¿Un año? ¿Más?) Para convertir el código heredado antes de que se active. -defecto. Al igual que, agregue la bandera en v4.0, configúrelo como activado por defecto en v5.0, ese tipo de cosas.

Pero eso es un futuro

No tiene sentido discutir la idea de hacer que | undefined devuelva el comportamiento predeterminado: _nunca_ lanzarán una versión de Typecript que explote millones de líneas de código heredado debido a una actualización del compilador. Un cambio como ese sería el cambio más importante en cualquier proyecto que haya seguido.

Sin embargo, estoy de acuerdo con el resto de la publicación.

A su punto @ thw0rted , propuse un compromiso aquí: https://github.com/microsoft/TypeScript/issues/38779

Permite esta funcionalidad, en una característica que se introdujo mucho más tarde, que todavía no se usa tan ampliamente, y parece que ya está "casi" allí y solo necesita cambios menores.

Para la coherencia de "strict" algo de Array<T> debe verse como "a list of type T that is infinitly long with no gaps" Siempre que no lo desee, debe usar tuplas.

Sí, hay inconvenientes en esto, pero una vez más, siento que es un "compromiso" justo entre esta característica que nunca se implementa e implementa exactamente como lo pedimos aquí.

Si estás de acuerdo, vota a favor

No estoy de acuerdo con que esto necesite una opción / configuración completamente nueva.

No creo que deba ser un comportamiento predeterminado cuando la opción estricta está deshabilitada, pero cuando habilito la opción estricta SE ESTRICTO y las hago cumplir incluso en proyectos más antiguos. Habilitar estricto es lo que las personas optan por las mecanografías más estrictas / precisas posibles. Si habilito una opción estricta, me gustaría tener confianza en mi código y esa es la mejor parte de TS. Pero estas mecanografías inexactas, incluso con estricto habilitado, son solo un dolor de cabeza.

Con el fin de mantener la legibilidad de este hilo, oculté una serie de comentarios tangenciales que no se agregaron de manera significativa a la conversación. En general, puede suponer que los comentarios de la siguiente manera se han hecho antes en los 212 comentarios anteriores y no es necesario repetirlos:

  • TypeScript debería introducir un cambio radical e impracticable en el comportamiento de verificación por primera vez; no, no lo hemos hecho antes y no tenemos la intención de hacerlo en el futuro, por favor, participe más en los objetivos de diseño a largo plazo. del proyecto antes de venir aquí para abogar por cambios sin precedentes
  • Argumentos basados ​​en la idea de que nadie ha tenido en cuenta el hecho de que existe una bandera de configuración: somos conscientes de que poner cosas detrás de una bandera significa que no es necesario que estén encendidas de forma predeterminada, este no es el primer rodeo de nadie.
  • ¿Cuál es el retraso, solo hazlo ya! - bueno ahora que lo pones así estoy convencido 😕

Hay desafíos prácticos y técnicos muy reales que deben resolverse aquí y es decepcionante que no podamos hacer un trabajo significativo en este hilo porque está lleno de gente que sugiere que cualquier cosa detrás de una bandera no tiene consecuencias reales o que cualquier error que pueda be ts-ignore 'd no es una carga de actualización.

@RyanCavanaugh

gracias por tu resumen.

Comprométase más con los objetivos de diseño a largo plazo del proyecto antes de venir aquí para abogar por cambios sin precedentes.

He eliminado mi comentario y, si lo desea, también puedo eliminar este comentario. Pero cómo entender el primer objetivo del proyecto:

1. Identifique estáticamente los constructos que probablemente sean errores.

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

No entiendo su comentario citado, porque el acceso al índice no verificado puede conducir a un error de tiempo de ejecución. Si en JavaScript esperas cosas así, en TypeScript obtienes una confianza peligrosa e incorrecta. Los tipos incorrectos son peores que any .

bueno ahora que lo pones así estoy convencido

Por supuesto, usted es uno de los mantenedores principales y gracias a usted, al equipo y a los colaboradores por ts. Pero ya no se trata solo de ti. Compare los votos a favor: 365 con los votos en contra: ¡6! ¡Solo 6! Muestra una gran demanda de seguridad de tipos.

Pero hablemos de soluciones. ¿Hay alguna solución que el equipo pueda hacer o se le ocurra?

¿Puede explicar un poco más qué está mal con ts-ignore en este caso? Quiero decir que se puede automatizar con herramientas como codemod y no cambia el comportamiento del código actual. Por supuesto, no es una buena solución, pero bueno, es una forma posible de introducir este cambio sin banderas.

¿Qué opinas sobre la publicación automática de una versión parcheada de ts, por ejemplo 4.0.0-breaking ? Introduce un poco (¿mucho?) Trabajo en los conflictos, pero permite a todos probar los cambios y preparar la base del código (no solo para esta solicitud de función). Esto se puede hacer por un período de tiempo limitado, como de 3 a 6 meses. Seríamos los primeros en utilizar esta versión.

ya no. Compare los votos a favor: 365 con los votos en contra: ¡6! ¡Solo 6! Muestra una gran demanda de seguridad de tipos.
- @Bessonov

ja ja.
No es que esté de acuerdo con toda la publicación de
hay que millones? ¿¡¿Usuarios de TS por ahí?!? 365 quieren esta función lo suficiente como para comentar y votar en este hilo ...

No hay ninguna notificación de github, pero para todos los que intenten jugar con un índice indefinido de este problema, echen un vistazo al borrador de PR arriba de mi comentario.

@RyanCavanaugh muchas gracias por darnos la oportunidad de jugar con él. Lo ejecuté contra un código base bastante pequeño (~ 105 archivos ts (x)) y jugué un poco con él. No encontré ningún problema importante. Una línea que se cambió de:

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

para:

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

Lo intentaré en un proyecto mediano la semana que viene.

@ lonewarrior556 es 60 veces más y está en la primera página si lo ordena por votos a favor :)

@Bessonov Creo que deberías probarlo en la base de código del compilador de TypeScript, probablemente causará una rotura masiva debido al uso no trivial de bucles for.

Incluso podría tener banderas para optar por entrar / salir para proporcionar compatibilidad con versiones anteriores.

Cada indicador que agrega es otra matriz de configuración que debe probarse y ser compatible. Por esa razón, TypeScript quiere mantener las banderas al mínimo.

@MatthiasKunnen Si esta fuera una pequeña característica de caso de borde, estaría de acuerdo en que esa es una razón viable para no agregar una bandera para esto. Pero el acceso directo a la matriz aparece en el código con bastante frecuencia, y es tanto un agujero flagrante en el sistema de tipos como un cambio importante que solucionar, por lo que creo que se justificaría una bandera.

Una bandera es el enfoque incorrecto para esto, en parte debido a la combinación
problema. Deberíamos llamar a esto TypeScript v5 ... ese tipo de enfoque
convierte el número de combinaciones comprobables de 2 ^ N a solo N ...

El sábado 15 de agosto de 2020 a las 15:56, Michael Burkman [email protected]
escribió:

@MatthiasKunnen https://github.com/MatthiasKunnen Si esto fuera algo
característica de caso de borde pequeño, entonces estoy de acuerdo en que esa es una razón viable para no
agregue una bandera para esto. Pero el acceso directo a la matriz se muestra en el código bastante
con frecuencia, y es a la vez un agujero evidente en el sistema de tipos y también un
romper el cambio para arreglar, así que creo que una bandera estaría justificada.

-
Estás recibiendo esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/microsoft/TypeScript/issues/13778#issuecomment-674408067 ,
o darse de baja
https://github.com/notifications/unsubscribe-auth/AAADY5AXEY65S5HDGNGIPZDSA2O3FANCNFSM4C6KEKAA
.

Acabo de tener un error indefinido inesperado que derribó mi aplicación para cientos de usuarios, un error que se habría detectado en el momento de la compilación si este tipo de verificación estuviera en su lugar. TypeScript ha sido una manera asombrosa para nosotros de entregar software más confiable, pero su gran utilidad se ve socavada por esta única omisión.

¿Qué tal si las próximas 10 personas que quieran comentar aquí simplemente prueben el borrador de PR # 39560 en su lugar?

Esto es muy molesto si desea habilitar la regla @typescript-eslint/no-unnecessary-condition ESLint porque luego se queja de todas las instancias de

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

Piensa que es una condición innecesaria (¡porque Typescript dice que lo es!) Pero no lo es. Realmente no quiero tener que agregar // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition todo mi código base.

Si arreglar esto correctamente es demasiado para el trabajo de los programadores de Javascript perezosos, tal vez podríamos tener una sintaxis alternativa de acceso a la matriz que agregaría undefined , por ejemplo

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

(sugerencia de sintaxis aleatoria; se aceptan alternativas)

@Timmmm por favor lea el comentario justo encima del que hizo. Hay una compilación que puede probar que implementa una versión de esta sugerencia; puede informarnos allí si resuelve su problema eslint .

@ the0rted Leí ese comentario. No implementa una versión de mi sugerencia. Tal vez usted sólo debe leer de nuevo.

Lo siento, cuando dije "esta" sugerencia me refería a la capacidad en el OP, es decir, tratar array_of_T[i] como T | undefined . Veo que está preguntando acerca de una sintaxis para marcar un operador de índice específico como "tal vez indefinido", pero si usa la implementación en el PR de Ryan, no la necesitaría, porque todos los operadores de índice estarían "tal vez indefinidos". . ¿No satisfaría eso tu necesidad?

Sí, lo haría, si / cuando aterrice, definitivamente lo usaré. Pero de esta discusión tuve la impresión de que mucha gente se resistía porque agrega una bandera adicional y hace que el código que parece que debería funcionar más complicado (el simple ejemplo de bucle for).

Así que quería ofrecer una sugerencia alternativa, pero aparentemente la gente la odia. : - /

Se agradecen todas las aportaciones bien intencionadas, no las tomes a mal @Timmmm. Creo que los votos negativos solo transmiten que esto ya es un terreno muy trillado en este hilo en este momento.

Para mí, agregar una bandera que mejore la seguridad de los tipos en todos los ámbitos tiene un costo mucho menor que introducir una nueva sintaxis de inclusión voluntaria.

No lo mencioné anteriormente, pero ya existe una sintaxis para anulaciones únicas: if ((some_array[i] as MyType | undefined) === undefined) . No es tan conciso como una nueva abreviatura, pero es de esperar que no sea una construcción que tenga que usar con mucha frecuencia.

_ Publicado originalmente por @osyrisrblx en https://github.com/microsoft/TypeScript/issues/40435#issuecomment -690017567_

Si bien esta parece una opción mucho más segura, sería bueno optar por no participar en este comportamiento con una sintaxis separada para las raras ocasiones en las que está 100% seguro de que siempre está definido.

¿Quizás !: podría afirmar siempre definido?

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

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

Esa sugerencia caracteriza mal el problema. El problema no es la comprensión de TypeScript del tipo en sí. El problema proviene de _acceder_ a los datos, que es donde ya se ha propuesto una nueva sintaxis anteriormente en este hilo.

Puedes probar esto en la versión beta de TypeScript 4.1, que saldrá pronto ™ (¡o esta noche si lo necesitas ahora mismo! 🙂)

Lo siento si esto se ofreció antes, pero ¿podría ser compatible con {[index: string]?: number} // -> number | undefined ? Es coherente con la sintaxis de las propiedades opcionales de la interfaz: se requiere de forma predeterminada, posiblemente sin definir con "?".

La opción del compilador en 4.1 es genial, pero tener un control más granular también sería bueno.

Si quieres probarlo en tu repositorio (me tomó un poco averiguarlo):

  1. Instalar mecanografiado @ next yarn (add|upgrade) typescript@next
  2. Agregar bandera (para mí en tsconfig.json) "noUncheckedIndexedAccess": true

En el proceso de habilitar esta regla en mi proyecto, encontré este interesante error:

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];

En este caso, no esperaba que myRecord[key] devolviera el tipo MyRecord[Key] | undefined , porque key está restringido a keyof MyRecord .

Actualización : problema archivado https://github.com/microsoft/TypeScript/issues/40666

¡Eso es probablemente un error / descuido!

En el proceso de habilitar esta regla en mi proyecto, encontré este interesante error:

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];

En este caso, no esperaba que myRecord[key] devolviera el tipo MyRecord[Key] | undefined , porque key está restringido a keyof MyRecord .

Yo diría que es un error. Básicamente, si keyof Type solo incluye tipos literales de cadena / número (en lugar de incluir realmente string o number ), entonces Type[Key] donde Key extends keyof Type no debería incluir undefined , creo.

enlace cruzado con # 13195, que también analiza las diferencias y similitudes entre "aquí no hay propiedad" y "aquí hay una propiedad, pero es undefined "

Seguimiento de algunos problemas relacionados con las limitaciones de diseño aquí

  • # 41612

FYI: Atrapé dos errores en mi base de código gracias a esta bandera, ¡muchas gracias!
Es un poco molesto que verificar arr.length > 0 no sea suficiente para proteger arr[0] , pero eso es un inconveniente menor (puedo reescribir el cheque para hacer feliz a tsc) en comparación con la seguridad adicional, en mi opinión.

@rubenlg Estoy de acuerdo con arr.length . Por solo verificar el primer elemento, reescribí nuestro código como

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

Pero hay algunos lugares donde hacemos if (arr.length > 2) o más, que son un poco incómodos. Sin embargo, no creo que la comprobación .length sea ​​totalmente segura de todos modos, ya que puede modificarla:

const a: number[] = [];

a.length = 1;

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

Imprime undefined .

Sin embargo, no creo que la comprobación .length sea ​​totalmente segura, ya que puede modificarla:

const a: number[] = [];

a.length = 1;

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

Imprime undefined .

Básicamente, sería una matriz dispersa, que está fuera del alcance de este tipo de cosas. Siempre hay cosas no estándar o disimuladas que se pueden hacer para sortear el verificador de tipos.

Siempre hay una forma de invalidar la suposición anterior, y ese es el problema. Analizar todas las rutas de ejecución posibles sería demasiado costoso.

const a: number[] = [1]
if (a.length > 0) {
    a.pop();
    console.log(a[0])
}
¿Fue útil esta página
0 / 5 - 0 calificaciones