У меня есть этот код:
const f = (x: unknown) => {
if (!x || !Array.isArray(x)) {
throw new Error("bad");
}
for (const y of x) {
if (!y || typeof y !== "object") {
throw new Error("bad");
}
y // y does intellisense think this has type `any`
}
};
В моем коде VS Intellisense считает, что финальный y
имеет тип any
. Я ожидаю, что это будет тип object
. Почему это? И как мне структурировать свой код, чтобы Intellisense распознавал второй y
тип object
?
@jcalz да, это полностью отвечает на мой вопрос. Спасибо!
Я напишу ответ, когда у меня будет возможность, вероятно, часов через 8 (сейчас мне пора спать)
// потому что x: неизвестно
// Случай 1: объект массива => typeof y === 'object'
f([{ "ключ": "значение" }])
// Случай 2: строка массива => typeof y === 'string'
е(['а'])
Пожалуйста, отредактируйте , чтобы написать словами то, что, по вашему мнению, демонстрирует код в вашем ответе. ОП знает, что если вы вызовете f(['a'])
, то y
не будет объектом, но тогда будет выдана ошибка времени выполнения. Если поток кода достигает закомментированной строки, y
обязательно должен быть объектом, верно? Тогда вопрос ОП: почему он не был сужен до object
? Этот ответ, похоже, вообще не касается этого.
y
— это элемент массива, его тип не указан, поэтому TypeScript интерпретирует его как any
. Рассмотрите возможность использования Универсальных типов, чтобы обеспечить безопасность типов и выполнять операции на основе типа входных данных.
function f<T>(x: T[]) {
if (!x || !Array.isArray(x)) {
throw new Error('bad');
}
for (const y of x) {
if (!y || typeof y !== 'object') {
throw new Error('bad');
}
y; // y does intellisense think this has type `any`
}
}
f([1, 2, 4]);
Тип функции приведенного выше вызова функции будет автоматически выведен function f<number>(x: number[]): void
, поскольку вы передаете числа.
Однако f()
предназначен для приема unknown
ввода. Если вы готовы требовать f()
принимать только массивы, почему бы просто не написать function f(x: object[]) {}
и потом ничего не проверять внутри? Кажется, что этот ответ упускает из виду суть вопроса, и неясно, как дженерики здесь могут сделать что-то полезное.
Это сочетание двух проблем.
Первая проблема описана по адресу microsoft/TypeScript#43865 . Метод Array.isArray() в настоящее время действует как функция защиты типа , которая сужает входные данные до any[]
. Это вполне разумное поведение, но есть много крайних случаев, когда оно не делает того, чего хотят люди. Тип элемента — намеренно небезопасный любой тип , а это означает, что как только вы получите доступ к элементам, они будут иметь тип any
. В вашем случае более желательным поведением было бы, если бы оно сужалось до unknown[]
, где элементы имеют типобезопасный неизвестный тип.
Вторая проблема описана по адресу microsoft/TypeScript#54239 . Определенные операции защиты типа, такие как сужение истинности, просто не работают со значениями типа any
. Предположительно, проверка правдивости any
должна сузиться как минимум до {}
, а затем typeof
существо "object"
сузится до object
. Но этого не происходит, к сожалению. Однако это случается с unknown
.
Вот почему это происходит.
Чтобы продолжить, вам нужно обойти хотя бы одну из этих двух проблем. Есть бесчисленное множество способов сделать это (вы всегда можете просто отказаться от Asserty as object
внизу кода), но я бы, вероятно, порекомендовал один из них:
Чтобы обойти проверку Array.isArray()
, дающую any[]
, вы можете переназначить результат any[]
на unknown[]
, как показано здесь:
const f = (x: unknown) => {
if (!x || !Array.isArray(x)) {
throw new Error("bad");
}
for (const y of x satisfies unknown[] as unknown[]) {
if (!y || typeof y !== "object") {
throw new Error("bad");
}
((y))
//^? const y: object
}
};
Написав x satisfies unknown[] as unknown[]
, я использую оператор satisfies , чтобы убедиться, что TypeScript в порядке с присвоением x
unknown[]
(поэтому вы получите ошибку, если, скажем, измените тест !Array.isArray(x)
на Array.isArray(x)
), а затем я используя оператор утверждения типа as, чтобы рассматривать x
как unknown[]
. (Вы могли бы сделать это без satisfies
, но тогда вы можете не получить ошибку, если x
не может быть присвоен unknown[]
. Тогда for (const y of x
будет видеть y
как тип unknown
, а остальная часть кода будет работать, потому что его можно сузить до object
.
Точно так же вы можете просто использовать новую переменную, например
const f = (x: unknown) => {
if (!x || !Array.isArray(x)) {
throw new Error("bad");
}
const _x: unknown[] = x;
for (const y of _x) {
if (!y || typeof y !== "object") {
throw new Error("bad");
}
((y))
//^? const y: object
}
};
Или вы можете оставить проверку Array.isArray()
в покое и обойти сужение y
от any
до object
, написав собственную функцию защиты типа для сужения произвольных значений до object
:
function isObject(z: unknown) {
return !!z && typeof z === "object"
}
Здесь isObject
подразумевается, что является функцией предиката типа формы (z: unknown) => z is object
(но если вы используете более старую версию TypeScript, вам может потребоваться аннотировать возвращаемый тип самостоятельно). И теперь вы можете просто использовать isObject()
вместо встроенной проверки:
const f = (x: unknown) => {
if (!x || !Array.isArray(x)) {
throw new Error("bad");
}
for (const y of x) {
if (!isObject(y)) {
throw new Error("bad");
}
((y))
//^? const y: object
}
};
Выглядит хорошо.
Похоже, вы столкнулись с комбинацией ms/TS#43865 и ms/TS#54239 , и вам нужно обойти хотя бы одну из них, например, присвоив
any[]
unknown[]
, как показано ️ 🔁 по ссылке на эту площадку. Если это полностью отвечает на вопрос, я мог бы написать ответ, объясняющий; если нет, то что мне не хватает?