为什么新的 `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 };
我以为我理解了新的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 };