Урок 08

Generics: введение

Generics позволяют писать функции и типы, работающие с разными типами, сохраняя типовую безопасность.

Проблема

Без дженериков функция принимает any и теряет тип:

function identity(value: any): any {
    return value;
}

const result = identity('hello');
result.toFixed(2); // нет ошибки, но упадёт в рантайме

Дженерик-функция

function identity<T>(value: T): T {
    return value;
}

const a = identity('hello'); // string
const b = identity(42);      // number

<T> — параметр типа. TypeScript выводит его из аргумента.

Явное указание

identity<string>('hello');
identity<number>(42);

Обычно не нужно — тип выводится.

Несколько параметров

function pair<A, B>(a: A, b: B): [A, B] {
    return [a, b];
}

pair('x', 1); // [string, number]

Дженерик-массивы

function first<T>(items: T[]): T | undefined {
    return items[0];
}

first([1, 2, 3]);        // number | undefined
first(['a', 'b']);       // string | undefined

Дженерик-интерфейсы

interface Box<T> {
    value: T;
}

const box: Box<string> = { value: 'hello' };

Дженерик-типы

type Result<T> = {
    data: T;
    error: string | null;
};

type UserResult = Result<User>;
type PostResult = Result<Post>;

Соответствие типов

TypeScript проверяет, что тип совпадает:

function identity<T>(value: T): T {
    return value;
}

const n: number = identity('hello'); // ошибка

С ограничением

function logLength<T extends { length: number }>(value: T): T {
    console.log(value.length);
    return value;
}

logLength('hello');    // ок
logLength([1, 2, 3]);  // ок
logLength(42);         // ошибка

extends ограничивает возможные типы.

Значение по умолчанию

type Box<T = string> = {
    value: T;
};

const box: Box = { value: 'hello' }; // T = string

Классы

class Stack<T> {
    private items: T[] = [];

    push(item: T) {
        this.items.push(item);
    }

    pop(): T | undefined {
        return this.items.pop();
    }
}

const stack = new Stack<number>();
stack.push(1);
stack.push('a'); // ошибка

Async

async function fetchData<T>(url: string): Promise<T> {
    const res = await fetch(url);
    return res.json();
}

const user = await fetchData<User>('/api/user');

Итоги

  • Дженерики — параметры типа
  • Синтаксис <T>
  • TypeScript выводит тип из аргумента
  • extends — ограничение
  • Работают с функциями, типами, классами
  • Сохраняют типовую безопасность