Почему необязательная цепочка не успокаивает TypeScript?

Допустим, у меня есть следующий обработчик событий (в React):

onClick = {(e: MouseEvent<HTMLElement>) => {
     const link = e.target.href;

Я понимаю, почему TypeScript жалуется на приведенный выше код: e.target может быть тегом <a> (в этом случае он будет иметь href), или это может быть какой-то другой элемент, у которого нет href, и если этого не сделать, все может сломаться. т.

Но когда я использую необязательный оператор цепочки, не имеет значения, есть ли у e.targethref или нет:

onClick = {(e: MouseEvent<HTMLElement>) => {
     const link = e.target?.href;

То же самое относится и к этой (более подробной) версии:

onClick = {(e: MouseEvent<HTMLElement>) => {
     if (!e.target.href) return;
     const link = e.target.href;

Но несмотря ни на что, TypeScript выдает мне ошибку Property 'href' does not exist on type 'EventTarget'.ts(2339).

Как я могу выразить: «Мне все равно, есть ли у e.targethref или нет» (без использования @ts-ignore)?

• Зависит ли этот вопрос от React? Если да, отметьте это как таковое. Если нет, пожалуйста, отредактируйте свой код так, чтобы это был минимально воспроизводимый пример, которому не нужны внешние библиотеки для демонстрации проблемы. • Я не понимаю, почему замена e.target.href на e.target?.href имеет какое-либо отношение к наличию или отсутствию href. Необязательное связывание меняет то, что происходит, только если e.target сам по себе не определен. Если e.target всегда ненулевое, то e.target?.href ничего не меняет. Является ли необязательное связывание отвлекающим маневром в этом вопросе?

jcalz 05.04.2024 20:38

Для этого не требуется React, но контекст важен (невозможно иметь MouseEvent без какой-либо библиотеки DOM, такой как React), поэтому я включил код React. И необязательная цепочка — это не отвлекающий маневр, в этом вся суть вопроса!

machineghost 05.04.2024 20:44

Вопрос в том, что когда Typescript знает, что у вас есть объект (например, объект MouseEvent), и вы знаете, что этот объект может иметь свойство (а может и не иметь), например href, как вы сообщите Typescript, что «у него может быть это свойство href ( тот, который я дополнительно связываю)", чтобы это не вызывало ошибки.

machineghost 05.04.2024 20:45

Необязательная цепочка связана с тем, что e.target сам по себе является null или undefined, а не с тем, имеет ли он свойство href или нет, так что да, это отвлекающий маневр. У вас есть различные варианты, например. для начала используйте более конкретный тип события или сузьте значение, которое вы получаете, до одного.

jonrsharpe 05.04.2024 20:45

@Джаред Смит Да; Я отмечу.

machineghost 05.04.2024 20:52

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

Jared Smith 05.04.2024 20:52

(Также просто не размещайте обработчики событий щелчка на якорных элементах, используйте настоящую кнопку.)

jonrsharpe 05.04.2024 20:53

В данном конкретном случае это не вариант (использование делегированных событий для обработки событий, происходящих из div полной визуализированной уценки)

machineghost 05.04.2024 20:54
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
2
8
64
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Typescript жалуется, потому что интерфейс HTMLElement не имеет атрибута href. Это связано с тем, что этот интерфейс представляет все возможные элементы DOM и имеет их общие свойства.

Вместо этого вам следует использовать HTMLAnchorElement.

Если вы не можете определить e: MouseEvent<HTMLAnchorElement>, вы можете привести функцию позже, используя const target = e.target as HTMLAnchorElement. Обратите внимание, что этот подход безопасен, только если вы уверены, что e.target — это HTMLAnchorElement.

Если вы лечите дженерики HTMLElement, вы можете использовать защиту типа :

const target = e.target
if ("href" in target) {
  // here you can use `target.href`
}

Или, если вам нужна специальная функция

const isAnchorElem = (target): target is HTMLAnchorElement => "href" in target
const target = e.target
if (isAnchorElem(target)) {
  // here `target` will be of type `HTMLAnchorElement`
}

Это имеет смысл, но это не всегда будет элемент привязки: событие просто настроено на срабатывание при каждом щелчке по элементу, и хотя этот элемент может быть привязкой... он не может быть :(. Итак, говоря, что событие всегда будет один, кажется неправильным.

machineghost 05.04.2024 20:49

Хорошо, тогда, похоже, стоит использовать защиту типа

Igor Cantele 05.04.2024 20:52

Да, конкретно (кажется) для подобных вещей это должен быть охранник типа in. Настолько разочаровывает, что вам нужно добавлять целые дополнительные операторы if в действительный/рабочий код только для того, чтобы сообщить TS то, что он должен понять.

machineghost 05.04.2024 20:58

Проблема в том, что ваш машинописный код был скомпилирован в код javascript во время выполнения, поэтому у него нет доступа к такой информации, как «какие свойства есть в моем объекте». Он может только статически выводить типы, поэтому «дополнительные if» необходимы, чтобы сделать тип кода безопасным.

Igor Cantele 05.04.2024 21:37

Но иногда переменная может иметь свойство, а может и не иметь его... и это нормально, правда? Мне бы хотелось, чтобы в Typescript был способ сказать: «дайте мне foo.bar, и если у foo нет bar, ничего страшного (я буду использовать undefined)». Вместо этого вы вынуждены добавлять бессмысленные if утверждения к «узким» вещам.

machineghost 06.04.2024 00:04

Да, в этом сценарии использование foo?.bar — это нормально, но здесь вы имеете дело с типом, который никогда не имеет нужного вам свойства, поэтому необходима защита типа. В сигнатуре функции вы сообщаете компилятору: «Моя цель имеет этот интерфейс», а в теле функции вы пытаетесь получить доступ к свойствам за пределами интерфейса, который вы сообщили компилятору. Ошибка, с которой вы столкнулись, только что заявила об этом.

Igor Cantele 06.04.2024 09:29

Спасибо, это действительно прояснило ситуацию.

machineghost 07.04.2024 18:28

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

Доступ к частной (#) функции-члену из обратного вызова
Рекурсивное построение древовидной структуры данных из массива строк, сохраняющих порядок приоритета
Проблема с динамическими данными перевода без перезагрузки в React с использованием i18next
Как сделать положение модального окна статическим при масштабировании или изменении размера экрана
Проблема «В настоящее время используется версия TypeScript, которая официально не поддерживается @typescript-eslint/typescript-estree» после обновления Nuxt
Использование Cypress.IO для установки данных в CKeditor 4
Можно ли одновременно прослушивать изменение состояния и действие с помощью прослушивателя набора инструментов reduxMiddleware?
Как добавить счетчик загрузки в MaterialUI DataTable, не задавая родительскому компоненту фиксированную высоту?
Как отправить файл cookie на автономный сервер trpc
Как сделать синхронные вызовы API и вернуть цепочку в функции?