Подтвердить что ты не робот

Почему я не могу косвенно возвращать литерал объекта для удовлетворения типа возвращаемой подписи индекса в TypeScript?

Эти три функции, похоже, делают одно и то же, но последнее - ошибка. Почему это так?

interface StringMap {
    [key: string]: string;
}

function a(): StringMap {
    return { a: "1" }; // OK
}

function b(): StringMap {
    var result: StringMap = { a: "1" };
    return result; // OK
}

function c(): StringMap {
    var result = { a: "1" };
    return result; // Error - result lacks index signature, why?
}
4b9b3361

Ответ 1

Такое поведение уходит.

Начиная с любого выпуска, следующего после TypeScript 1.8 (или прямо сейчас, если вы используете компилятор с кратким выпуском), вы больше не увидите эту ошибку, когда исходное выражение для типа является литералом объекта.

См. https://github.com/Microsoft/TypeScript/pull/7029


Старый ответ для старых компиляторов

Индексы и объектные литералы ведут себя особенно в TypeScript. Из спецификации 4.5, Object Literals:

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

Что это значит?

Контекстный ввод

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

var x: number = y;

Выражение y получает контекстный тип number, потому что он инициализирует значение этого типа. В этом случае ничего особенного не происходит, но в других случаях будет происходить более интересное.

Одним из наиболее полезных случаев являются функции:

// Error: string does not contain a function called 'ToUpper'
var x: (n: string) => void = (s) => console.log(s.ToUpper());

Как компилятор знал, что s является строкой? Если вы сами написали это выражение функции, s будет иметь тип any, и не было бы никакой ошибки. Но поскольку функция была введена в контекстном типе типа x, параметр s приобрел тип string. Очень полезно!

Индексные подписи

Индексная подпись указывает тип, когда объект индексируется строкой или числом. Естественно, эти подписи являются частью проверки типов:

var x: { [n: string]: Car; };
var y: { [n: string]: Animal; };
x = y; // Error: Cars are not Animals, this is invalid

Важное значение имеет также отсутствие сигнатуры индекса:

var x: { [n: string]: Car; };
var y: { name: Car; };
x = y; // Error: y doesn't have an index signature that returns a Car

Надеюсь, очевидно, что эти два фрагмента должны вызывать ошибки. Что приводит нас к...

Индексные подписи и контекстный ввод

Проблема с допущением, что объекты не имеют индексных сигнатур, заключается в том, что у вас нет способа инициализировать объект с помощью сигнатуры индекса:

var c: Car;
// Error, or not?
var x: { [n: string]: Car } = { 'mine': c };

Решение состоит в том, что когда литерал объекта контекстуально вводится типом с сигнатурой индекса, эта подпись индекса добавляется к типу объектного литерала, если он соответствует. Например:

var c: Car;
var a: Animal;
// OK
var x: { [n: string]: Car } = { 'mine': c };
// Not OK: Animal is not Car
var y: { [n: string]: Car } = { 'mine': a };

Вводя все вместе

Посмотрим на исходные функции в вопросе:

function a(): StringMap {
    return { a: "1" }; // OK
}

ОК, потому что выражения в операторах return конкретизируются типом возвращаемой функции. Литерал объекта {a: "1"} имеет строковое значение для его единственного свойства, поэтому подпись индекса может быть успешно применена.

function b(): StringMap {
    var result: StringMap = { a: "1" };
    return result; // OK
}

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

function c(): StringMap {
    var result = { a: "1" };
    return result; // Error - result lacks index signature, why?
}

Не в порядке, потому что тип result не имеет индексной подписи.