TypeScript + React:强制使用正确的引用道具

时间:2019-05-30 12:48:32

标签: reactjs typescript ref

我只是被运行时异常咬住了,因为我将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!
}

2 个答案:

答案 0 :(得分:1)

之所以没有在ref上出现任何错误,是因为您的示例中两个组件都是同一基类 React.Component 的派生类,但它们却没有任何不同的属性,并且没有明确的界面。

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

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

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

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

以防您重写为:

React.createRef<HTMLInputElement>();

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

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

并不是说这个断言不起作用,更多的是它在幕后如何精确运作的情况,这遵循上面概述的规则。

回到当前问题

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

您有2个组成部分,具有不同的反应道具。

如果您的组件具有不同的道具,请为两者创建接口,并且不会出现此问题,因为它将返回类型错误,这些组件上的道具不匹配,这是正确的。

您有2个组件,没有自定义react属性

您当前的问题。如果您将新的属性成员添加到 Modal 类中不存在的 SomeOtherComponent 中,例如open = () =>,则它们将被解析为不同的属性。

摘要

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

您只需要找到一种在两种情况下都可以使用的更好方法。

答案 1 :(得分:-1)

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

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