A: Не думай о помощи.
Б: Сложно не думать о помощи, когда пишешь на javascript.

Примерно такой диалог я слышал на одной из конференции. Решить проблему отсутствия строгой типизации был призван typescript.

Конкретно в этой статье я хотел бы рассмотреть один из приемов использования интерфейсов typescript, который мне кажется неочевидным, его я подсмотрел и смог оценить его преимущества в процессе написания приложений на языке golang.

Для большинства typescript разработчиков типы и интерфейсы, не имеют как таковой большой разницы.

type Dog = {name: string, age: number}

interface Dog {
name: string,
age: number
}

И запись при помощи интерфейса и при помощи типа по своей сути равнозначно смогут дальше работать, и подсказывать ошибки типизации при дальнейшей разработке

Что я предлагаю?

Я предлагаю разделять эти 2 понятия:

  1. Типы используем когда хотим описать определенные атрибуты объектов.

  2. Интерфейсы когда хотим описать поведение объектов, их методы.

Для начала создадим интерфейс “greeter”.

interface greeter {
  greet() : void;
}

Создадим типы “Dog” и “Cat”.

type Dog = {name: string, age: number}
type Cat = {first_name: string, second_name: string, owner: string}

Как мы видим эти типы, имеют различные наборы атрибутов.

Далее создадим функцию, которая принимает в качестве первого аргумента, объект удовлетворяющий интерфейсу “greeter” и далее вызывает его метод “greet”.

function greet(g: greeter) {
  g.greet();
}

Что-бы наши типы “Dog” и “Cat”, можно было использовать при вызове функции “greet”, необходимо явно объединить типы “Dog” и “Cat” c интерфейсом “greeter”

type Dog = {name: string, age: number} & greeter
type Cat = {first_name: string, second_name: string, owner: string} & greeter

Реализуем объект каждого типа

const dog: Dog = {
  name: "jack",
  age: 10,
  
  greet() {
    console.log(`hello im dog. My name is ${this.name}. I'm ${this.age} years old.`)
  }
}

const cat: Cat = {
  first_name: "Myley",
  second_name: "Meows",
  owner: "Ivan",
  
  greet() {
    console.log(`hello im cat. My name is ${this.first_name} ${this.second_name}. My owner name is ${this.owner}`);
  }
}

Что мы имеем? У нас 2 объекта, разные по своей реализации, но их объединяет наличие метода, указанного в интерфейсе “greeter”

Вызовем эти методы

greet(dog);//"hello im dog. My name is jack. I'm 10 years old."
greet(cat);//"hello im cat. My name is Myley Meows. My owner name is Ivan"

Итог:

Интерфейсы позволяют разделять реализацию от так называемого слоя бизнеса.

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

Код становится чище и в результате более легким для восприятия.

Позволяет разделять приложения, на более мелкие, независимые слои.

Комментарии (88)


  1. DiegoV
    22.04.2024 14:28
    +2

    Добро пожаловать в C#...
    Когда-нибудь javascript разработчики поймут, что хватит стрелять себе в колени, все давно придумали за них и героически изобретут SharpScript


    1. 19Zb84
      22.04.2024 14:28

      AsemblerScript уже есть.

      Написанеие кода на java script позволяет писаить на любом практичесуки языке где есть wasm.

      Все зависит от задачи.

      Не видел еще проектов, крооме блокчейнов где type sacript был бы прямо неоьходим.


      1. bromzh
        22.04.2024 14:28
        +8

        Не видел еще проектов, крооме блокчейнов где type sacript был бы прямо неоьходим.

        Ну ничто не необходимо, так-то можно и на бэке всё на ассемблере писать или сразу в машкодах. Типизация это просто вопрос удобства разработки и уменьшения проблем до запуска программы.

        Попробуйте порефакторить любой большой js проект. Например, есть 2 эндпоинта /api/user и /api/item. Они возвращают схожие объекты с полем name: `{ id: 1, name: 'name', ... }`. Далее, эти данные разлетаются по всему приложению на сотню различных страниц. Теперь мы хотим переименовать name в fullName для юзера, но оставить name у item нетронутым. Typescript позволит сразу увидеть, где мы используем старое поле и где нужно его сменить (Ну и чем больше описано типов, тем проще что-то добавлять или изменять). Для js же обычно всё вылезет в рантайме.


        1. 19Zb84
          22.04.2024 14:28

          Ну и чем больше описано типов, тем проще что-то добавлять или изменять

          Скорость стайп скриптом падает раза в три

          Как минимум имз за того что раза в три больше кода писать надо.

          Я бы стейт сделал из которого брал данные.

          Задача легко решается изменением одного объекта и посмотреть где данные из стейта используются. Не особо проблема. С type script тоже самое делать придееется... но я давно не видел сайты на 100 страниц.

          Как паравило это небольшие сервисы соединенные вместе.


          1. Lainhard
            22.04.2024 14:28
            +8

            Низкая скорость написания с лихвой компенсируется скоростью/сложностью рефакторинга.


            1. 19Zb84
              22.04.2024 14:28

              Низкая скорость написания с лихвой компенсируется скоростью/сложностью рефакторинга.

              Как может наличие типов повлиять на скорость рефакторинга ?

              Например.
              У вас было 5 методов, которые вы решили объединить в один модуль и обращаться к нему из всего приложения.

              * Сделать модуль и подключить в него все методы.

              * Создавать модуль, создавать типы с которыми он работает, создавать новые интерфейсы для методов, если параметры поменяются.

              Где тут ускорение рефакторинга ?

              Тем более если определенные части кода должны иметь строгую типизацию, то в чем проблема написать эти части кода с использованием type script и жить спокойно ?

              А если хочется по каким то причинам использовать типы, в чем проблема настроить статическую типизацию с jsdoc например и так же спокойно писать код так же как и с type script.


              1. bromzh
                22.04.2024 14:28
                +1

                Создавать модуль, создавать типы с которыми он работает

                Так я рефакторю typescript-проект, у меня типы уже есть.

                создавать новые интерфейсы для методов, если параметры поменяются

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

                Теперь перейдём в мир js. Что там будет, если поменять сигнатуру функции? Да ничего, если вы забудете где-то поменять параметры, то об этом узнаете только в рантайме. Повезёт, если сразу станет понятно проблемное место.

                Если же меняется структура данных, которыми манипулирует функция, т.е. интерфейсы, то их поправить занимает не так много времени. При этом опять же, тс сразу покажет, где эти типы используются и

                Где тут ускорение рефакторинга ?

                Рефакторинг - это не только изменение кода, но и проверка на корректность. Изменение кода обычно быстрее, так как я сразу вижу всё что нужно исправить. Проверка тоже быстрее, потому что часть глупых ошибок, типа "забыл изменить имя поля, забыл добавить второй параметр в функцию, опечатался в названии" просто исчезают навсегда. Пример я приводил - поменять поле name в одной структуре, не трогая другую. Простым поиском и заменой тут дело не решишь. Могу ещё примеров накидать

                Противники типизации никак не могут понять, что они тоже в конечном итоге работают с типами, потому что данные, которыми манипулируют не рандомные, а имеют какую-то структуру. Только вот типы у них "вылазят" в самый последний момент, когда их уже используют. Но из-за динамичности языка где-то на пути от, условно получения из апи до вывода на экран, какие-то типы могут быть неправильно преобразованы или использованы. Тайпскрипт просто позволяет выявить это раньше, до запуска.

                Это как писать программу по ТЗ. JS - это устные договорённости, которые все забывают или искажают, неверно интерпретируют (потому что описано размыто). Поэтому нужный результат долго получается - слишком много доделок. TS - это нормальное ТЗ записанное в условном конфлюенсе, где всё оговорено, где пограничные случаи описаны, так что ты просто читаешь и пишешь. Мне вот по второму способу удобнее и быстрее работать.


              1. ijustwanttobeacool
                22.04.2024 14:28
                +1

                Сначала вы говорите

                Скорость стайп скриптом падает раза в три

                А потом вы пишете

                в чем проблема настроить статическую типизацию с jsdoc

                Действительно...

                Я в своё время пытался сделать проект с использованием jsdoc и это боль, когда проект становится хоть сколько-нибудь большим


          1. bromzh
            22.04.2024 14:28
            +12

            Скорость стайп скриптом падает раза в три

            Сочувствую. В моём тайпскрипте по-другому.

            Как минимум имз за того что раза в три больше кода писать надо.

            Берите больше, в 10 раз!

            Я вот код больше читаю/исправляю, чем пишу. Писать мне всякие AI-ассистенты и IDE помогают. А вот время на поиск глупых ошибок, вроде опечаток имени полей в рантайме, почему-то противники тайпскрипта не учитывают. Видимо они реально только пишут код, но не сопровождают его.


            1. 19Zb84
              22.04.2024 14:28

              Видимо они реально только пишут код, но не сопровождают его.

              Я в этом году поддерживал 5 проектов, которые сам спроектировал и написал.
              Знаю о чем говорю.

              Судя по минусам, это самое токсичное сообщество из всех которые я только видел.

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

              Обидно что из за некчемной статьи, какие то люди с достаточно низким интелектом, испортили карму. Напиши парень мне, я тебе в личку напишу что я о тебе думаю.


              1. doctorw
                22.04.2024 14:28
                +1

                Как долго вы поддерживали эти 5 проектов и какого рода поддержку выполняли?


                1. 19Zb84
                  22.04.2024 14:28
                  +2

                  В течении уже 2-х лет. Они со своими проблемами.

                  Но деплой 400 миллесекунд
                  Отсутсвие всех лишних зависимостей.
                  Соусры запскаются без всяких бандлеров и все сервисы при желании можно объединить в один проект и либо сделать разделами либо динамически подгружать.

                  Все четко работает.

                  Минус тайп скрипта в том, что он половину плюсов моего проекта убьет своей компиляцией. + я не видел ни одного стоящего проекта написанного на type script (Исключение только уже очень матерые проекты, где уже вся остальная работа сделана)

                  Type Script к себе в проект добавить это дело 1 часа с перекурами.

                  Тема статьи тем отличается типы от интерфейсов.

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

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


                  1. doctorw
                    22.04.2024 14:28

                    Вы употребляете фразы вроде "Здешние шавки", делаете категоричные заявления, и жалуетесь, что вас минусуют? Вы странный.

                    Что касается проекта, который вы поддерживали в течение 2-х лет и деплоящийся за 400 мс, то складывается впечатление, что это не серьёзный проект, а одностраничник с парой кнопок. А сколько релизов было за эти 2 года?

                    На серьёзных проектах, проблемы начинают ощущаться на горизонте либо 5 лет либо при интенсивной разработке уже до 5 лет. Либо вы помимо этого проекта должны заниматься ещё минимум 3-4 проектами, чтобы в голове не умещалась вся модель оного.


              1. DEugene
                22.04.2024 14:28
                +2

                Мне кажется это закономерно. Вы без тени сомнения делаете голословные и категоричные заявления. Перечеркиваете всю пользу теории типов, материализованную в TS.


                1. 19Zb84
                  22.04.2024 14:28

                  Мне кажется это закономерно. Вы без тени сомнения делаете голословные и категоричные заявления. Перечеркиваете всю пользу теории типов, материализованную в TS.

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

                  Факт Это в 90 % кода на фронте не имеет смысла.

                  Ни одного аргумента в пользу тайп скрипта тут ещё не сказал, что бы можно было сказать что это да.


                  1. doctorw
                    22.04.2024 14:28

                    Если вы не собираетесь заниматься поддержкой проекта в перспективе 5-10 лет или получить по лбу от тех, кто будет заниматься поддержкой проекта после вас, то стоит использовать TS.

                    А если вы пишете write-only код для proof-of-concept, то конечно можно и JS обойтись.


              1. kellas
                22.04.2024 14:28

                Я занимаю веб-разработкой больше 15 лет уже , в основном фронтендом и полностью с вами согласен. TS тот еще костыль. Не обращайте внимания на этих "разработчиков" они озабочены по большому счету только своим комфортом и у них совершенно другие приоритеты и подход к работе, c TS лично им приятнее ковыряться в IDE, они чувствуют себя умными, а не идиотами которые не могут отдебажить элементарные вещи, это не они платят за увеличение сроков разработки а им, так что их даже можно понять )



                1. bromzh
                  22.04.2024 14:28

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

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

                  идиотами которые не могут отдебажить элементарные вещи

                  Высокомерный мем внутри


                  1. kellas
                    22.04.2024 14:28
                    +1

                    А почему никто больше не разберётся? И как люди разбирались со сложными проектам типа визуальных конструкторов сайтов до появления TS? Как же смогли написать github , tilda(вообще кажется на jQuery), vkontakte без тайпскрипта?

                    У меня почти все проекты на работе на TS и я прекрасно вижу какой ценой достигается это "удобство" и "понятность". Код пишется дольше, читается дольше и работает хуже по всем метрикам . Тесты писать всё равно приходится , иначе баги лезут, а эти тесты и без TS могу поверить что где-то не те данные приходят.


                    1. bromzh
                      22.04.2024 14:28

                      А почему никто больше не разберётся

                      Ну вы обобщаете, про идиотов, сроки и дебаг, я вот тоже обобщаю. Я говорил про конкретную касту "отрицальщиков типов", с которыми я лично работал. Они обычно гордятся своим незнанием, любят делать запутанные штуки, используют динамические возможности JS во зло, скажем так. При этом есть огромная куча хороших проектов на чистом JS, которые понятны и хорошо работают.

                      я прекрасно вижу какой ценой достигается это "удобство" и "понятность". Код пишется дольше, читается дольше и работает хуже по всем метрикам . 

                      Конечно можно верить во всемирный заговор Microsoft по созданию TS, как инструмента для затягивания сроков и увеличения бюджетов. А можно задуматься, а почему с TS у вас код даже работает хуже, хотя по-факту это надмножество языка. TS создавался с целью помочь программистам. Если же вы с ним боретесь, то вероятно надо повысить экспертизу в команде, например. Know your instrument, как говорится. Причем, в отличие от других языков, тут можно задавать уровень точности компилятора, и в случае затруднений точечно избегать типизации. Можно обходиться только простыми интерфейсами, без дженериков и всяких advaced types, чисто для дополнительной помощи в IDE. А можно проинвестировать в знания и фигачить всё на максималках, с большим трудом, но и с большими гарантиями и большим профитом по итогу.

                      В конце концов, я предпочту, чтобы за меня всякой рутиной по проверке корректности данных занимались проклятые роботы, а я уж логику буду писать и тестировать именно её, а не писать тесты на то чтобы в функцию какую-то фигню не передали.


        1. 19Zb84
          22.04.2024 14:28

          Ну и чем больше описано типов, тем проще что-то добавлять или изменять

          Скорость с тайп скриптом падает раза в три

          Как минимум имз за того что раза в три больше кода писать надо.

          Я бы стейт сделал из которого брал данные.

          Задача легко решается изменением одного объекта и посмотреть где данные из стейта используются. Не особо проблема. С type script тоже самое делать придееется... но я давно не видел сайты на 100 страниц.

          Как паравило это небольшие сервисы соединенные вместе.


        1. flancer
          22.04.2024 14:28

          Typescript позволит сразу увидеть, где мы используем старое поле и где нужно его сменить

          JSDoc позволяет сделать то же самое.


          1. nin-jin
            22.04.2024 14:28

            ..путём написания раз в 10 больше кода, чем с TS.


            1. flancer
              22.04.2024 14:28

              зато без транспиляции :)


              1. nin-jin
                22.04.2024 14:28

                Ну-ну, вы и на прод все эти килотонны комментариев повезёте?


                1. flancer
                  22.04.2024 14:28

                  А у вас есть статистика на сколько килотонна комментов замедляет работу программы? А на сколько увеличивает скорость отлова багов? И, да - я потяну все эти килотонны на прод :) А вы можете их вырезать, если они вам не нужны. И всё равно без транспиляции;)


                  1. 19Zb84
                    22.04.2024 14:28

                    А у вас есть статистика на сколько килотонна комментов замедляет работу программы? 

                    Вы гений. Задали вопрос и сами на него ответили.


              1. bromzh
                22.04.2024 14:28

                То есть фактически без проверок на ошибки, потому что ломаный код всё равно можно запустить.


                1. flancer
                  22.04.2024 14:28
                  +1

                  А TS даёт 100% гарантию запускаемости кода? Или только повышает вероятность? Пишите прямой код, и не пишите кривой - вот и не будет ломанного кода. JSDoc'и лишь помогают IDE помогать вам ориентироваться в вашем коде - т.е., "сразу увидеть, где мы используем старое поле и где нужно его сменить".


                  1. bromzh
                    22.04.2024 14:28

                    TS не даст скомпилироваться некорректному коду. Когда вся кодовая база на нём надо очень постараться, чтобы скомпилировать код с ошибками. На этом шаге надо призадуматься, а точно ли оно надо? Что нужно сделать, чтобы запустить ломаный код с JSDoc? node index.js и всё.

                    JSDoc'и лишь помогают IDE помогать вам ориентироваться в вашем коде 

                    А TS позволяет описывать контракты и всякие иварианты. Что позволяет сузить количество проблемных мест.


                    1. flancer
                      22.04.2024 14:28

                      Успешное компилирование кода в TS говорит лишь о том, что в коде нет ошибок компиляции. А вот насколько хорошо тот код будет работать, то вопрос открытый. Я бы сравнил разработку на TS с занятием сексом в презервативе - он обязателен, если я не уверен в своих партнёрах. Или в себе.


                      1. doctorw
                        22.04.2024 14:28
                        +1

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


                      1. bromzh
                        22.04.2024 14:28
                        +1

                        Я понял, вы просто сразу пишете программы без ошибок. Всем бы так


                      1. doctorw
                        22.04.2024 14:28

                        Без ошибок компиляции – да, если речь о коде, который попадает в remote branch. Я думаю о бизнес-логике, а о том, чтобы я не передал метры, там где ожидаются футы - заботится компилятор.


                      1. flancer
                        22.04.2024 14:28

                        а о том, чтобы я не передал метры, там где ожидаются футы - заботится компилятор.

                        Это заблуждение.

                        https://overclockers.ru/blog/amv212/show/62908/oshibka-nasa-stoila-agentstvu-dorogostoyaschego-kosmicheskogo-apparata-dlya-issledovaniya-marsa-v-125-mln


                      1. doctorw
                        22.04.2024 14:28

                        Это проблема границ кодовой базы, там где она соприкасается с другой кодовой базой посредством API. Если код на разных языках или API использует сериализацию, то такое компилятор естественно не найдёт. Однако, в пределах одной кодовой базы компилятор проверит согласованность.

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


                      1. bromzh
                        22.04.2024 14:28

                        Ну я тоже) Я отвечал мистеру @flancer, который как бы доверяет себе и партнёру, но типы всё-равно везде ставит, просто в менее удобном и менее мощном виде.


                      1. flancer
                        22.04.2024 14:28
                        +1

                        ... и в менее сковывающем движения :)


                      1. bromzh
                        22.04.2024 14:28

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


                      1. flancer
                        22.04.2024 14:28

                        Сочувствую :( Лично я не вижу в этом ничего плохого. Но я и не на галерах.


                      1. doctorw
                        22.04.2024 14:28

                        del


              1. doctorw
                22.04.2024 14:28

                А в чём проблема транспиляции?


                1. flancer
                  22.04.2024 14:28

                  В неожиданности - код с которым ты имел дело в IDE, совсем не похож на тот код, который ты видишь на проде. Если что, я полтора года писал код с использованием GWT.


            1. 19Zb84
              22.04.2024 14:28

              ..путём написания раз в 10 больше кода, чем с TS.

              Там вообще ничего не надо писать кроме коментариев с описанием типов.


              1. bromzh
                22.04.2024 14:28

                Вот вам пример кода от адепта типов в JsDoc. В тайпскрипте строчек будет поменьше, если вас так пугает количество букв. Как минимум,/** @type { заменяется на : .


                1. flancer
                  22.04.2024 14:28

                  Спасибо, что привели в пример :) Так-то я макросы использую. Нажимаешь горячую клавишу и вся комбинация `/** @type {} */` автоматом рисуется. Многословно? Пожалуй. Зато без транспиляции ;) Дебажить в браузере - одно удовольствие!


                  1. doctorw
                    22.04.2024 14:28

                    Вы боитесь транспиляции, как я понял. Почему?


                    1. flancer
                      22.04.2024 14:28

                      Ответил выше. Я не пишу код без ошибок, я просто их быстро нахожу.


          1. aamonster
            22.04.2024 14:28
            +1

            Ну ok. Не так важно, как именно внедрена типизация, лишь бы она была)


            1. bromzh
              22.04.2024 14:28

              Всё-таки важно. Она должна быть удобной (чтобы ты ей пользовался, а не избегал) и обязательной (иначе в один момент просто забудешь запускать проверки).


      1. aamonster
        22.04.2024 14:28

        Необходим – не скажу, но я помню, какое счастье было, когда js-часть нашего проекта перевели на ts. Стало на порядок легче жить.


    1. bromzh
      22.04.2024 14:28
      +5

      Зачем? Уже есть typescript. Система типов там ощутимо лучше C#. Некоторых языковых фич C# может и не хватает, но не так чтобы прям критично.


      1. GospodinKolhoznik
        22.04.2024 14:28

        Система типов typescript более менее неплохая. Проблема только том, что компилятор typescript зачастую в упор не видит несоответствия типов в выражениях при чуть более сложных типах, чем тривиальные. Что сводит на нет преимущества типизации.


        1. bromzh
          22.04.2024 14:28
          +3

          Можно пример?


          1. olivera507224
            22.04.2024 14:28

            Предположу, что человек имел ввиду отсутствие номинальной системы типов в TS. Проблема действительно есть, но преимущества типизации она, конечно же, на нет ни в коем разе не сводит. Где-то читал, что номинальную систему в будущем всё же хотят ввести. Сейчас проблема частично решается брендированием.


            1. Alexandroppolus
              22.04.2024 14:28

              Да и в структурной типизации TS хватает всякого странного, например вот


              1. nin-jin
                22.04.2024 14:28

                Это ожидания у вас странные. Всё нормально тс там вывел.


              1. olivera507224
                22.04.2024 14:28

                Прости, а что странного в приведённом тобой примере?


                1. Alexandroppolus
                  22.04.2024 14:28

                  Ну, например, что в виде параметра может закатиться объект с полем y, которое не number, а TS уверен, что проверки на наличие поля достаточно. Результат можно увидеть, нажав кнопку Run.


                  1. olivera507224
                    22.04.2024 14:28

                    Да, согласен с тобой. Действительно неожиданное поведение. Для меня странно что in в таком контексте почему-то работает как typeguard, хотя я этого не ожидаю. Как и ты, при написании такого я был бы уверен, что после проверки на наличие ключа TS должен бы вывести A & { y: unknown } .


                    1. bromzh
                      22.04.2024 14:28

                      после проверки на наличие ключа TS должен бы вывести A & { y: unknown }

                      В этом месте не будет выведения типа, а только сужение из уже имеющегося множества. Изначально у нас тип param равен A | B , так что в дальнейшем он не может вывести в другой тип


                      1. olivera507224
                        22.04.2024 14:28

                        A & { y: unknown } это тип, который расширяет А. Вполне логично, что я могу передать его в функцию, и до передачи в функцию TS это учитывает. А вот внутри функции уже забывает от этом. Проблема в том, что TS здесь считает что все, что имеет х и у - это B, но это не так, потому что у у B ограничен конкретным типом number.


                1. bromzh
                  22.04.2024 14:28

                  Там нежданчик не в месте проверки и вывода типа param , а в месте вызова функции. Странно то, что можно в функцию передать obj, потому что он не соответствует типу A | B, который принимает функция. Тип у obj выводится компилятором как { x: number, y: null }, но функция принимает либо { x: number }, либо { x: number, y: number } . Там на месте y может быть любой тип, и функция всё-равно его схавает. А ты вроде как ждёшь только A или B, в котором y будет number.


                  1. olivera507224
                    22.04.2024 14:28

                    Да, это я проверил уже не с телефона, действительно поведение не очевидное. То, что функция принимает всё, что расширяет A - это логично, так и должно быть. С толку сбивает другое - проверка на 'y' in param сужает тип с A | B до B, хотя должно бы сужать до A & { y: unknown } .


                  1. Alexandroppolus
                    22.04.2024 14:28

                    Передаваемый объект соответствует типу А по правилам TS, значит соответствует и типу A | B

                    В моем примере A | B вообще должно схлопнуться до A, потому что B - подтип A. Но даже если и не был подтипом, всё равно проверка недостаточна.


              1. olivera507224
                22.04.2024 14:28

                Для того чтобы избежать такой ситуации, стоит использовать tyguards. Вот так отработает правильно:

                type A = {
                    x: number;
                };
                
                type B = {
                    x: number;
                    y: number;
                };
                
                function isB(param: object): param is B {
                    return 'a' in param && param.a instanceof Number && 'y' in param && param.y instanceof Number;
                }
                
                const f = (param: A | B): void => {
                    if (isB(param)) {
                        // здесь param почему-то B, вместо очевидного A & {y: unknown}
                        console.log(param.y.toFixed());
                    }
                };
                
                const obj = {
                    x: 1,
                    y: null,
                };
                
                f(obj);
                

                Опять же, для того чтобы не опростоволоситься - нужно заранее знать, что TS в приведённом тобой примере будет вести себя таким образом.

                Либо, как вариант, проверить можно вот так:

                if ('y' in param && (param as any).y instanceof Number)

                Но я с тобой согласен и в твоём примере такого поведения TS не ожидал.


              1. KataevAS
                22.04.2024 14:28

                На самом деле тут нет ничего странного, результат пересечения типов B | A = { x: number, y?: number }, именно поэтому y - может иметь значение null от этого и необходимо отталкиваться в порядке проверок внутри функции ПРИМЕР


                1. KataevAS
                  22.04.2024 14:28

                  Во избежание критики, поправлюсь не пересечения, а объединения типов, описался. Чтобы получить ожидаемый результат, приведу еще один пример


                  1. Alexandroppolus
                    22.04.2024 14:28

                    Но только если B | A заменить на { x: number, y?: number }, то совсем всё по-другому, проверки делаются корректно.

                    Ваш второй пример тоже не сообщает об ошибке типов, если немного поменять obj


                    1. KataevAS
                      22.04.2024 14:28

                      Вы правы, но это буквальное понимание, реальное я не могу имитировать средствами js, тут больше подойдут картинки из объяснения механики работы объединения.

                      По поводу изменений второго примера, больше походит на попытку "обмануть систему", типизация в TS имеет некую "синтетику", в которой правила игры Вы описываете на этапе типизации исходных данных, т.е. если вы не описали, что полученный данные могу иметь null, то TS и не будет об этом знать, это Ваш контракт с собственной типизацией проекта. Реальность же будет выглядеть так, что если вы подготавливаете исходные данные, то и гарантировать их соответствие придется самостоятельно, например, просить TS явно их валидировать.


                      1. Alexandroppolus
                        22.04.2024 14:28

                        В этом последнем примере - частный случай: объектный литерал, создаваемый вручную "на лету". Он дополнительно проверяется на отсутствие посторонних полей в объекте. А для всего остального - только проверка указанных полей.


          1. GospodinKolhoznik
            22.04.2024 14:28
            +1

            Вот так вроде всё хорошо, т.е. компилятор ругается:

            type Just<A> = {fromJust: A}
            
            var x : Just<Number>;
            
            // Тут всё ОК - возникает ошибка: Type 'string' is not assignable to type 'Number'.
            x = {fromJust: "string"};
            console.log(x);
            

            Однако если сделать так, то компилятор не видит проблемы:

            type Maybe<A> = Nothing | Just<A>
            type Nothing = {}
            type Just<A> = {fromJust: A}
            
            var x : Maybe<Number>;
            
            x = {fromJust: "WTF? String is not a number!"};
            console.log(x);


            1. Alexandroppolus
              22.04.2024 14:28

              {} - это не "пустой объект", а "что угодно, кроме null и undefined". В переменную х можно воткнуть любое значение, кроме этих двух.


              1. GospodinKolhoznik
                22.04.2024 14:28

                Тогда я признаю, что неправ. По-видимому я действительно неправильно использую интерфейсы (и не только интерфейсы) typescript.


            1. olivera507224
              22.04.2024 14:28

              {fromJust: "WTF? String is not a number!"} расширяет {} - любой объект, поэтому компилятор считает, что это Nothing , а не Just<Number> . По сути, тип Maybe<A> определён как какой угодно объект или {fromJust: A} . Потому эта запись валидна.


    1. meonsou
      22.04.2024 14:28

      Вы не поверите ... https://github.com/nikhilk/scriptsharp

      Это буквально то, с чего начинался TS. Ребята компилировали C# в JS, затем обратились к Хейлсбергу и начали делать новый ЯП. Ранний TypeScript пытался быть "SharpScript", поэтому в нём есть интерфейсы, перечисления и прочие фичи, которые в современном его виде только мешают и создают путаницу.


  1. wdhappyk
    22.04.2024 14:28
    +5

    Данный подход не совсем уместен, так как связывает руки в ряде случаев. Основное отличие интерфейса от типа в том, что интерфейс по сути является partial и мы можем расширить его нужной структурой не прибегая к оберткам и дополнительным типам. А типы в свою очередь поддерживают вычисления и типы-суммы.

    Мы в работе используем иной подход разделения, когда контракты объектов данных и сервисов представляют из себя интерфейс, а типы используются для утилитарных, существующих только на клиентской части элементов, так как использование union и прочих сложных вычислений над типами сложно повторить в других ЯП (ОО). И конечно там, где функциональность интерфейсов не важна.


  1. Djaler
    22.04.2024 14:28

    А неправильно то что?


  1. meonsou
    22.04.2024 14:28
    +2

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


    1. aamonster
      22.04.2024 14:28

      Множественное наследование от интерфейсов безболезненно, множественное наследование от типов тянет за собой ряд проблем.


  1. ArthurG
    22.04.2024 14:28

    Спасибо за статью!

    В интерфейсах есть баг в TS, лучше определять функции в полях, а не методы.

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


    1. meonsou
      22.04.2024 14:28

      В интерфейсах есть баг в TS, лучше определять функции в полях, а не методы.

      Что за баг?


      1. ArthurG
        22.04.2024 14:28

        В методах плохая проверка типов аргументов.


        1. meonsou
          22.04.2024 14:28

          Так это не баг, это by design у них. Так сделано чтобы привычные практики из жса у людей не ломались.

          During development of this feature, we discovered a large number of inherently unsafe class hierarchies, including some in the DOM. Because of this, [strict function types] only applies to functions written in function syntax, not to those in method syntax.


          1. Alexandroppolus
            22.04.2024 14:28

            Если речь об этом, то такая проблема не только в интерфейсах, но и в типах и даже, как ни прискорбно, в классах


  1. KataevAS
    22.04.2024 14:28
    +1

    Весь пример из статьи можно полностью написать на inteface и так же полностью на type. Правильно я понимаю, что Вы не предлагаете разбираться чем же все-таки они отличаются, а предлагаете соглашение для дополнительного смыслового разделения? Тогда вопрос такой - что Вам мешает использовать type для описания поведения объектов и их методов и продолжать все так же разделять исходный код на:

    1. ... определенные атрибуты объектов.

    2. ... поведение объектов, их методы.

    Интерфейсы позволяют разделять реализацию от так называемого слоя бизнеса.

    Получается, что type тоже это позволяет?

    Ощущение, что статья больше не про применение interface (особенно удобного в ООП) и type, а про разделение сборки свойств объектов.


    1. AidarNasibullin Автор
      22.04.2024 14:28

      Да действительно, все можно написать и на типах и на интерфейсах,интерфейсы позволяют не привязываться к конкретной реализации чего либо. Если я правильно Вас понял, при использовании например типов, придется писать адаптеры, которые будут приводить конкретную реализацию, к типу, который необходим конкретной функции.


      1. KataevAS
        22.04.2024 14:28
        +1

        Возможно, в примере не хватает некоторой логики, которая заставляет Вас применять адаптеры, т.к. мне они не понадобились, вот Ваш пример на типах