为什么新的 `Pick<T, K extends keyof T>` 类型允许 React 的 `setState()` 中的 `K` 的子集?

Why does the new `Pick<T, K extends keyof T>` type allow subsets of `K` in React's `setState()`?

我以为我理解了新的TS 2.1 Pick type, but then I saw how it was being used in the React type definitions的目的,但我不明白:

declare class Component<S> {
    setState<K extends keyof S>(state: Pick<S, K>, callback?: () => any): void;
    state: Readonly<S>;
}

这允许你这样做:

interface PersonProps {
  name: string;
  age: number;
}

class Person extends Component<{}, PersonProps> {
  test() {
    this.setState({ age: 123 });
  }
}

我在这里的困惑是 keyof S{ name, age } 但我只用 age 调用 setState() -- 为什么它不抱怨缺少 name?

我的第一个想法是,因为 Pick 是一种索引类型,所以它根本不需要所有键都存在。说得通。但是如果我尝试直接分配类型:

const ageState: Pick<PersonProps, keyof PersonProps> = { age: 123 };

确实抱怨缺少 name 键:

Type '{ age: number; }' is not assignable to type 'Pick<PersonProps, "name" | "age">'.
  Property 'name' is missing in type '{ age: number; }'.

我不明白这个。 似乎 我所做的只是用 S 已经分配给的类型填写 S,它允许 sub-将 键设置为需要 所有 键。这是一个很大的不同。 Here it is in the Playground。谁能解释这种行为?

简答:如果你真的想要显式类型,你可以使用Pick<PersonProps, "age">,但使用隐式类型更容易。

长答案:

关键是 K 是一个通用类型变量,它 扩展了 keyof T.

类型 keyof PersonProps 等于字符串联合 "name" | "age"。类型 "age" 可以说是对类型 "name" | "age".

的扩展

回想一下 Pick 的定义是:

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
}

这意味着对于每个 K,此类型描述的对象必须具有与 属性 K 中相同类型的 属性 P T。您的示例游乐场代码是:

const person: Pick<PersonProps, keyof PersonProps> = { age: 123 };

展开泛型类型变量,我们得到:

  • Pick<T, K extends keyof T>,
  • Pick<PersonProps, "name" | "age">,
  • [P in "name" | "age"]: PersonProps[P],最后
  • {name: string, age: number}.

这当然与 { age: 123 } 不兼容。如果你改为说:

const person: Pick<PersonProps, "age"> = { age: 123 };

那么,遵循相同的逻辑,person 的类型将适当地等同于 {age: number}

当然,无论如何,TypeScript 都会为您计算所有这些类型——这就是您得到错误的原因。由于 TypeScript 已经知道类型 {age: number}Pick<PersonProps, "age"> 是兼容的,因此您最好保持类型隐含:

const person = { age: 123 };