All streams
Search
Write a publication
Pull to refresh

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 на это не способен.

Благодарю, за отличный разворот. Рад, что есть разработчики, которые делятся и подмечают кейсы, делятся своим опытом и конструктивным мнением.

У меня к enum примерно такое же сложилось отношение, приведенный пример очень в тему

  1. про избыточный контекст в именах - то что тупо дублируется имя типа в каждом первом поле это может и не очень хорошо, но совет в целом звучит как будто "называйте поля максимально кратко". Кроме этого, есть два поля, которые наоборот лучше дублировать, это id и name. Чтобы при сериализации мы получали объекты { personId: 1 } и { companyName: "name" }. Так и удобнее дебажить и снизит вероятность ошибок подставления id от не той сущности.

  2. негативы в именах функций - вредный совет. Как раз наоборот, если по бизнесу оно звучит негативно, надо и в коде писать негативно и наоборот избегать булевого инвертирования. Проблема в том, что инвертирование не всегда корректно, и при нём легко допустить ошибку. К примеру "у нас должен быть unchecked что то" люди инвертируют в "!(у нас есть checked что то)". И вот у вас на руках ошибка: когда у вас на экране нет ни checked ни unchecked, первый вариант корректно уронит тест, второй скажет что тест зелёный и допустит крэш в релиз. Эта проблема как то по научному называется, не помню где чита. Инверсией вы можете по недосмотру сделать false negative вместо false positive. Где false positive это "задержать не-террориста", т.е. потом придется перед человеком извиняться, а false negative "!(задержать террориста)", где погибнут люди.

  3. 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летней давности. Нейросеть писала?

Sign up to leave a comment.

Articles