Ответ 1
Текущий тип возвращаемого значения (string[]
) является преднамеренным. Зачем?
Рассмотрим такой тип:
interface Point {
x: number;
y: number;
}
Вы пишете такой код:
function fn(k: keyof Point) {
if (k === "x") {
console.log("X axis");
} else if (k === "y") {
console.log("Y axis");
} else {
throw new Error("This is impossible");
}
}
Позвольте задать вопрос:
В хорошо ли типизированной программе может ли законный вызов
fn
вызвать ошибку?
Желаемый ответ, конечно, "Нет". Но какое это имеет отношение к Object.keys
?
Теперь рассмотрим этот другой код:
interface NamedPoint extends Point {
name: string;
}
const origin: NamedPoint = { name: "origin", x: 0, y: 0 };
Обратите внимание, что в соответствии с системой типа машинописи, все NamedPoint
действительны Point
s.
Теперь давайте напишем немного больше кода:
function doSomething(pt: Point) {
for (const k of Object.keys(pt)) {
// A valid call iff Object.keys(pt) returns (keyof Point)[]
fn(k);
}
}
// Throws an exception
doSomething(origin);
Наша хорошо набранная программа просто выбросила исключение!
Здесь что-то пошло не так! Возвращая keyof T
из Object.keys
, мы нарушили предположение, что keyof T
образует исчерпывающий список, потому что наличие ссылки на объект не означает, что тип ссылки не является супертипом типа значение.
По сути, (по крайней мере) одна из следующих четырех вещей не может быть правдой:
-
keyof T
- исчерпывающий список ключейT
- Тип с дополнительными свойствами всегда является подтипом своего базового типа.
- Допустимо псевдоним значения подтипа ссылкой на супертип
-
Object.keys
возвращаетkeyof T
Выбрасывание точки 1 делает keyof
практически бесполезным, поскольку подразумевает, что keyof Point
может быть некоторым значением, keyof Point
"x"
или "y"
.
Отбрасывание точки 2 полностью разрушает систему типов TypeScript. Не вариант.
Отбрасывание точки 3 также полностью разрушает систему типов TypeScript.
Отбрасывание пункта 4 - это хорошо, и вы, программист, задумываетесь, является ли объект, с которым вы имеете дело, псевдонимом для подтипа того, что, по вашему мнению, у вас есть.
"Отсутствующей функцией", которая делает это законным, но не противоречивым, являются Точные Типы, которые позволили бы вам объявить новый тип типа, который не подпадает под пункт № 2. Если бы эта функция существовала, вероятно, можно было бы заставить Object.keys
возвращать keyof T
только для T
которые были объявлены как точные.