Búsqueda de sitios web

Cómo proteger el código TypeScript para condiciones derivadas (modo estricto)


TypeScript (en modo estricto) admite proteger el código mediante el uso de condiciones previas como guardias, por lo que el siguiente error:

export const myFun1 = (data: { a?: string; b?: string }) => {
    const obj = { a: 1, b: 2 };
    return obj[data.a]; // Type 'undefined' cannot be used as an index type
}

Se puede evitar fácilmente con una especie de guardia condicional (if (!data.a) return;) como sigue:

export const myFun1 = (data: { a?: string; b?: string }) => {
    if (!data.a) return;
    const obj = { a: 1, b: 2 };
    return obj[data.a]; // No error
}

De la misma manera, tendríamos un error con la propiedad b que también podría evitarse:

export const myFun1 = (data: { a?: string; b?: string }) => {
    if (!data.a || !data.b) return;
    const obj = { a: 1, b: 2 };
    return obj[data.a] + obj[data.b]; // No error
}

Este código es 100% válido. No hay errores en absoluto. Pero ahora quiero agrupar las comprobaciones en un booleano para que se verifique para el return. Sorprendentemente, lo siguiente no funciona como esperaba y TypeScript sigue lanzando un error de tipo:

export const myFun1 = (data: { a?: string; b?: string }) => {
    const both = data.a && data.b;
    if (!both) return;
    const obj = { a: 1, b: 2 };
    return obj[data.a] + obj[data.b]; // Type 'undefined' cannot be used as an index type
}

¿Qué me estoy perdiendo? ¿Cuál es la razón detrás de este comportamiento extraño?

Esperaba que TypeScript no se quejara cuando ambos comprobaciones se agrupan en un booleano intermedio.

Respuesta #1

Typescript no es tan inteligente como para considerar both como una protección de tipo. Para el compilador both es independiente de data. La forma más simple sería usar directamente el siguiente código:

const func = (data: {a?: string; b?: string}) => {
   if (!(data.a && data.b)) return;
   // do stuff
}

De lo contrario, puedes usar la función assertiva:

function isRequiredData(data: Data): asserts data is Required<Data> {
  if (!data.a || !data.b) {
    throw new Error('data has missing properties');
  }
}

const func = (data: Data) => {
  isRequiredData(data);

  // data.a and data.b here are not optional
}

Esto todavía no corregirá todos los errores, ya que obj no tiene una anotación de tipo y el compilador obtiene el tipo del valor, que es {a: string; b: string}. Como la solución más sencilla, podemos tipar obj como un Record<string, number> y, para mayor seguridad en tiempo de ejecución, verificar si data.a y data.b están en el obj:

const func = (data: Data) => {
  isRequiredData(data);

  const obj: Record<string, number> = { a: 1, b: 2 };

  if (data.a in obj && data.b in obj) {
    return obj[data.a] + obj[data.b];
  }

  return -1;
};

parque infantil

Como alternativa, puedes usar un guardia de tipo personalizado en lugar de la función de aserción si no necesitas lanzar un error:

function isRequiredData(data: Data): data is Required<Data> {
  return !!data.a && !!data.b;
}

const func = (data: Data) => {
  if (!isRequiredData(data)) return;

  const obj: Record<string, number> = { a: 1, b: 2 };

  if (data.a in obj && data.b in obj) {
    return obj[data.a] + obj[data.b];
  }

  return -1;
};

parque infantil