Comments 9
Я бы добавил еще в дополнение к 7 пункту немаловажный аспект про слияние деклараций и возможность повтроного объявления с тем же именем у интерфейса, что часто используется к примеру для расширения базовых интерфейсов у сторонних библиотек или отдельных приложений.
interface User {
id: number;
}
interface User {
name: string;
}
// В результате слияния двух интерфейсов: { id: number; name: string }
const user: User = { id: 1, name: "Nikita" };
А здесь нужен interface, если будут классы-имплементаторы.
Делать реализацию от типа тоже можно
Теперь любой разработчик, увидев вызов функции, мгновенно поймет, что к дате добавляется определенное количество месяцев
Ну вит я увидел addMonthToDate
и начал думать, а как к дате добавить месяц, например январь. Как минимум addMonthsToDate
. А, возможно, Date.addInterval(interval: DateInterval)
, где
interface DateInterval {
days?: number,
months?: number,
years?: number,
};
Попробую дать некоторую обратную связь.
Хорошо: Использование enum.
А я вот наоборот стараюсь enum не использовать. Смысла в нем не очень мнного, а вот при поддержке кода проблем больше.
Рассмотрим пример:
Скрытый текст
enum Genre {
Romantic = 'romantic',
Drama = 'drama',
Comedy = 'comedy',
Documentary = 'documentary',
}
class Projector {
configureFilm(genre: Genre) {
switch (genre) {
case Genre.Romantic:
// Логика для романтического фильма
break;
}
}
}
const projector = new Projector
projector.configureFilm(Genre.Comedy);
А теперь навалим жира, что бы Genre так же использовалась в свойстве объекта
type UsefullData = {
value: Genre
}
Подобное использование изначального enum вполне обычное явление. А проблемы начинаются, когда необходимо создавать для различных целей литералы этих данных.
const getRomanticDataMock = () => ({
value: 'romantic'
}) satisfies UsefullData
Тут будет ошибка:
Type '"romantic"' is not assignable to type 'Genre'.(2322)
И если таких свойств много придется тащить зависимости от Genre
const getRomanticDataMock = () => ({
value: Genre.Romantic
}) satisfies UsefullData
projector.configureFilm(getRomanticDataMock().value)
А вот если отказаться от enum такой ситуации легко избежать.
Скрытый текст
const Genre = {
Romantic: 'romantic',
Drama: 'drama',
Comedy: 'comedy',
Documentary: 'documentary',
} as const
type Genres = (typeof Genre)[keyof typeof Genre]
class Projector {
configureFilm(genre: Genres) {
switch (genre) {
case Genre.Romantic:
// Логика для романтического фильма
break;
}
}
}
const projector = new Projector
projector.configureFilm(Genre.Comedy);
type UsefullData = {
value: Genres
}
const getRomanticDataMock = () => ({
value: 'romantic'
}) satisfies UsefullData
projector.configureFilm(getRomanticDataMock().value)
Что изменилось: Мы избавились от изменяемого состояния (let totalOutput). Код стал короче, выразительнее и сфокусирован на что мы хотим сделать (посчитать сумму), а не на как это сделать (инициализировать счетчик, перебрать индексы, прибавить значение).
По моему, тут вы перемудрили.
Вам же ничто не мешает сделать так:
const contributions = [{ linesOfCode: 100 }];
let totalOutput = 0;
for (const contribution of contributions) {
totalOutput += contribution.linesOfCode;
}
И чем больше опереций будет в теле цикла, тем меньше вы будете экономить места в процентном соотношении. Да и выразительность даже в данном случае у каждого подхода плюс минус одна и тажа.
Хорошо: Использование readonly для защиты от изменений.
Это конечно хорошо, но делать сразу все readonly не всегда удобно. Например, вам ничто не мешает сделать так:
interface Config {
host: string;
port: string;
db: string;
}
const config = {
host: 'myhost',
port: '100',
db: 'mydb'
} as const satisfies Config
Таким образом где надо у вас всегда будут ридоли конфиги, а в других местах с ними можно будет без доп. телодвижений работать.
Используйте interface, если вы хотите использовать классическое ООП с extends или implements. Интерфейсы лучше работают с ошибками в IDE.
Вот тут не очень понял мысль. Вам ничто не мешает с extends или implements использовать объектный type. Если ваши данные - это всегда какой-то объект, то совершенно спокойно можно его описывать и через type, и через interface. Все различия начнут появляться только в специфических случаях, т.е. когда мы хотим расширить объектный тип определенный в другом месте. Вот тогда придется использовать interface, просто потому что type на это не способен.
про избыточный контекст в именах - то что тупо дублируется имя типа в каждом первом поле это может и не очень хорошо, но совет в целом звучит как будто "называйте поля максимально кратко". Кроме этого, есть два поля, которые наоборот лучше дублировать, это id и name. Чтобы при сериализации мы получали объекты { personId: 1 } и { companyName: "name" }. Так и удобнее дебажить и снизит вероятность ошибок подставления id от не той сущности.
негативы в именах функций - вредный совет. Как раз наоборот, если по бизнесу оно звучит негативно, надо и в коде писать негативно и наоборот избегать булевого инвертирования. Проблема в том, что инвертирование не всегда корректно, и при нём легко допустить ошибку. К примеру "у нас должен быть unchecked что то" люди инвертируют в "!(у нас есть checked что то)". И вот у вас на руках ошибка: когда у вас на экране нет ни checked ни unchecked, первый вариант корректно уронит тест, второй скажет что тест зелёный и допустит крэш в релиз. Эта проблема как то по научному называется, не помню где чита. Инверсией вы можете по недосмотру сделать false negative вместо false positive. Где false positive это "задержать не-террориста", т.е. потом придется перед человеком извиняться, а false negative "!(задержать террориста)", где погибнут люди.
type vs interface - система типов в ts это отдельный декларативный язык программирования и type в нём это тоже что и const в обычном разделе. К примеру function myFunc() {} можно записать в виде const myFunc = function() {}.
interface a {} и type a = {} соотносятся друг с другом так же.
Я бы дал совет использовать interface для первичных объявлений, а type для производных типов.
пример производного типа:
type Partial<T> = { [P in keyof T]?: T[P]; };
во первых большая часть советов никак не связана с TS, они в уелом про чистый код.
во вторых enum (как и в целом все runtime TS фичи) это зло. Они нарушают саму идею TS как типизированный JS. Например последняя нода может игнорировать типы и запускать TS напрямую без транспайла, но только в том случае если не используются runtime фичи (enum, namespace, etc). Не говоря уже про возможные проблемы енамов с автогенерацией кода.
в третьих лучше всегда использовать ‘interface’ over ‘type’. Использовать типы лучше только тогда, когда интерфейс не подходит, например union или intersection. Разница в том, что при пересечении типов TSу нужно больше памяти, чем при использовании интерфейсов с экстендом. На больших проектов это может влиять на скорость проверки типов тсом
Да, про енам вообще не в тему. Как будто статья скомпилена из чужих статей 10летней давности. Нейросеть писала?
Чистый код на TypeScript: практические советы