为什么 getProp 必须写 K extends keyof T
Why does getProp need the constraint K extends keyof T
一句话:这个约束向编译器证明 「K 一定是 T 的属性名之一」。有了它,obj[key]才编译得过,返回类型才能精确写成 T[K] —— 一个约束同时解决合法性和精确性两件事。
不写约束报什么错:Type 'K' cannot be used to index type 'T'.不加约束时 K 和 T 之间没有任何关系, 编译器无法证明 obj 上存在这个键,索引表达式直接被拒。 这是泛型题里最常见的报错,认出它就知道缺的是约束。
为什么返回 T[K] 而不是 any:T[K] 是索引访问类型,在每个调用点按实参单独求值 ——getProp(user, "name") 得 string,getProp(user, "active") 得 boolean。 精确性的来源是 K 被推断成字面量类型(literal type)("name",而不是 string)。 返回 any 的版本编译也过,但类型信息在函数出口全部蒸发, 调用方从此拿着一个不受检查的值。
会追问:「那写(obj: object, key: string): any 有什么问题?」—— 能跑,但传错对象、拼错键、用错返回值,全都要等运行时才炸; junior 版和 senior 版的差距就在这。 「约束能带默认值吗?」—— 能:<T extends object = Record<string, unknown>>。 「为什么要两个类型参数?」—— 因为 K 的合法取值依赖 T。 「一个参数的取值范围由另一个参数决定」, 正是泛型约束的典型使用场景。
In one line: the constraint proves to the compiler that K is one of T’s property names. Only then does obj[key] compile, and only then can the return type be written precisely as T[K] — one constraint buys both legality and precision.
What fails without it: Type 'K' cannot be used to index type 'T'. With no constraint there is no relationship between K and T, so the compiler cannot prove the key exists on obj, and it rejects the index expression outright. It is the single most common error in generics questions — recognize it and you know a constraint is missing.
Why return T[K] rather than any: T[K] is an indexed access type, evaluated per call site against the actual argument — getProp(user, "name") gives string, getProp(user, "active") gives boolean. The precision comes from K being inferred as a literal type ("name", not string). The any version compiles too, but every bit of type information evaporates at the function exit, and callers are left holding an unchecked value.
Follow-up: “So what is wrong with (obj: object, key: string): any?” — it runs, but a wrong object, a misspelled key or a misused return value all wait until runtime to fail; that gap is the junior version versus the senior one. “Can a constraint have a default?” — yes: <T extends object = Record<string, unknown>>. “Why two type parameters?” — because the legal values of K depend on T. One parameter whose range is decided by another is exactly what generic constraints are for.