将类型不兼容的函数分配给 ref 属性

问题描述

为什么下面的代码不会导致编译时错误?

import * as React from 'react';

export class Test extends React.Component {
  private _onReferenceUpdated = (ref: HTMLCanvasElement) => {
    ref.width = 4; // This could throw,when ref is null
  };

  render(): JSX.Element {
    return (
      <canvas ref={this._onReferenceUpdated} />
    );
  }
}

ref 属性被推断为

(JSX attribute) React.ClassAttributes<HTMLCanvasElement>.ref?: string | ((instance: HTMLCanvasElement | null) => void) | React.RefObject<HTMLCanvasElement> | null | undefined

这似乎是正确的(string 有点奇怪,但我想这只是一般的属性)。 (ref: HTMLCanvasElement) => void 如何分配给 (instance: HTMLCanvasElement | null) => void

解决方法

答案就在您的问题中。

正如您所指出的,ref 的类型被推断为:

(JSX attribute) React.ClassAttributes<HTMLCanvasElement>.ref?: string | ((instance: HTMLCanvasElement | null) => void) | React.RefObject<HTMLCanvasElement> | null | undefined

这是一个“联合”类型,这意味着由管道 (|) 分隔的任何类型在这里都有效。所以 ref 可以是以下任何一种:

  • (JSX attribute) React.ClassAttributes<HTMLCanvasElement>.ref?: string
  • ((instance: HTMLCanvasElement | null) => void)
  • React.RefObject<HTMLCanvasElement>
  • null
  • undefined

好吧,您已经定义了 _onReferenceUpdated 属性,您将其传递给 ref,如下所示:

  private _onReferenceUpdated = (ref: HTMLCanvasElement) => {
    ref.width = 4; // This could throw,when ref is null
  };

所以 _onReferenceUpdated 的类型是 (ref: HTMLCanvasElement) => void,它很容易匹配我们上面的有效 ref 类型之一:((instance: HTMLCanvasElement | null) => void)

请注意,包装括号没有意义,它们只是为了提高可读性。另请注意,参数名称在一种类型中为“ref”而在另一种中为“instance”并不重要。重要的是函数参数的顺序类型,以及返回类型。

在 TypeScript 中,做这样的事情是完全有效的:

let Foo = (a: string) => {}; // inferred type of Foo is "(a: string) => void"

Foo = (b: string) => {}; // no error; order and type of args and return type match
Foo = (c: number) => {}; // error! type of arguments don't match
,

免责声明:我只是进行了足够的研究以了解问题的轮廓。如果在不同的问题中有适当的解释,或者有更好理解的人愿意提供,请随时补充。


在 Typescript 2.6 中,添加了 strict function types 设置,现在可以逆变检查函数参数。因此,

const f = (p: HTMLCanvasElement) => void p;
const g: (p: HTMLCanvasElement | null) => void = f;

是启用该设置的错误。但是,遗憾的是,由于似乎是 design limitation,因此存在问题,例如TSX 中的 ref 道具:

没有办法告诉 TypeScript “ref” prop 实际上是逆变的

接下来是 bivarianceHack,或者换句话说,ref 的行为就像 strictFunctionChecks 被禁用一样。那么,HTMLCanvasElementHTMLCanvasElement | null 的子类型,并被接受。

当遵循类型定义时,这也是可见的,这导致:

type RefCallback<T> = { bivarianceHack(instance: T | null): void }["bivarianceHack"];

我觉得这很可悲(尤其是,这种 hack 在类型注释中不可见,至少在 vscode 中不可见),欢迎任何适当的解决方案或对该主题的更新。至少,我正在考虑一个 ESLint 打字稿规则,它只是以硬编码的方式强制传递给 ref 的任何函数的参数可以为空。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...