Careeroza
Generic Constraints with Conditional Types
Constraining by Property Type
A constraint can be computed. KeysOfType<T, V> gives only the keys whose value type is V, so a function can accept "any numeric field of T".
Example
type KeysOfType<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T];
type Product = { id: number; name: string; price: number; stock: number; brand: string };
function sumBy<T, K extends KeysOfType<T, number>>(rows: T[], key: K): number {
return rows.reduce((s, r) => s + (r[key] as number), 0);
}
function groupBy<T, K extends KeysOfType<T, string>>(rows: T[], key: K): Record<string, T[]> {
const out: Record<string, T[]> = {};
for (const r of rows) (out[r[key] as string] ??= []).push(r);
return out;
}
const rows: Product[] = [
{ id: 1, name: "Kettle", price: 1299, stock: 5, brand: "Prestige" },
{ id: 2, name: "Cooker", price: 1899, stock: 2, brand: "Prestige" },
{ id: 3, name: "Iron", price: 999, stock: 8, brand: "Philips" },
];
console.log("stock:", sumBy(rows, "stock"));
console.log(Object.fromEntries(Object.entries(groupBy(rows, "brand")).map(([b, ps]) => [b, ps.length])));Output
stock: 15
{ Prestige: 2, Philips: 1 }Using a Non-numeric Key
Example
"name" is a string field, so it is not allowed for sumBy (TS2345).
type KeysOfType<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T];
declare function sumBy<T, K extends KeysOfType<T, number>>(rows: T[], key: K): number;
sumBy([{ name: "Kettle", price: 1299 }], "name");Compiler output (tsc)
Line 4: error TS2345: Argument of type '"name"' is not assignable to parameter of type '"price"'.
const Type Parameters
Example
TypeScript 5.0 added const type parameters: the argument is inferred as if written with as const, without the caller adding it.
function defineRoles<const R extends readonly string[]>(roles: R): R {
return roles;
}
function routeOf<const P extends string>(path: P): { path: P } {
return { path };
}
const roles = defineRoles(["admin", "seller", "buyer"]); // readonly ["admin", "seller", "buyer"]
type Role = (typeof roles)[number];
const r: Role = "seller";
const home = routeOf("/home"); // { path: "/home" }
console.log(r, roles.length, home.path);Output
seller 3 /home