Привет .
Я Михаил, fullstack-разработчик, занимаюсь интернет-проектами: от сайтов до систем автоматизации.

TypeScript: описание типов сторонней UI-библиотеки React-компонентов, написанной на JavaScript

В кратции

Так уж случилось, что уже полгода я занимаюсь проектом на React+TypeScript. Ничего нового, наслаждаюсь прелестями статической типизации благодаря «ещё одному плагину для автодополнения кода».

И всё бы хорошо, но UI-компоненты, которые используются в нашем и других проектах компании, написаны без TypeScript, и описаний типов для них нет.

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

Учитывая, что недавно мы мигрировали с одной мажорной версии UI-библиотеки на другую, вполне мог остаться неактуальный код (спойлер: да, он оставался).

Было

<Field
  mode="password"
  value={password}
  onChange={handlePasswordChange}
/>

Стало

<Input.Password
  value={password}
  onChange={handlePasswordChange}
/>

К счастью, даже если библиотека написана без TypeScript, можно описать её типы, чтобы использовать их в типизированных проектах. Описание типов в таком случае представляет собой отдельный файл с расширением d.ts, который можно хранить как в коде сторонней библиотеки, так и в своём проекте.

Я выбрал последний вариант: хранить описание типов в своём проекте. Это наиболее простой способ: не нужно проносить изменения в стороннюю библиотеку, проходя публикацию новой версии и обновление версии в проекте.

Как используется библиотека

Библиотека устанавливается с помощью npm и поселяется в node_modules, версия фиксируется в package.json.

import { Input } from "@company/ui"

export const App = () => (
  <form onSubmit="handleSubmit">
    <Input.Text value="login" />
    <Input.Password value="password" />
    <Button isLoading={isLoading}>Войти</Button>
  </>
)

Описание типов

Создаём файл company.ui.d.ts в каталоге types.

Название файла не принципиально, размещение тоже. Главное, чтобы путь до этих файлов был указан в tsconfig.json.

Содержимое файла company.ui.d.ts:

declare namespace CompanyUi {
    interface InputPropsBase {
        value?: string;
        onChange?: (event: React.ChangeEvent) => void;
    }
    interface InputPasswordProps extends InputPropsBase {
        refWrapper?: (input: HTMLInputElement) => void;
    }
    interface InputProps extends InputPropsBase {
        type?: 'email' | 'tel' | 'text';
    }
    export class Input extends React.Component {
        static readonly Password = class extends React.Component {
            static theme: InputTheme;
        };
    }

    // Utils
    export const hello = (arg0: string) => void 0
}

declare module '@company/ui' {
    export import Input = CompanyUi.Input;
    export import hello = CompanyUi.hello;
}

Данный файл содержит описание компонента Input, а также его подкомпонента Password. Для наглядности добавил ещё описание утилиты, входящей в состав этой же библиотеки компонентов.

На самом деле, в рабочем проекте содержится описание более 10 компонентов, но все они описаны по тому же принципу.

Вывод

Библиотека описана, IDE теперь подсказывает, какие пропсы можно передавать, а при сборке мы получим ошибку, если решим передавать пропсы неправильно. Удобно.