ДокументацияTypeScriptФункции в TypeScript
Начальный 10 мин чтения

Функции в TypeScript

Типизация параметров, возвращаемых значений, стрелочных функций, перегрузок и callback-ов в TypeScript. Практические примеры и паттерны.

typescriptfunctionsoverloadscallbackgenerics

Базовая типизация функции

TypeScript позволяет типизировать как параметры, так и возвращаемое значение:

function add(a: number, b: number): number {
  return a + b
}

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

function greet(name: string): string {
  return `Привет, ${name}!`
}

Если функция ничего не возвращает — тип void:

function log(message: string): void {
  console.log(message)
}

Стрелочные функции

Синтаксис тот же, что и для обычных функций:

const add = (a: number, b: number): number => a + b

const greet = (name: string): string => `Привет, ${name}!`

Часто стрелочные функции используются как callback-и:

const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map((n: number): number => n * 2)

В большинстве случаев TypeScript сам выведет тип callback-а из контекста:

const doubled = numbers.map(n => n * 2) // TypeScript знает, что n: number

Опциональные и дефолтные параметры

Параметры с ? можно не передавать:

function createUser(name: string, age?: number): string {
  if (age !== undefined) {
    return `${name}, ${age} лет`
  }
  return name
}

createUser('Анна')      // ok
createUser('Анна', 25)  // ok

Дефолтные значения не требуют явной типизации опциональности:

function request(url: string, method: string = 'GET'): void {
  console.log(`${method} ${url}`)
}

request('/api/users')           // GET /api/users
request('/api/users', 'POST')   // POST /api/users

Rest-параметры

function sum(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0)
}

sum(1, 2, 3)       // 6
sum(1, 2, 3, 4, 5) // 15

Rest-параметр всегда должен быть массивом.

Тип функции как тип переменной

Функцию можно описать через type alias:

type MathOperation = (a: number, b: number) => number

const add: MathOperation = (a, b) => a + b
const subtract: MathOperation = (a, b) => a - b
const multiply: MathOperation = (a, b) => a * b

Это удобно для callback-ов и стратегий:

type Comparator<T> = (a: T, b: T) => number

function sort<T>(items: T[], compare: Comparator<T>): T[] {
  return [...items].sort(compare)
}

Альтернативный синтаксис через интерфейс:

interface GreetFunction {
  (name: string): string
}

const greet: GreetFunction = (name) => `Привет, ${name}!`

Перегрузки (Overloads)

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

function parse(input: string): number
function parse(input: number): string
function parse(input: string | number): string | number {
  if (typeof input === 'string') {
    return Number(input)
  }
  return String(input)
}

const a = parse('42')  // TypeScript знает, что a: number
const b = parse(42)    // TypeScript знает, что b: string

Практический пример — утилита для создания HTML-элементов:

function createElement(tag: 'div', props?: Record<string, string>): HTMLDivElement
function createElement(tag: 'span', props?: Record<string, string>): HTMLSpanElement
function createElement(tag: string, props?: Record<string, string>): HTMLElement {
  const el = document.createElement(tag)
  if (props) {
    Object.entries(props).forEach(([key, value]) => {
      el.setAttribute(key, value)
    })
  }
  return el
}

Типизация callback-ов

Callback-и типизируются как параметры-функции:

function fetchData(url: string, onSuccess: (data: unknown) => void, onError: (error: Error) => void): void {
  fetch(url)
    .then(res => res.json())
    .then(onSuccess)
    .catch(onError)
}

Выносить типы callback-ов в отдельные type alias — хорошая практика:

type SuccessCallback<T> = (data: T) => void
type ErrorCallback = (error: Error) => void

function fetchData<T>(url: string, onSuccess: SuccessCallback<T>, onError: ErrorCallback): void {
  // ...
}

this в функциях

TypeScript позволяет явно указать тип this первым параметром. Этот параметр не попадёт в скомпилированный код:

interface Button {
  text: string
  click(): void
}

function handleClick(this: Button) {
  console.log(`Клик по кнопке: ${this.text}`)
}

Asserts-функции

Функция-утверждение сообщает TypeScript, что условие выполняется:

function assertString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new TypeError('Expected string')
  }
}

const input: unknown = 'hello'
assertString(input)
input.toUpperCase() // ok, TypeScript теперь знает, что input — string

Практические примеры

Типизированный debounce:

function debounce<T extends (...args: any[]) => void>(
  fn: T,
  delay: number,
): (...args: Parameters<T>) => void {
  let timeoutId: ReturnType<typeof setTimeout>

  return (...args) => {
    clearTimeout(timeoutId)
    timeoutId = setTimeout(() => fn(...args), delay)
  }
}

Типизированный event emitter:

type EventMap = {
  login: { userId: string }
  logout: undefined
  'page-view': { url: string; timestamp: number }
}

function on<K extends keyof EventMap>(
  event: K,
  callback: EventMap[K] extends undefined ? () => void : (data: EventMap[K]) => void,
): void {
  // ...
}

on('login', (data) => {
  console.log(data.userId) // TypeScript знает тип
})

on('logout', () => {
  console.log('Вышли')
})

Типизированный fetch-wrapper:

async function api<T>(
  url: string,
  options?: RequestInit,
): Promise<T> {
  const response = await fetch(url, options)

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`)
  }

  return response.json() as Promise<T>
}

interface User {
  id: number
  name: string
}

const user = await api<User>('/api/users/1')
// TypeScript знает, что user — User

Итог

Функции в TypeScript типизируются через параметры и возвращаемое значение. Основные моменты: явная типизация возвращаемого значения помогает при рефакторинге, rest-параметры всегда массивы, перегрузки дают разные сигнатуры для одной функции, а asserts позволяет сужать типы. Callback-и лучше выносить в отдельные type alias для читаемости.