Search
Write a publication
Pull to refresh

Comments 31

  1. При реализации интерфейсов, и работе с объектами корректность соблюдения принципа подстановки Барбары Лисков ложится на плечи разработчиков.

Просто надо по возможности использовать "стрелочную" форму, тогда всё будет окей.

interface IA {
  setValue: (a: number | string) => void;
}

Ага, спасибо. На всякий допишу тут ещё 2 поинта (если вдруг у читателей возникнут вопросы). Во первых, для типов/интерфейсов нет разницы в поведении между стрелочной и "обычной" формой, как для объектов js (значения this, super, и т.д.), и реализовывать в классе можно и так и сяк, что видно в примере. Во вторых, если нужны перегрузки, то, например, аналогом такого для "обычной" формы

interface IA {
  setValue(a: number | string): void;
  setValue(a: boolean): boolean;
}

будет такой код

interface IA {
  setValue: {
    (a: number | string): void;
    (a: boolean): boolean;
  };
}

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

Сам я еще кол-сигнатуры не перегружал, но выглядит как рабочее решение.

interface IA { setValue: (a: number | string) => void;}

Попробовал использовать и что-то не пошло.

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

Уже не могу вспомнить контекста.

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

Сейчас уже не могу вспомнить где с этим столкнулся. Сам только что проверил и strict этому никак не препятствует. Я поэтому из статьи и не стал это убирать.

Я сейчас посмотрел, если интерфейсы задавать через абстрактные классы как раз и получается:

TS2425: Class <AbstractClassName> defines instance member property <Method>, but extended class <ExtendedClassName> defines it as instance member function.

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

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

Почему? Такое же решение как и то с которым столкнулся я. Если на то пошло, то поведение языка на данный момент само по себе является хаком.

Чего конкретно тайпскрипт не поддерживает в классах? Мне приходит в голову только return в конструкторе, которым в принципе крайне редко пользуются даже в жс.

наследовование со статическими методами и генериками например, возвращаю this часто из конструктора например прокси

наследовование со статическими методами и генериками

Не понял о чём речь. Наследование есть, статические методы есть, наследование статических методов есть. Генерики там на месте.

Во "Вариант 2" вы явно забыли комменты с результатами // true | false :

type E3 = TC extends TD ? true : false
type E4 = TD extends TC ? true : false

Кстати да, поправил. А то вроде как именно для этого и писалось XD

type TC = (a: number | string) => void
type TD = (a: string) => void

мне кажется впихивать 2 типа в 1 не самый лучший вариант, ведь всегда можно сделать перегрузку разве нет?

type TC = (a: number) => void;
type TD = (a: string) => void;

const foo: TC & TD = (a: string|number) => {
  if(typeof a === 'string') {
      // logic
  } else {}
  
}

Перегрузка полезна, когда у нас есть связь между параметрами и выходным значением. Когда такой связи нет в перегрузке тоже никакого смысла нет.
А второй момент, где в вашем примере перегрузка?
К тому же, данные примеры можно контролировать на ts используя соответствующие настройки типа strict.

Вместо тысячи слов :)
Вместо тысячи слов :)

[занудамод]Тут у меня придирка конкретно к примеру - зачем делать так, если нужно делать так. Если пример только для примера - то всегда будет такое. Советую пытаться находить "рабочий" код для таких вещей- так как если в начале возникают сомнения, то дальше быть солидарным с автором становиться всё сложнее и сложнее :) [/занудамод]

А так делать не нужно. Выше я указал, когда нужна перегрузка.
Обратите внимание, у нас функция возвращает void. Никакого смысла ее перегружать нет. Например, если бы она у нас всегда возвращала string ее тоже не нужно было бы перегружать. Поэтому мой пример совершенно валидный и спокойно проецируется на любой продакшен.

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

И мне будет любопытно узнать, как ваш подход можно использовать для настоящей перегрузки, типа когда `(a: number) => number` и `(a: string) => string`

Мне интересно с каких пор перегрузка параметров является антипатерном? Может быть если я напишу вот так - то всё будет нормально?

class Point {
  constructor(private x: number, private y: number) {}

  move(d: string): void
  move(d: number): void
  move(d: string | number) {
    this.x += +d || 0;
    this.y += +d || 0;
  }
}
const a = new Point(1,1);

a.move('1')

Тогда же опять проблема в примере - и это то что по моему мнение не очень удачно подобрано.


ну а последнее вообще странно

(a: number) => number
(a: string) => string

так как любой маломальский знающий TS напишет

<T>(a: T) => T

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

Поэтому у меня с примерами в этом плане все нормально. А вы разными способами используете анти шаблон. То, что вы сейчас написали - это тоже анти шаблон.

По поводу дженериков, все верно. Очень часто, когда знакомят с дженериками показывают их работу как естественную замену перегрузки функции. Кстати, и у дженериков есть абсолютно такое же правило, что должна быть связь между данными. Там так же <T>(a: T): void является анти шаблоном.

Можете для ясности number/string заменить на какие-нибудь уникальные интерфейсы и тогда у нас будет не функция идентичности, а какая-то фабрика.

Можно пожалуйста ссылку на источник где сказано что это антишаблоны, ни капли не верится

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

https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#use-union-types

Ну так загляните в эту доку - буквально тоже самое что я написал, только не воид а Moment возвращается :D

function fn(x: string): Moment;
function fn(x: number): Moment;
function fn(x: number | string) {
  // When written with separate overloads, incorrectly an error
  // When written with union types, correctly OK
  return moment().utcOffset(x);
}

а смысл от этого огромный - так как типизация это одновременно документация:

Уже первый вариант более приятный, так как относится к конкретному юзкейсу. А если число типов которые могут быть переданы будет намного больше и это будут не примитивы, а какие-нибудь сложные классы, которые значительно влияют на исполнение(например возникает возможность выброса особых исключений) то первый вариант будет юзерфрендли, в отличии от второго. И материть будут за второй код, в то время как за первый будут хвалить :D

Ну так загляните в эту доку - буквально тоже самое что я написал, только не воид а Moment возвращается :D

В документации написано, что так как вы советуете делать не надо. На этом точка.

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

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

/**
 * @param {string} x maybe string value
 * @param {number} x maybe number value
 */

Я смотрю в результирующей справке нормально все сливается. Зачем вы тогда столько мусора в код добавляете?

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

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

А заодно познакомите сообщество с тем как надо оформлять функции. А то открываешь такой lib.es5.dt.s, а там один за другим:

     /**
     * Replaces text in a string, using a regular expression or search string.
     * @param searchValue A string or regular expression to search for.
     * @param replaceValue A string containing the text to replace. When the {@linkcode searchValue} is a `RegExp`, all matches are replaced if the `g` flag is set (or only those matches at the beginning, if the `y` flag is also present). Otherwise, only the first match of {@linkcode searchValue} is replaced.
     */
    replace(searchValue: string | RegExp, replaceValue: string): string;

    /**
     * Replaces text in a string, using a regular expression or search string.
     * @param searchValue A string to search for.
     * @param replacer A function that returns the replacement text.
     */
    replace(searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string;

И ведь надо же, всем все понятно. А так, получается, делать нельзя. Мало места занимает. Нужно использовать липовые перегрузки функций. Кстати, обратите внимание, ваш бесполезный код для одной функции, из примера выше, занимает 10 строк, выразительная документация с лишней пустой строкой двух функций занимает 13 строк. Просто, еще одна информация к размышлению.

Странный какой-то комментарий. В статье говорилось о контравариантности параметров, и флаге strict, для того чтобы это нормально проверялось. Причем тут перегрузки?

Sign up to leave a comment.

Articles