使用 React 和 Typescript 描述柯里化状态处理程序的类型

Describe type of curried state handler with React and Typescript

我正在尝试描述处理更改事件以反映状态值的函数类型。

所需的实现如下所示:

handleChange: ChangeHandler<State> = field => value =>
    this.setState({ [field]: value });

给定状态:

interface State {
  title: string;
  description: string | null;
}

可以将此函数传递给组件:

<TextComponent onChange={this.handleChange('title')} />

我解决这个问题的最佳尝试是创建以下类型:

type ChangeHandler<S> = <K extends keyof S, V extends S[K]>(key: K) => (value: V) => void;

但是,这似乎只有在状态的所有属性都是可选的情况下才有效。我无法设法让它与可选和非可选属性的状态一起使用。

编译器错误:

[ts] Argument of type '{ [x: string]: V; }' is not assignable to parameter of type 'State | ((prevState: Readonly, props: Readonly) => State | Pick | null) | Pick<...> | null'.
Type '{ [x: string]: V; }' is missing the following properties from type 'Pick': title, description [2345]

您不能定义 V extends S[K],因为您可以直接内联此声明,如下所示:

type ChangeHandler<S> = <K extends keyof S>(key: K) => (value: S[K]) => void;

根本原因

这是由 TypeScript 的 known issue 引起的,其中计算的 属性 名称由 文字联合 组成。

handleChange中,field的类型从'title' | 'description'扩大到string,导致this.setState由于[=的方式而失败17=] 的类型已定义。

下面是setState的简化定义:

function setState<K extends keyof State>(s: Pick<State, K>): void {
    console.log(s);
}

将该定义应用于handleChange时,上面的K变为'title' | 'description's参数变为Pick<State, 'title' | 'description'>,本质上等同于State 界面。

因此,鉴于我们的 field 已扩大到 string 并且 this.setState 在这种情况下接受 State,我们将得到您所看到的错误因为我们使用以下类型调用 this.setState

{ [x: string]: V }

无法分配给 State,因为它缺少必需的参数 titledescription

解决方案

我们最好的选择是使用 类型断言 来消除错误,同时保持类型检查。更新后的定义如下:

let handleChange: ChangeHandler<State> = field => value =>
    setState({ [field]: value } as unknown as State);

let x = handleChange('title');

x('test'); // OK

x(50); // Error: should be string

参见包含上述代码的 this playground link


请参阅 discussion 上有关 DefinitelyTyped 的此特定问题的替代解决方案/方法。