【问题标题】:Assigning function with incompatible type to ref attribute将具有不兼容类型的函数分配给 ref 属性
【发布时间】:2021-04-07 09:23:39
【问题描述】:

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

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&lt;HTMLCanvasElement&gt;.ref?: string | ((instance: HTMLCanvasElement | null) =&gt; void) | React.RefObject&lt;HTMLCanvasElement&gt; | null | undefined

这似乎是正确的(string 有点奇怪,但我想这通常只是用于属性)。 (ref: HTMLCanvasElement) =&gt; void 如何分配给(instance: HTMLCanvasElement | null) =&gt; void

【问题讨论】:

    标签: reactjs typescript tsx


    【解决方案1】:

    答案就在你的问题中。

    如您所述,ref 的类型推断为:

    (JSX attribute) React.ClassAttributes&lt;HTMLCanvasElement&gt;.ref?: string | ((instance: HTMLCanvasElement | null) =&gt; void) | React.RefObject&lt;HTMLCanvasElement&gt; | null | undefined

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

    • (JSX attribute) React.ClassAttributes&lt;HTMLCanvasElement&gt;.ref?: string
    • ((instance: HTMLCanvasElement | null) =&gt; void)
    • React.RefObject&lt;HTMLCanvasElement&gt;
    • null
    • undefined

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

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

    所以_onReferenceUpdated 的类型是(ref: HTMLCanvasElement) =&gt; void,它很容易匹配我们上面的有效ref 类型之一:((instance: HTMLCanvasElement | null) =&gt; 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
    

    【讨论】:

    • 谢谢,但为什么这是有效的?在一个函数中,参数可能是null,而在另一个函数中,它不能。这也是真正的问题:ref 可能是null(当组件卸载时),在函数中忽略这一点是无效的。如果我手动执行相同操作,也会出现错误:i.imgur.com/cF796Xt.png - 这是我所期望的。
    【解决方案2】:

    免责声明:我只进行了足够深入的研究以了解问题的轮廓。如果对不同的问题有适当的解释,或者有更好理解的人愿意给出一个,请随时补充。


    在 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 的任何函数的参数以硬编码的方式可以为空。

    【讨论】:

      猜你喜欢
      • 2018-09-08
      • 2017-12-12
      • 2019-02-20
      • 2023-03-03
      • 2021-07-07
      • 2021-07-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多