TypeScript в проектах JavaScript (JSDoc)

JSDoc — это стандарт документирования JavaScript‑кода с помощью специальных комментариев. В сочетании с TypeScript он позволяет:

  • добавлять аннотации типов в .js-файлы без их конвертации в .ts;
  • получать проверку типов на уровне TypeScript;
  • сохранять читаемость кода и его документацию в одном месте.

Ключевое преимущество - вы получаете типобезопасность TypeScript, оставаясь в рамках синтаксиса JavaScript.

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


Как настроить JSDoc для TypeScript

  1. Установите TypeScript (если ещё не установлен):

    
    npm install --save-dev typescript
    # или
    yarn add --dev typescript
    
    
  2. Создайте tsconfig.json с необходимыми опциями:

    
    {
      "compilerOptions": {
        "allowJs": true,      // разрешать JavaScript-файлы
        "checkJs": true,     // проверять типы в .js-файлах
        "noEmit": true         // не компилировать, только проверять
      },
      "include": ["**/*.js"] // проверять все .js-файлы
    }
    
    
  3. Добавьте // @ts‑check в начало каждого .js-файла, где нужна проверка типов.

Пример: JSDoc для типобезопасности


// @ts-check

/**
 * Складывает два числа.
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
function add(a, b) {
  return a + b;
}


Основные аннотации JSDoc

Ниже — ключевые теги JSDoc.

  1. @param — тип параметра функции:

    
    /**
     * @param {string} name — Имя пользователя
     * @param {number=} age — Возраст (опционально)
     */
    function greet(name, age) {
      // ...
    }
    
    
  2. @returns — тип возвращаемого значения:

    
    /**
     * @returns {boolean} — Истинность условия
     */
    function isValid() {
      return true;
    }
    
    
  3. @type — тип переменной или выражения:

    
    /** @type {string[]} */
    const tags = ['js', 'ts', 'doc'];
    
    
  4. @typedef — определение сложного типа (аналог interface или type в TS):

    
    /**
     * @typedef {Object} User
     * @property {number} id — ID пользователя
     * @property {string} username — Имя
     * @property {boolean} [active] — Активен ли (опционально)
     */
    
    /** @type {User} */
    const user = { id: 1, username: 'alice' };
    
    
  5. @callback — тип функции обратного вызова:

    
    /**
     * @callback Processor
     * @param {string} input — Входные данные
     * @returns {string} — Обработанный результат
     */
    
    
  6. @template — обобщённые типы:

    
    /**
     * @template T
     * @param {T} value — Любое значение
     * @returns {T} — То же значение
     */
    function identity(value) {
      return value;
    }
    
    
  7. @enum — перечисления:

    
    /** @enum {string} */
    const Color = {
      RED: 'red',
      BLUE: 'blue'
    };
    
    

Объекты и интерфейсы

Встроенные типы объектов


// @ts-check

/**
 * @param {{ firstName: string, lastName: string, age?: number }} person
 */
function greet(person) {
  return `Hello, ${person.firstName} ${person.lastName}`;
}

greet({ firstName: 'John', lastName: 'Doe' }); // OK
greet({ firstName: 'Jane' }); // Ошибка: отсутствует свойство 'lastName'

Использование @typedef для сложных типов


// @ts-check

/**
 * @typedef {Object} User
 * @property {number} id — ID пользователя
 * @property {string} username — Имя пользователя
 * @property {string} [email] — Необязательный адрес электронной почты
 * @property {('admin'|'user'|'guest')} role — Роль пользователя
 * @property {() => string} getFullName — Метод, возвращающий полное имя
 */

/** @type {User} */
const currentUser = {
  id: 1,
  username: 'johndoe',
  role: 'admin',
  getFullName() {
    return 'John Doe';
  }
};

// TypeScript предоставит автодополнение для свойств User
console.log(currentUser.role);

Расширение типов


// @ts-check

/** @typedef {{ x: number, y: number }} Point */

/**
 * @typedef {Point & { z: number }} Point3D
 */

/** @type {Point3D} */
const point3d = { x: 1, y: 2, z: 3 };

// @ts-expect-error — отсутствует свойство z
const point2d = { x: 1, y: 2 };


Типы функций

Объявления функций


// @ts-check

/**
 * Вычисляет площадь прямоугольника.
 * @param {number} width — Ширина прямоугольника
 * @param {number} height — Высота прямоугольника
 * @returns {number} Вычисленная площадь
 */
function calculateArea(width, height) {
  return width * height;
}

// TypeScript знает типы параметров и возвращаемое значение
const area = calculateArea(10, 20);

Функциональные выражения и обратные вызовы


// @ts-check

/**
 * @callback StringProcessor
 * @param {string} input
 * @returns {string}
 */

/**
 * @type {StringProcessor}
 */
const toUpperCase = (str) => str.toUpperCase();

/**
 * @param {string[]} strings
 * @param {StringProcessor} processor
 * @returns {string[]}
 */
function processStrings(strings, processor) {
  return strings.map(processor);
}

const result = processStrings(['hello', 'world'], toUpperCase);
// result будет ['HELLO', 'WORLD']

Перегрузки функций


// @ts-check

/**
 * @overload
 * @param {string} a
 * @param {string} b
 * @returns {string}
 */
/**
 * @overload
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
/**
 * @param {string | number} a
 * @param {string | number} b
 * @returns {string | number}
 */
function add(a, b) {
  if (typeof a === 'string' || typeof b === 'string') {
    return String(a) + String(b);
  }
  return a + b;
}

const strResult = add('Hello, ', 'World!'); // строка
const numResult = add(10, 20); // число


Продвинутые типы

Объединения и пересечения типов


// @ts-check

/** @typedef {{ name: string, age: number }} Person */
/** @typedef {Person & { employeeId: string }} Employee */
/** @typedef {Person | { guestId: string, visitDate: Date }} Visitor */

/** @type {Employee} */
const employee = {
  name: 'Alice',
  age: 30,
  employeeId: 'E123'
};

/** @type {Visitor} */
const guest = {
  guestId: 'G456',
  visitDate: new Date()
};

/**
 * @param {Visitor} visitor
 * @returns {string}
 */
function getVisitorId(visitor) {
  if ('guestId' in visitor) {
    return visitor.guestId; // TypeScript знает, что это гость
  }
  return visitor.name; // TypeScript знает, что это Person
}

Преобразованные и условные типы


// @ts-check

/** * @template T * @typedef {[K in keyof T]: T[K] extends Function ? K : never}[keyof T] MethodNames */

/** * @template T * @typedef {{ *   [K in keyof T as `get${'<' }Capitalize<string & K>{'>'}`]: () => T[K] * }} Getters */


/** @type {Getters<{ name: string, age: number }> } */
const userGetters = {
  getName: () => 'John',
  getAge: () => 30
};

// TypeScript проверяет возвращаемые типы
const name = userGetters.getName(); // строка
const age = userGetters.getAge(); // число


Импорт типов

Импорт типов из других файлов


// @ts-check

// Импорт типов из TypeScript‑файлов
/** @typedef {import('./types').User} User */


// Импорт типов из node_modules
/** @typedef {import('express').Request} ExpressRequest */


// Импорт с переименованием
/** @typedef {import('./api').default as ApiClient} ApiClient */

Создание файлов объявлений

Создание файла types.d.ts в проекте:


// types.d.ts
declare module 'my-module' {
  export interface Config {
    apiKey: string;
    timeout?: number;
    retries?: number;
  }

  export function initialize(config: Config): void;
  export function fetchData<T = any>(url: string): Promise<T>;
}

Затем используем его в JavaScript‑файлах:


// @ts-check

/** @type {import('my-module').Config} */
const config = {
  apiKey: '12345',
  timeout: 5000
};

// TypeScript обеспечит автодополнение и проверку типов
import { initialize } from 'my-module';
initialize(config);


Лучшие практики

При работе с JSDoc и TypeScript соблюдайте следующие рекомендации:

  • Включайте // @ts-check в начале файлов, где требуется проверка типов.
  • Используйте @typedef для сложных типов, которые применяются в нескольких местах.
  • Документируйте все параметры функций и возвращаемые значения.
  • Применяйте @template для обобщённых (generic) функций и типов.
  • Создавайте файлы объявлений (.d.ts) для сторонних библиотек без типов.
  • Используйте @ts-expect-error вместо @ts-ignore, если ожидаете ошибку.

Типичные ошибки

Обратите внимание на следующие распространённые проблемы:

  • Отсутствие // @ts‑check: Без этой директивы проверка типов работать не будет.
  • Неправильный синтаксис JSDoc: Даже одна опечатка может отключить проверку типов.
  • Конфликты типов: Возникают, когда типы из разных источников не согласуются между собой.
  • Проблемы с выводом типов: Иногда TypeScript не может корректно вывести типы автоматически.
  • Производительность: Проверка больших JavaScript‑файлов со сложными типами может занимать значительное время.

Заключение

Использование JSDoc вместе с TypeScript — мощный способ обеспечить типобезопасность в JavaScript‑проектах без необходимости конвертировать файлы в .ts.

Этот подход особенно полезен в следующих случаях:

  • постепенная миграция кодовых баз на TypeScript;
  • добавление проверки типов в уже существующие JavaScript‑проекты;
  • работа в средах, где файлы .ts не поддерживаются;
  • документирование JavaScript‑кода с указанием информации о типах.

Следуя шаблонам и лучшим практикам, описанным в этом руководстве, вы сможете воспользоваться многими преимуществами TypeScript, продолжая работать с JavaScript.

Важно: Хотя JSDoc обеспечивает отличную проверку типов, для новых проектов или полной миграции рекомендуется использовать файлы .ts — так вы получите максимально полный опыт работы с TypeScript.

Готовы попробовать TypeScript с JSDoc?

Начните с добавления // @ts‑check в ваши JavaScript‑файлы, а затем постепенно добавляйте типовые аннотации с помощью JSDoc.

Компилятор TypeScript поможет вам обнаружить ошибки ещё до их попадания в рабочий проект!