【问题标题】:TypeScript + React: enforce correct ref propsTypeScript + React:强制执行正确的 ref props
【发布时间】:2019-10-16 03:11:07
【问题描述】:

我刚刚被运行时异常所困扰,因为我将 React ref 传递给了错误的组件。

我想知道 TypeScript 是否/如何将我的培根保存在这里?

简化的测试用例:

import * as React from 'react';

class Modal extends React.Component<{}> {
    close = () => {};
}

declare const modal: Modal;

modal.close();

const modalRef = React.createRef<Modal>();

// Let's try giving this ref to the correct component…
// No error as expected :-)
<Modal ref={modalRef} />;

class SomeOtherComponent extends React.Component<{}> {}

// Let's try giving this ref to the wrong component…
// Expected type error but got none! :-(
<SomeOtherComponent ref={modalRef} />;

// Now when we try to use this ref, TypeScript tells us it's safe to do so.
// But it's not, because the ref has been incorrectly assigned to another component!
if (modalRef.current !== null) {
    modalRef.current.close() // runtime error!
}

【问题讨论】:

  • 您可以将modalRef 输入为Modal 吗? const modalRef:Modal = ...
  • 不,因为 refs 是一种独特的类型。 (他们有一个名为current 的属性。)
  • React.RefObject&lt;Modal&gt; 怎么样?
  • 这不会改变任何东西,因为这与我的示例中 createRef 返回的类型相同。
  • TypeScrip 不捕获所有错误。因为您也可以将字符串分配给错误的函数。它在语法上是正确的。但在语义上不正确。我只是在类似的情况下使用React.createRef&lt;ReactInstance&gt;()。这样,两个组件都只是一个反应实例。

标签: reactjs typescript ref


【解决方案1】:

ref 没有出现任何错误的原因在于,您的两个组件都是示例中相同基类 React.Component 的派生,但它们没有任何不同的属性并且没有明确的接口。

检查 Typescript 中关于类型兼容性的部分: https://www.typescriptlang.org/docs/handbook/type-compatibility.html

由于 Typescript 使用结构而不是名义子类型,因此可以在以下情况下合并和比较类:

  • 两者具有相同的属性成员
  • 两者都没有任何属性成员(这不是一个现实的情况,但它就是您所拥有的)
  • 一个类可以有其他类没有的成员,但前提是它具有其他类的所有属性。例如。它可以添加东西,但不能引入新东西
  • 构造函数可以不同
  • 您还可以重新定义相同的属性成员

在所有其他情况下,它们是不兼容的。

如果您重写为:

React.createRef&lt;HTMLInputElement&gt;();

例如,您将清楚地看到 ref 在您的组件上被概述为不兼容。

这是因为 React.Component 与 HTMLElement 类型不兼容(HTMLInputElement 是衍生出来的,例如这些类具有不同的属性)。

这并不是说这个断言不起作用,更多的是它在底层是如何工作的,它遵循上面列出的规则。

回到手头的问题

根据您的具体情况,您可以采取多种措施来避免此类问题,

你有 2 个组件,具有不同的 react props。

如果你的组件有不同的 props,为两者创建接口,你就不会遇到这个问题,因为它会返回一个类型错误,即这些组件上的 Props 不匹配,这是真的。

你有 2 个组件,没有自定义反应属性

您当前的问题。如果您向 SomeOtherComponent 添加了一个新的属性成员,该成员在 Modal 类中不存在,例如 open = () =&gt;,那么它们将被解析为不同的。 p>

总结

如果你不能添加一个接口,并且你在这两个类中没有不同的属性成员,那么这是同一个类就足够现实了。

您只需要找出一种更好的方法来在这两种情况下使用它。

【讨论】:

  • 你的解释很有道理,谢谢。很遗憾,我唯一的选择是向一个不需要任何道具的组件添加一个假道具——但至少这是一个选择。我希望 TypeScript 对此有更好的解决方案,因为这对我来说似乎是一个常见的用例。
  • 没问题。是的,TS 中的类型推断并不理想,但对于大多数场景来说仍然足够好。这里的问题实际上是为什么你的类如此相似以至于它们通过 ref 场景进行类型推断,并且它们可以合并到 1 个单个类中,但是如果没有真实的例子就很难说。
  • 更新:我向TSgithub.com/microsoft/TypeScript/issues/31798提交了一个建议
【解决方案2】:

可能的解决方案是对类和引用使用接口。

interface IModal {
  close(): void;
}

class Modal extends React.Component<{}> implements IModal {
    close = () => {};
}

const modalRef = React.createRef<IModal>();

<Modal ref={modalRef as React.RefObject<Modal>}/>
<SomeOtherComponent ref={modalRef} />
// Type 'RefObject<IModal>' is not assignable to type 'string | ((instance: //SomeOtherComponent | null) => void) | RefObject<SomeOtherComponent> | null | undefined'.
//  Type 'RefObject<IModal>' is not assignable to type 'RefObject<SomeOtherComponent>'.
//    Type 'IModal' is missing the following properties from type 'SomeOtherComponent': render, context, setState, forceUpdate, and 3 more.  TS2322 

【讨论】:

  • 这需要在有效情况下进行类型断言,这是不好的做法。我必须小心,仅在技术上有效时才使用类型断言。是什么阻止我做&lt;SomeOtherComponent ref={modalRef as React.RefObject&lt;Modal&gt;} /&gt;;
  • 在正确的 Typescript 类型断言的情况下,没有什么能阻止你写 ref={modalRef as any}
  • @IgorTkachuk 转换类型不会解决运行时错误。这是一个编译器黑客,应尽可能避免。此外,理想情况下,不应使用强制转换为 any(除了强制转换为对象,其中 any 优先于对象)。另一方面,将这两种类型转换为 any 并不能解决这里的问题,因为它们已经是相同的“相似”类型,所以这将是后退,而不是前进。
猜你喜欢
  • 2022-01-08
  • 2021-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-04
  • 1970-01-01
  • 1970-01-01
  • 2018-11-14
相关资源
最近更新 更多