The keyof Operator
The tracker lets users sort tasks by any column: id, title or done. A sortBy(key: string) function accepts "titel" just as happily as "title", and the bug only appears when the list refuses to sort. keyof lets the compiler know exactly which keys exist.
The rule
keyof T is the union of T's property names.
type Task = { id: number; title: string; done: boolean };
type TaskKey = keyof Task; // "id" | "title" | "done"
let k: TaskKey = "title"; // fine
k = "titel"; // error: not a key of Taskkeyof is a type operator: it takes an object type and produces a union of string literal types, one per property. Add a property to Task and TaskKey updates itself.
Its natural partner is the indexed access type T[K], which looks up the type of a property:
type TitleType = Task["title"]; // string
type AnyValue = Task[keyof Task]; // number | string | booleanPut them together with a generic function and you get a property reader that is safe for every key:
function getField<K extends keyof Task>(task: Task, key: K): Task[K] {
return task[key];
}
const t: Task = { id: 1, title: "Buy milk", done: false };
getField(t, "done"); // type: boolean
getField(t, "title"); // type: string
getField(t, "owner"); // error: "owner" is not a key of TaskK extends keyof Task means "K must be one of Task's keys". The return type Task[K] then follows whichever key was passed. Do not worry if generics are new — here the pattern is all you need.
keyof also pairs with typeof, which turns a value into its type:
const labels = { todo: "To do", done: "Done" };
type LabelKey = keyof typeof labels; // "todo" | "done"The shortcut
To work out keyof X, list the property names of X and put quotes around each. That list, joined as a union, is the answer.
List the keys
Task has
id,title,done, sokeyof Taskis"id" | "title" | "done".
Drills
Answer fast — trust the rule.
What is keyof { x: number; y: number }?
With type Task = { id: number; title: string; done: boolean }, what is Task["done"]?
function getField<K extends keyof Task>(task: Task, key: K): Task[K] — which call is an error?
What does keyof typeof labels give for const labels = { todo: "To do", done: "Done" }?
Remember
- keyof T is the union of T's property names.
- List the property names, add quotes, join with a union — that is keyof.
- Trap:
key: stringaccepts typos;key: keyof Tdoes not.
