Комментарии 32
Ваше описание слишком расплывчато. Что значит «взаимодействие пользователя с кодом»? Пользователь у нас кто? Программист. И что же за взаимодействие с кодом может быть у программиста? Создание/изменение структур данных и функций, их обрабатывающих. А для этого эти самые структуры надо как-то описать. Вот и пришли к вашему определению типов.
С другой стороны, «описание передаваемых данных» само по себе в вакууме не нужно. Оно есть для того, чтобы мы понимали, что с этими данными можно делать, а чего нельзя. А это — работа с данными. Какого рода работа? Преобразование, передача в функции, возврат из функций. Всё это есть взаимодействие с кодом. Приходим к вашему описанию интерфейсов.
Получается, типы и интерфейсы — одно и то же?
Непонятно, что поменяется для вас, как для пользователя библиотеки, если вместо interface Libp2pInit<T> {
будет type Libp2pInit<T> = {
.
Для меня — пока ничего, но я как раз и пытаюсь выяснить, что мне следует использовать в своих проектах на TypeScript — type
или interface
? Потому что пока ясности нет, зачем нужны оба, делают вроде одно и тоже, просто чуть по-разному.
Не понял, почему с типами вы потратите 4 часа на поиск информации, если с интерфейсами вы тратите на это 10 минут?
Проект действительно небольшой, но типизация даже в нём была бы полезна. Ну и вообще, он в том числе затевался с целью потрогать этот ваш TypeScript «в условиях, приближенных к боевым».
Я раньше так же думал, JS хватает, какие проблемы, если я знаю, что я возвращаю из своей функции, к чему описывать типы. Потом поработал в проекте с Typenscript и по другому уже не могу.
На прошлой неделе переводил свой старый, относительно небольшой проект с JS на TS. В некоторых местах даже удивлялся, как оно вообще работало. :) Там такие смешные ошибки были в коде, которые без типов были просто не видны. Просто опечатки, пропущенные параметры.
Да и вообще, когда просто ide делает подсказки уже ускоряет работу с кодом. Тот же GitHub Copilot лучше ориентируется в коде. Нет нужды открывать файл с функцией, что бы понять, что за структуру она возвращает.
Ну и заставляет лучше структурировать код, а не генерить какие то структуры в разных местах разные.
Короче выигрыш от описания типов больше, чем затраты на описание типов.
Даже если в конкретном случае не поменяется ничего, как минимум кодовая база станет однороднее, а значит понятнее. Вот есть 2 почти одинаковых инструмента с редкими практическими различиями, я предпочитаю по этим различиям и проводить границу. Либо использовать интерфейсы всегда, кроме случаев когда нужны типы, либо наоборот. Когда начинаются разговоры про "взаимодействие пользователя с кодом" и "описание данных" это уже какая-то субъективная ерунда безосновательная. Вот как, например, новый разработчик в вашей команде поймёт когда ему использовать что? Вы можете сформулировать чёткое и однозначное определение своих терминов? А если и можете, будет ли оно проще и будет ли эта сложность того стоить?
Что касается выбора между "всегда типы пока не нужны интерфейсы" и "всегда интерфейсы пока не нужны типы" (где граница предельно ясна), я предпочитаю первое, потому что типы работают проще и интуитивнее. С интерфейсами могут потом ещё возникнуть проблемы и окажется что надо переписывать на типы, с типами такое случается реже.
Я понимаю, что по заголовку статьи это не так понятно, но в ней идет речь именно про ключевые слова type
и interface
в TypeScript.
Какой интересный язык! Кажется в работе с типами он продвинулся дальше многих других современных языков.
Кстати получается что пересечение & это по сути теоретико-множественное объединение, а объединение | это "тип-сумма" (tagged union, variant). А вот интересно, можно в нем сделать теоретико-множественное пересечение, разность или симметрическую разность типов?
Объединение это не тип-сумма. Tagged union это дизъюнктивное объединение из теории множеств, а union это обычное объединение. Ну а & это пересечение, а не объединение.
ИМХО в теории множеств вообще нет аналога для Tagged union. Потому что в tagged union нельзя хранить разные элементы одновременно, а в теории множеств про "одновременность" вообще ничего не говорится.
По поводу пересечения & - вот пример из интернета
type User = {
name: string;
age: number;
};
type Employee = {
name: string;
department: string;
salary: number;
};
type CommonUserEmployee = User & Employee;
// Result: { name: string; age: number; department: string; salary: number; }
т.е. на выходе мы имеем тип, объединяющий поля из двух типов, причем одинаковые поля сливаются - в точности как в объединении множеств. A={1,2}, B={2,3}, A∪B={1,2,3}.
Там уже ниже написали, типы рассматриваются как множества значений. Пересечение типов это пересечение множеств их значений. Точно так же тип-сумма называется суммой потому что количество его значений это сумма количеств значений входящих в него типов (как у дизъюнктивного объединения), а у типа-произведения, соответственно, произведение.
Про разные элементы одновременно не понял что вообще имелось в виду.
Там уже ниже написали, типы рассматриваются как множества значений
Тогда понятно
а у типа-произведения, соответственно, произведение.
Т.е. здесь имеется в виду декартово произведение
Про разные элементы одновременно не понял что вообще имелось в виду
Я просто пытался рассматривать типы как множества полей структур, а не как множества значений.
A & B
- это объединение известных утверждений про типы A и B (вида "у этого типа есть поле А), но пересечение множеств значений этих типов.
Разность множеств значений можно выразить через встроенный тип Exclude<A, B>
. Симметрическая разность - это, соответственно, Exclude<A | B, A & B>
.
Кажется в работе с типами он продвинулся дальше многих других современных языков.
Насколько понимаю, это было вынужденно - нужно было натянуть статические типы на очень динамические интерфейсы уже существующих JS-библиотек.
Теоретически широкие возможности -- это хорошо. А на практике в результате такой код получается, что начинаешь думать, что лучше бы этих возможностей не было .
Вообще просто не использую интерфейсы. Ну ли о так редко, что даже не помню.
Просто незачем, типы все перекрывают, удобно «наследуются», есть partial, а больше ничего и не нужно обычно.
Вы говорите не про типы, а алиасы типов (type alias)
типы гибче, например здесь интерфейс не может, а тип может:
type C = { a: 12 }
interface A { [key in keyof C]: 12}
type B = { [key in keyof C]: 12 }
Дико такое читать: "интерфейсы или типы?", "интерфейсы не использую". Вам там нормально в вашем фронтенде?
Можно взять за основу правило: использовать типы по умолчанию, а интерфейсы, когда это необходимо.
Что можно сделать интерфейсами, чего нельзя типами?
Указали выше. Аугментация есть только у интерфейсов.
То есть когда у нас есть interface A{} это позволяет добавить к нему функционал которого не было изначально .
Этим пользуются когда у нас есть стандартные типы из JS , а наш проводник скажем умеет больше и с помощью интерфейсов легко добавить дополнительные свойства и методы.
После прочтения статьи осталось ощущение недостаточного и чёткого объяснения по поводу "Когда использовать типы, а когда интерфейсы?".
Типы или интерфейсы в TypeScript: что и когда использовать?