typescript - Hur typar man `K extends keyof T` när T kan vara undefined?
Jag vill lägga till TypeScript-typer till denna JS-funktion:
function getProp(obj, prop) {
if (!obj) {
return undefined;
}
return obj[prop];
}
Om det inte fanns något krav på att hantera undefined, skulle detta fungera:
function getProp<T, P extends keyof T>(obj: T, prop: P): T[P] {
// ...
}
Men med undefined fungerar inte detta naiva tillvägagångssätt med | undefined:
function getProp<T, P extends keyof T>(obj: T | undefined, prop: P): T[P] | undefined {
if (!obj) {
return undefined;
}
return obj[prop];
}
const obj1 = {
id: 1,
name: 'obj1',
};
const obj2 = undefined;
getProp(obj1, 'id');
getProp(obj2, 'id');
^^^ Argument of type '"id"' is not assignable to parameter of type 'never'.ts(2345)
Lekplats.
Vad är det bästa sättet att skriva detta på?
Svar #1
Jag tror att om jag skulle försöka gå all-in och göra så att getProp()-funktionen accepterar alla typer av obj-parametrar och sedan tillåta vilken nyckel som helst om obj kanske är undefined, skulle jag göra något liknande detta:
function getProp<T,
P extends T extends any ? (undefined extends T ? keyof any : keyof T) : never
>(
obj: T,
prop: P
): T extends any ? (P extends keyof T ? T[P] : undefined) : never;
function getProp<T, P extends keyof T>(obj: T | undefined, prop: P): T[P] | undefined {
if (typeof obj === "undefined") {
return undefined;
}
return obj[prop];
}
Det är superkomplicerat men det har några möjligtvis önskvärda beteenden:
const obj1 = {
id: 1,
name: 'obj1',
};
const obj2 = undefined;
const items = [obj1];
getProp(items.find(i => i.id === 1), "id"); // number | undefined
getProp(obj1, "id"); // number
getProp(obj2, "who knows"); // undefined
getProp(Math.random() < 0.5 ? { a: 1 } : { a: "b" }, "a"); // string | number
getProp(Math.random() < 0.5 ? { a: 1 } : { b: 1 }, "z"); // error! "z" not "a" | "b"
getProp(Math.random() < 0.5 ? { a: 1 } : { b: 1 }, "a"); // number | undefined
Men för användningsfallet som du beskrev är du bara intresserad av att skicka in undefined i den mån obj kan vara T | undefined för någon specifik T, och du planerar aldrig att faktiskt kalla getProp(undefined, "something"). I så fall fungerar din "naiva" metod tillräckligt bra:
function getProp<T, P extends keyof T>(obj: T | undefined, prop: P): T[P] | undefined {
if (typeof obj === "undefined") {
return undefined;
}
return obj[prop];
}
Observera :
getProp(items.find(i => i.id === 1), "id"); // number | undefined
getProp(obj1, "id"); // number | undefined
getProp(obj2, "who knows"); // error!
getProp(Math.random() < 0.5 ? { a: 1 } : { a: "b" }, "a"); // string | number | undefined
getProp(Math.random() < 0.5 ? { a: 1 } : { b: 1 }, "z"); // error! "z" not "a" | "b"
getProp(Math.random() < 0.5 ? { a: 1 } : { b: 1 }, "a"); // number | undefined
Möjligen är det enda som är "dåligt" med detta att alla utskrifter av getProp() inkluderar undefined i deras typ, även när du anropar getProp(obj, "id"), ett känt definierat obj med ett känd-närvarande "id". Men det verkade vara vad du ville, så där har du det. Du svarade i princip på din egen fråga!
Så, eh, hoppas det hjälper. Lycka till!
Länk till kod
Svar #2
Jag tror att detta är vad du vill uppnå:
function getProp<O, P extends O extends undefined ? any : keyof O>(obj: O, prop: P) {
if (!obj) {
return undefined;
}
return obj[prop];
}
const obj1 = {
id: 1,
name: 'obj1',
};
const obj2 = undefined;
getProp(obj1, 'id');
getProp(obj2, 'id');
Så om den första parametern är undefined sätter jag den andra typen som any. Jag typar inte returvärdet eftersom det infereras fint. Playground
