Есть ли способ обеспечить полиморфную дискриминацию типов, аналогичную перечислениям Rust?

Недавно я вернулся к JS/TS после того, как много писал код на Rust, и мне очень не хватает этой функции.

Итак, предположим, что у меня есть значение, которое может быть экземпляром Cat или экземпляром Dog, и в зависимости от того, из какого класса оно принадлежит, я хочу выполнить другую логику. Поскольку Javascript не может classname == (я так думаю?), я обычно полагаюсь на передачу строки, указывающей класс, например:

function groomAnimal(animal: Cat | Dog, animalType: 'cat' | 'dog') {
    if (animalType === 'cat') {
        (animal as Cat).trimNails();
    } else {
        (animal as Dog).trimFur();
    }
}

Я ненавижу это, потому что это громоздко, и компилятор не может гарантировать, что вызывающая сторона правильно выполняет контракт. С другой стороны, в Rust это было бы довольно элегантно:

enum Animal {
    Cat(CatData),
    Dog(DogData),
}

fn groomAnimal(animal: Animal) {
    match animal {
        Cat(catData) => catData.trimNails(),
        Dog(dogData) => dogData.trimFur(),
    }
}

По сути, сам enum, который указывает «класс», также содержит данные «класса», и, таким образом, получение данных и проверка типа данных являются одной и той же операцией.

Есть ли какой-либо шаблон в машинописном тексте, который позволяет использовать полиморфные сигнатуры функций, обеспечивая при этом во время компиляции, что код работает с правильными типами данных?

Поведение ключевого слова "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) для оценки ваших знаний,...
3
0
122
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Сохранение отдельного значения, указывающего тип, очень подвержено ошибкам. Если Cat и Dog являются типами классов (а не только интерфейсами), вы можете использовать instanceof для прямой проверки его типа:

class Cat {
    trimNails() {}
}

class Dog {
    trimFur() {}
}

function groomAnimal(animal: Cat | Dog) {
    if (animal instanceof Cat) {
        // animal must be Cat, so you can call trimNails()
        animal.trimNails();
    } else {
        // animal must otherwise be a Dog, so you can call trimFur()
        animal.trimFur();
    }
}

К сожалению, это интерфейсы. Они анализируются из JSON, загруженного с сервера. Я не думаю, что есть простой способ сделать их классами без каких-либо накладных расходов.

sicklybeans 12.12.2020 22:51

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

sicklybeans 12.12.2020 22:55
Ответ принят как подходящий

Вы можете создать компонуемую структуру с Cat/Dog и некоторым атрибутом идентификатора, немного похожим на ваш подход, но по-другому.

Что-то вроде этого,

type Animal = Cat & { animalType: 'Cat' } | Dog & { animalType: 'Dog' }

Ваш groomAnimal метод теперь будет принимать составную структуру,

function groomAnimal(animal: Animal) {
    if (animal.animalType === 'Cat') {
        animal.trimNails();
    } else {
       animal.trimFur();
    }
}

Вот один небольшой пример, где я создаю кота и передаю его методу groomAnimal,

const tom = {
    trimNails() {
        console.info('Trimming nails')
    }
}

groomAnimal({ ...tom, kind: 'Cat'})

В вашем случае вы сказали, что анализируете их из некоторых вызовов API. Вы уже можете подготовить свой объект Animal/Animals[] при синтаксическом анализе и использовать его позже при уходе.

И, конечно же, компилятор может гарантировать, что вызывающая сторона действительно правильно выполняет контракт, как вы можете видеть из кода.

Я знаю, что на данный момент это довольно старо, но, оглядываясь назад, это определенно правильное решение. Я не думаю, что оценил это в то время, но это очень чисто и прямолинейно.

sicklybeans 14.07.2021 15:25

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