Сужение типа в массиве объектов

У меня есть этот код:

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?

Похоже, вы столкнулись с комбинацией ms/TS#43865 и ms/TS#54239 , и вам нужно обойти хотя бы одну из них, например, присвоив any[]unknown[], как показано ️ 🔁 по ссылке на эту площадку. Если это полностью отвечает на вопрос, я мог бы написать ответ, объясняющий; если нет, то что мне не хватает?

jcalz 20.08.2024 04:38
totaltypescript.com/ts-reset обещает это исправить
Bergi 20.08.2024 09:44

@jcalz да, это полностью отвечает на мой вопрос. Спасибо!

johnmichaelwu 21.08.2024 05:46

Я напишу ответ, когда у меня будет возможность, вероятно, часов через 8 (сейчас мне пора спать)

jcalz 21.08.2024 05:47
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
4
73
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

// потому что x: неизвестно

// Случай 1: объект массива => typeof y === 'object'

f([{ "ключ": "значение" }])

// Случай 2: строка массива => typeof y === 'string'

е(['а'])

Пожалуйста, отредактируйте , чтобы написать словами то, что, по вашему мнению, демонстрирует код в вашем ответе. ОП знает, что если вы вызовете f(['a']), то y не будет объектом, но тогда будет выдана ошибка времени выполнения. Если поток кода достигает закомментированной строки, y обязательно должен быть объектом, верно? Тогда вопрос ОП: почему он не был сужен до object? Этот ответ, похоже, вообще не касается этого.

jcalz 20.08.2024 05:05

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[]) {} и потом ничего не проверять внутри? Кажется, что этот ответ упускает из виду суть вопроса, и неясно, как дженерики здесь могут сделать что-то полезное.

jcalz 20.08.2024 05:41
Ответ принят как подходящий

Это сочетание двух проблем.

Первая проблема описана по адресу 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 в порядке с присвоением xunknown[] (поэтому вы получите ошибку, если, скажем, измените тест !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
  }
};

Выглядит хорошо.

Детская площадка, ссылка на код

Другие вопросы по теме