javascript - Typescript: ¿guardia de tipo para saber si una propiedad de un objeto está definida, cuando la clave es un tipo amplio?
Tengo una función que devuelve si una propiedad de un objeto es undefined. Necesito esta función en lugar de simplemente hacer obj[key] === undefined porque de lo contrario a veces recibiría Property 'foo' does not exist on type 'Bar'.. Es sencillo escribir el tipo cuando la clave de la propiedad es un literal. Es decir:
function hasDefinedProp<
Obj extends Partial<Record<string, any>>,
Prop extends string,
>(
obj: Obj,
prop: Prop,
): obj is Obj & Record<Prop, Prop extends keyof Obj ? Exclude<Obj[Prop], undefined> : unknown> {
return obj[prop] !== undefined;
}
const obj: Partial<Record<string, number>> = {};
if (hasDefinedProp(obj, 'foo')) {
obj.foo + 1; // No error.
obj.bar + 1; // "Object is possibly 'undefined'."
}
Sin embargo, esto no funciona cuando el tipo de la clave es un tipo amplio, es decir:
const obj: Partial<Record<string, number>> = {};
const key: string = '';
if (hasDefinedProp(obj, key)) {
obj[key] + 1; // No error.
obj.bar + 1; // No error. Should be "Object is possibly 'undefined'."
}
¿Es posible hacer que la protección de tipos funcione para tipos amplios?
Respuesta #1
Hasta donde sé, no es posible. Una vez que has añadido explícitamente el tipo string a const key: string='';, TS no puede reducir el tipo literal de key. Como resultado, se te permite usar cualquier cadena que quieras para acceder a una propiedad. TS no puede distinguir dos tipos con tipo string en este ejemplo:
const obj: Partial<Record<string, number>> = {};
const key: string = '';
if (hasDefinedProp(obj, key)) {
obj[key] + 1; // No error.
obj.bar + 1; // No error. Should be "Object is possibly 'undefined'."
}
Esto me recuerda un ejemplo con tipo explícito y aserción de tipo inmutable:
const record: Record<string, number> = {
a: 1
} as const;
type Keys = keyof typeof record // string instead of "a"
Significa que en este caso particular podría ser una buena idea prohibir el uso de tipos amplios:
type ForbidWide<Prop> = Prop extends string ? string extends Prop ? never : Prop : never
function hasDefinedProp<
Obj extends Partial<Record<string, any>>,
Prop extends string,
>(
obj: Obj,
prop: ForbidWide<Prop>,
): obj is Obj & Record<Prop, Prop extends keyof Obj ? Exclude<Obj[Prop], undefined> : unknown> {
return obj[prop] !== undefined;
}
const obj: Partial<Record<string, number>> = {};
const key: string = '';
hasDefinedProp(obj, key) // error
Parque infantil
