TypeScript + React: принудительное использование правильных ссылок - PullRequest
1 голос
/ 30 мая 2019

Меня только что укусило исключение во время выполнения, потому что я передал ссылку React в неправильный компонент.

Интересно, мог ли 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 ]

1 голос
/ 30 мая 2019

Причина, по которой вы не получаете никаких ошибок с ref, заключается в том, что оба ваших компонента являются производными одного базового класса React.Component в вашем примере, но они не имеют различий свойства и не имеют явного интерфейса.

Проверьте раздел о совместимости типов в Typescript: https://www.typescriptlang.org/docs/handbook/type-compatibility.html

Поскольку Typescript использует структурные, а не номинальные подтипы, классы можно объединять и сравнивать как равные в следующих сценариях:

  • Оба имеют одинаковые члены собственности
  • Ни у кого нет каких-либо членов собственности (это не реалистичный случай, но это тот, который у вас есть)
  • У одного класса могут быть члены, которых у другого нет, но ТОЛЬКО если он имеет все свойства другого класса. Например. он может добавлять вещи, но не вводить новые вещи
  • Конструкторы могут отличаться
  • Вы также можете переопределить тот же элемент свойства

Во всех остальных случаях они несовместимы.

Если вы переписываете на:

React.createRef<HTMLInputElement>();

Вы четко увидите, что ссылки указаны как несовместимые, например, с вашими компонентами.

Это потому, что React.Component никоим образом не совместим с типом HTMLElement (который HTMLInputElement является производным от, например, эти классы имеют разные свойства).

Дело не в том, что это утверждение не работает, скорее в том, как именно оно работает под капотом, что соответствует правилам, изложенным сверху.

Вернуться к рассмотрению вопроса

Вы можете сделать несколько вещей, чтобы избежать подобных проблем, в зависимости от вашей ситуации,

У вас есть 2 компонента с различными реактивными характеристиками.

В случае, если у ваших компонентов разные реквизиты, создайте интерфейсы для обоих, и у вас не возникнет этой проблемы, поскольку она выдаст ошибку типа, что реквизиты на этих компонентах не совпадают, что верно.

У вас есть 2 компонента без пользовательских свойств реакции

Ваша текущая проблема. Если вы добавите новый элемент свойства в SomeOtherComponent , который не существует в классе Модальный , например, open = () =>, тогда они будут разрешены как разные.

Резюме

Если вы не можете добавить интерфейс, и у вас нет разных членов свойства в этих 2 классах, то вполне реально, что это один и тот же класс.

Вам просто нужно найти лучший способ, как использовать его для обоих сценариев.

0 голосов
/ 30 мая 2019

Возможные решения - использовать интерфейс для класса и ссылки.

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 
...