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

Posted

技术标签:

【中文标题】为啥新的 `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()`?为什么新的 `Pick<T, K extends keyof T>` 类型允许在 React 的 `setState()` 中使用 `K` 的子集? 【发布时间】:2017-07-25 18:56:31 【问题描述】:

我以为我理解了新的TS 2.1 Pick type 的用途,但后来我看到了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,然后它从允许 子集键变为需要所有键。这是一个很大的区别。 Here it is in the Playground。谁能解释这种行为?

【问题讨论】:

【参考方案1】:

简答:如果你真的想要一个显式类型,你可以使用Pick&lt;PersonProps, "age"&gt;,但是使用隐式类型更容易。

长答案:

关键是K 是一个泛型类型变量,扩展 keyof T

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

回忆Pick的定义是:

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

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

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

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

Pick&lt;T, K extends keyof T&gt;, Pick&lt;PersonProps, "name" | "age"&gt;, [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: numberPick&lt;PersonProps, "age"&gt; 类型是兼容的,所以你最好保留类型隐含:

const person =  age: 123 ;

【讨论】:

"age" 类型可以说是对"name" | "age" 类型的扩展。 啊,我没想到,我会认为"name" | "age" 扩展了@ 987654349@,不是相反。谢谢你的解释! 很抱歉再次碰到这个问题,但是虽然我现在理解这种行为(谢谢!),但我无法真正合理化 "age""age" | "name" 的超类型而不是子类型类型,即使编译器清楚地看到它。这似乎与看似等效的接口 name, ageage 相互关联的方式相反。你能解释一下这个理由吗? 在泛型类型约束中,也许最好将“扩展”理解为“是更具体的类型”。考虑type AB = "a" | "b";type ABC = "a" | "b" | "c";。如果您有一个变量const abc: ABC = "c";,那么您不能将它分配给AB 类型的变量,但是AB 类型的任何有效值都是ABC 的有效值。由于ABABC 的一种更具体的类型,所以AB 可以说是扩展 ABC (哎呀,我最后的评论我使用了超类型/子类型。)谢谢你的解释,我明白了这个意思。我想extends 对我来说对联合类型有不熟悉的含义,但我认为现在它更有意义了。谢谢。 感谢您的回答。但是 React 怎么可能在参数中使用部分状态而不将 state 参数中的属性声明为可选的呢?

以上是关于为啥新的 `Pick<T, K extends keyof T>` 类型允许在 React 的 `setState()` 中使用 `K` 的子集?的主要内容,如果未能解决你的问题,请参考以下文章

泛型工具及实现

interface BaseDao<T extends Serializable> 为啥要这样写

如何理解 Java 中的 lt;T extends Comparable<

Java 泛型 Class<? extends T>

Java泛型<? extends T>和<? super T>

ekmp模板