Avatar ·

Как получить тип возвращаемого значения функции в TypeScript?

Всем привет! Я только начал изучать TypeScript и столкнулся с проблемой. У меня есть функция, которая возвращает сложный объект, и мне нужно использовать этот же тип в другом месте, но я не хочу дублировать описание интерфейса. Вот пример: ```typescript function createUser(name: string, age: number) { return { id: Math.random().toString(36), name: name, age: age, createdAt: new Date(), isActive: true, permissions: ['read', 'write'] }; } // Как мне получить тип того, что возвращает createUser? // Я хочу сделать что-то вроде: // type User = ReturnType; // Это не работает! ``` Я пробовал использовать `typeof`, но он возвращает тип самой функции, а не её возвращаемое значение. Может быть есть какой-то встроенный механизм в TypeScript для этого? Видел где-то упоминание `ReturnType`, но не понимаю, как его правильно применить. Подскажите, пожалуйста, как правильно получить тип возвращаемого значения, чтобы потом использовать его для других переменных или параметров функций? Заранее спасибо! <<>>

Решение: использование встроенного типа ReturnType

В TypeScript есть встроенный условный тип ReturnType<T>, который позволяет извлечь тип возвращаемого значения функции. Он работает как с обычными функциями, так и с асинхронными.

Вот как правильно применить его к вашему примеру:

function createUser(name: string, age: number) {
  return {
    id: Math.random().toString(36),
    name: name,
    age: age,
    createdAt: new Date(),
    isActive: true,
    permissions: ['read', 'write']
  };
}

// Правильный способ получения типа возвращаемого значения:
type User = ReturnType<typeof createUser>;

// Теперь User — это тип объекта, который возвращает функция createUser
const user: User = {
  id: 'abc123',
  name: 'John',
  age: 30,
  createdAt: new Date(),
  isActive: true,
  permissions: ['read', 'write']
};

// Можно использовать в других функциях:
function updateUser(user: User, newName: string): User {
  return { ...user, name: newName };
}

Важные нюансы и расширенные примеры

  • Обязательно используйте typeof — ReturnType ожидает тип функции, а не саму функцию. Поэтому конструкция ReturnType<typeof createUser> — единственно верная.
  • Работа с асинхронными функциями — для async функций ReturnType вернёт Promise<T>. Чтобы получить "чистый" тип, используйте Awaited:
async function fetchUser(id: string) {
  const response = await fetch(`/api/users/${id}`);
  return response.json() as Promise<{ name: string; age: number }>;
}

// Тип будет Promise<{ name: string; age: number }>
type AsyncUser = ReturnType<typeof fetchUser>;

// Чтобы получить сам объект, используйте Awaited:
type ActualUser = Awaited<ReturnType<typeof fetchUser>>;
// ActualUser = { name: string; age: number }
  • Работа с функциями, имеющими перегрузки — ReturnType вернёт тип последней сигнатуры перегрузки.
  • Для стрелочных функций и методов класса — работает точно так же:
const getConfig = () => ({ theme: 'dark', lang: 'ru' });
type Config = ReturnType<typeof getConfig>; // { theme: string; lang: string }

class Service {
  getData() { return { success: true, data: [] }; }
}
type ServiceData = ReturnType<Service['getData']>; // { success: boolean; data: any[] }

Альтернативный подход: явное объявление типа

Хотя ReturnType — это элегантное решение, иногда лучше явно объявить тип и использовать его как возвращаемый тип функции. Это улучшает читаемость и позволяет документировать структуру данных:

interface User {
  id: string;
  name: string;
  age: number;
  createdAt: Date;
  isActive: boolean;
  permissions: string[];
}

function createUser(name: string, age: number): User {
  return {
    id: Math.random().toString(36),
    name,
    age,
    createdAt: new Date(),
    isActive: true,
    permissions: ['read', 'write']
  };
}

Когда использовать ReturnType

  • Когда функция возвращает сложный или динамический тип, который сложно описать вручную.
  • Когда вы работаете с библиотечными функциями, типы которых вы не контролируете.
  • Когда нужно быстро получить тип для временного использования.

Совет: Для production-кода предпочитайте явное объявление типов — это делает код самодокументируемым и упрощает рефакторинг. ReturnType отлично подходит для вспомогательных или утилитарных сценариев.

<<>>

Расширенный подход: создание собственного утилитарного типа

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

// Утилита для извлечения типа из функции
type FunctionReturn<T extends (...args: any[]) => any> = ReturnType<T>;

// Использование:
function getConfig() { return { port: 3000, host: 'localhost' }; }
type Config = FunctionReturn<typeof getConfig>;
// Config = { port: number; host: string }

Для асинхронных функций можно создать более специализированную версию:

type AsyncReturn<T extends (...args: any[]) => Promise<any>> = Awaited<ReturnType<T>>;

async function loadData() { return { items: [1, 2, 3] }; }
type Data = AsyncReturn<typeof loadData>;
// Data = { items: number[] }

Ограничения подхода:

  • ReturnType не работает с generic-функциями, если не указать конкретный тип аргументов.
  • Для функций с перегрузками берётся последняя сигнатура.

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