Теперь, после слияния этого PR, можно создавать универсальные функции, выводящие литеральные выражения. Если это кажется сложным, позвольте мне привести пример: добавив новый модификатор const для параметров типа (внутри <T>), это становится возможным:

const identity = <const T>(t: T) => {
    return t;
}

const b = identity({
    a: "a",
    b: "b",
    c: "c"
});

// type inferred as:
const b: {
    readonly a: "a";
    readonly b: "b";
    readonly c: "c";
}

Без модификатора const компилятор выводит более общий тип:

const identity = <const T>(t: T) => {
    return t;
}

const b = identity({
    a: "a",
    b: "b",
    c: "c"
});

// type inferred as:
const b: {
    a: string;
    b: string;
    c: string;
}

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

когда буквальное выражение в аргументе контекстуально типизировано параметром типа const, буквальному выражению присваивается наиболее точный возможный тип.

Что такое буквальные выражения?

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

const a = {
    a: "a",
    b: "b",
    c: "c"
};

// type inferred as:
const a: {
    a: string;
    b: string;
    c: string;
}

То же самое относится и к массивам — const args = [8,5] выводится как number[], потому что компилятор понятия не имеет, сколько или каких элементов существует, поэтому обычно разумно сделать вывод, что это массив любого размера и числового значения.

Когда мы хотим, чтобы область действия наших типов была уже, мы можем использовать литеральные выражения. Литеральные выражения можно создать с помощью утверждения as const (обратите внимание, что это не приведение, а утверждение, они разные), которое указывает компилятору вывести самый узкий возможный тип — точное значение строки.

const a = {
    a: "a",
    b: "b",
    c: "c"
} as const;

// type inferred as:
const a: {
    readonly a: "a";
    readonly b: "b";
    readonly c: "c";
}

Результат чем-то похож на то, как мы использовали бы перечисления или типы объединения, такие как type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';.

Это позволяет компилятору убедиться, что не только значение a не меняется, но и ключи изначения также не изменятся — другими словами, константа.

Заключение

Узнав о буквальных выражениях, легко понять, почему этот новый PR так важен. Особенно когда раньше нам приходилось заставлять Typescript делать сужение типов в дженериках с помощью сторонних инструментов.

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

Вот игровая площадка Typescript для игры: Игровая площадка Typescript