Pull to refresh

Comments 38

add = (path, cb) => {

В чём профит от такого объявления метода класса?

Чем это луше обычного объявления?
add(path, cb) {
Это не лучше, но и не хуже. Автор просто не любит явный код.

Данный код
class Test {
  myMethod = () => {
    ...
  };

  set = () => {
    setInterval(this.myMethod, 300);
  }
}


После компиляции тайпскриптом или бабелем становится таким
class Test {
  constructor() {
    this.myMethod = () => {
      ...
    };
    this.set = () => {
      setInterval(this.myMethod, 300);
    };
  }
}


Это что бы можно было передавать метод в setTimeout/setInterval не прибегая к bind или явному указанию стрелочной функции. Так же минус этого подхода в том, что это это не метод как таковой, а проперти инстанса класса. Наследование работать не будет.

Лично я предпочитаю явный код
class Test {
  myMethod() {
    ...
  }
  
  set() {
    setInterval(() => this.myMethod(), 300);
  }
}
Ну и хорошо, что наследование не будет работать, вместо него лучше композиция: en.wikipedia.org/wiki/Composition_over_inheritance
Необходимость помнить о потере this — лишняя когнитивная нагрузка, а бесконечный поток статей про контекст в JS тому подтверждение.
Ну и хорошо, что наследование не будет работать, вместо него лучше композиция

Но зачем тогда вам вообще class-ы?

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

Ну дык вопрос тот же. Зачем вам классы? Хотя кажется я знаю ответ — декораторы.


P.S. ещё вспомнил, в nest JS за счёт классов пытаются runtime type-checking делать.

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

Что значит явный/неявный? Class properties уже поддерживаются и в Chrome, и в Firefox, конвертировать совсем не обязательно.


И интересно, почему наследование работать не будет? Конструктор родителя вызовется первым, все нормально переопределится


class Parent {
   getValue = () => 1
}

class Child extends Parent {
   getValue = () => 2
}

console.log(new Parent().getValue()) // 1
console.log(new Child().getValue()) // 2

(ворчание) А потом они удивляются, почему вкладка с todo-list app съедает пару сотен мегабайт оперативки...


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


В JS наследование реализуется через прототипы, а не методы в class properties


Вот такой код


class Test {
  myMethod() {
    ...
  }

  set() {
    setInterval(() => this.myMethod(), 300);
  }
}

фактически означает вот это


function Test() {}

Test.prototype.myMethod = function() {...};

Test.prototype.set = function() { 
    setInterval(() => this.myMethod(), 300); 
};

А вот такой код


class Parent {
   getValue = () => 1
}

фактически означает


function Parent() {
  this.getValue =  () => 1
}
А потом они удивляются, почему вкладка с todo-list app съедает пару сотен мегабайт оперативки...

Come on… Вы сами себя обманываете. Следите за руками:


// variant 1
class Parent {
   getValue = () => 1 // +1 method
}

// variant 2
setInterval(() => this.myMethod(), 300); // +1 method

// variant 3
this.myMethod = this.myMethod.bind(this); // +1 method

Где вы видите экономию сотен MiB памяти?


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


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

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


Если у вас всего один экземпляр класса, то вы не заметите разницу. Только зачем вам тогда вообще классы?


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

В варианте 2 память выделяется при вызове при вызове метода, в котором лежит setInterval, и освобождается после завершения работы

В данном случае время жизни интервала равно времени жизни router-а. Память не высвобождается никогда (пока жива страница).


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


Сразу отмечу, что весь этот спор обретает хоть какой-либо смысл только тогда, когда у вас большие объёмы. Скажем это ядро какой-нибудь сетевой игры, или скажем недра реактивного-фреймворка, или некий html-шаблонизатор. Если речь не идёт о десятках тысячах объектов, то это последнее место, в котором стоит обсуждать вопрос памяти.

А практическая разница от этих отличий какая? Наследники перекрывают свойства у родителей, что еще нам нужно?


Патчинг свойств у прототипа для тестирования, как показывает faiwer – это анти-паттерн, такого кода лучше избегать.

это анти-паттерн, такого кода лучше избегать.

Мне кажется тестирование это в целом кладезь самых разных антипаттернов в одном месте. Все эти stub-ы, mock-и и пр. Особенно в динамически типизированных языках.

Функции — точно такой же объект, как любой другой. Их даже создавать можно через конструктор new Function.


Если метод лежит в прототипе, то все потомки по prototype chain получают возможность использовать одну копию метода.


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

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


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


Автор просто не любит явный код.

class Test {
  myMethod = () => {}
}

class Test {
  constructor() {
    this.myMethod = () => {};
  }
}

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

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

Подход с class-properties применимо в методам используется не всегда, а тогда, когда нужно за-bind-ить метод к instance-у его объекта. Тут та же зависимость: чем больше экземпляров, тем больше расход памяти.

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

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

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

Так же настоятельно рекомедую почитать документацию о «window.history» и событиях hashchange/popstate.
Согласен. Ужасное решение.

А почему в JS избегают именованных маршрутов?

Мне кажется, концепция "роутер" переоценена. Почему, просто в стэйт не писать распарсенный url? Почему урл вообще занимает какое то особенное место в источниках данных мне непонятно.

Это тоже слегка не то, по крайней мере в примерах, опять, точки входа, явно описываются компоненты итд. Может у меня конечно профдеформация, т.к. те спа, которые я пишу, не требуют роутера в принципе. Но что то мне подсказывает, что есть проблема восприятия — считается, что адрес, это информация от юзера, но в спа на самом деле, это генерированная приложением информация, своеобразный сторэдж, почему бы по /login просто не отрендерить диалог входа на главной, вместо того, чтобы роутить в непонятного дикобраза, который возьмет главную как декоратор и заинжектит туда форму входа? Например.

В плюсы роутинга можно отнести:
  • навигация средствами браузера Back/Forward
  • навигация по истории браузере
  • линк можно сохранить в Bookmarks и открыть нужную страницу быстро
  • линк можно скопировать и отправить другому юрезу, к примеру линк на какой нибуть репорт


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

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

Хотел добавить, что я не против использования url, и тем более history api. Я против такой фиксации на таких неважных вещах. По моему адрес из строки в SPA должен обрабатываться в бизнес логике, а указывать компоненты как точку входа, непонятно зачем.

Какая же это бизнес-логика? Вроде чистая логика отображения и компоненты вполне в ней уместны.

Мне кажется вы путаете Backend API url и Frontend url в SPA.

Для бекенда есть своя точка входа, куда вы отправляете запрос и получаете результат — список данных, созданный, обновленный или удаленный объект итд. Бизнес логика приложения бекенда.

Для фронтенда url служит для указания страницы или состояния. К примеру url откроет страницу с каким нибудь отчетом или графиком за период. Или это может быть страница товара или услуги. А под капотом у этой страницы, могут быть несколько запросов к BE API. И это уже логика фронтенда. И url это удобный инструмент для фиксирования или указания состояния.

В каждом случае url для BE и FE служат разным целям.
И url это удобный инструмент для фиксирования или указания состояния.

Как не ищу, всё никак не могу найти плюсы клиентского роутинга. Напротив — одни проблемы. Особеннно когда делают несколько уровней вложенности, а в особых случаях даже на попапы отдельные роуты заводят. Но при этом забывают, что пользователь может нажать f5 — и всё то, что он накликал до этого теряется и приложение падает. А потом просят исправить.

В плюсы роутинга можно отнести:
навигация средствами браузера Back/Forward
навигация по истории браузере

Чувствуете «семантику»?..)
Браузерные кнопки Back/Forward — служат для навигации по истории БРАУЗЕРА. Перебивать его клиентским роутингом — это какой-то костыль, можно сказать «несемантично».
Если нужна навигация по истории внутри ПРИЛОЖЕНИЯ — это делается кнопками в интерфейсе самого приложения. Как правило всегда есть кнопки «назад», «отменить» и т.п. Если таких кнопок в интерфейсе нет — то значит навигация этому приложению и не нужна вовсе.
SPA — ОДНОстраничное приложение, опять «семантика» ощущается..) Оно как бы за себя говорит — что должно находится на одном серверном роуте.

На самом деле для себя вижу только один кейс для клиентского роутинга — когда пользовательский сценарий приложения предусматривает сценарий «поделиться ссылкой на текущую вьюху» (тот же Zeplin онлайн допутим). Т.е. это есть в требованиях. Буду рад если подскажите ещё кейсы.

Можете сказать, что есть сценарий заполнения длинной формы/визарда, и при рефреше она должна сохранятся. Ок. Только вот как правило такие формы/визарды сохраняют промежуточные шаги на сервере. Восстановить нужный шаг не проблема, роутинг для этого не нужен.

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

Вобщем мой поинт в следующем:
— Роутинг — это про навигацию по истории браузера.
— Для навигации по истории приложения — лучше использовать элементы интерфейса самого приложения.
— Добавлять клиентский роутинг — только при явных требованиях, а не раньше времени, просто потому что так принято (ещё и ~+25кб тянуть).

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

Перебивать его клиентским роутингом — это какой-то костыль, можно сказать «несемантично».

«Вы не любите собак? Вы просто не умеете их готовить.» (с)

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

SPA — ОДНОстраничное приложение, опять «семантика» ощущается..) Оно как бы за себя говорит — что должно находится на одном серверном роуте.

SPA и роутинг это как сравнивать теплое с мягким. SPA это в первую очередь веб приложение, которое не требует перезагрузки страницы. Что позволяет: снизить нагрузку на бекенд — не нужны затраты на серверный рендеринг; снизить нагрузку на браузер — не надо заново рендерить всю страницу.

Роутинг это указание текущего состояния или страницы. К примеру имеем шаблон роута

/controller/action(/arg1)(/arg2)(/argN...)?options

И сразу как пример страница редактирования товара

/product/edit/12345

Второй пример отчет по продукту

/product/report/12345?dStart=01.04.2020&dEnd=15.04.2020&sortName=name&sort=asc

Глядя на примеры выше, кадый поймет что должно быть на странице. И трактовать двояко никак не получится. Это и есть семантика роутинга.

Даже если юзер нажмет f5, он попадет на нужную страницу с нужным контентом. Так же юзер без пробмем может совершать навигацию по приложению, кликая на линки или использую кнопки back/forward. Я постоянно наблюдаю ситуацию, когда пользователь просто кликает раз пять «back» что бы вернутся на какой нибыть продукт, который был открыт ранее. Потому что так проще и быстрее, чем открыть страницу списка товаров, кликнуть в поле поиска и вбить имя товара или его номер.

Можете сказать, что есть сценарий заполнения длинной формы/визарда, и при рефреше она должна сохранятся

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

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

Плюсы роутинга я написал выше.
Добавлю еще один пункт к плюсам — табы браузера, когда линк можно открыть новом табе. К примеру сравнить какие нибудь данные.

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

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

Я постоянно наблюдаю ситуацию, когда пользователь просто кликает раз пять «back» что бы вернутся на какой нибыть продукт, который был открыт ранее. Потому что так проще и быстрее, чем открыть страницу списка товаров, кликнуть в поле поиска и вбить имя товара или его номер.

Если вы говорите про какие-то интернет магазины / блоги, где основной так сказать «business value» — это фильтры и карточка товара / статьи, которые должны в первую очередь индексироваться поисковиками, то делать это как SPA считай разводить заказчика на деньги.
Такие вещи делать необходимо как раз-таки с классическим серверным рендерингом (не SSR который оживляет SPA, а по старинке — серверные html-шаблоны), и с серверным роутингом. При этом где какой роут (его название, глубина и т.д) должен соответствовать какой странице — решает в первую очередь seo-специалист, а не фронт на свой вкус. Да, внезапно, на бекенде придётся больше поработать чем на фронте..)

А как вы представляете себе тот же vk или facebook без client routing-а? К чёрту ссылки? Пусть всё ведёт на главную? Или пусть любая ссылка перегружает всё приложение? Серьёзно?


На самом деле для себя вижу только один кейс для клиентского роутинга — когда пользовательский сценарий приложения предусматривает сценарий «поделиться ссылкой на текущую вьюху» (тот же Zeplin онлайн допутим). Т.е. это есть в требованиях. Буду рад если подскажите ещё кейсы.

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

Сохранить в закладках прежде всего

А как вы представляете себе тот же vk или facebook без client routing-а?

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

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

Сложный личный кабинет подразумевает routing


с максимум каким-то сайдбаром сбоку

Если sidebar содержит "разделы", то это автоматически подразумевает роутинг.


Если его не было в требованиях, то требования были сформулированы плохо.


На самом деле сложно найти SPA размером больше 5-10 тысяч строк, где не был бы нужен хотя бы элементарный routing. Это уже скорее не SPA даже.

Не могу не добавить, что когда важный и сложный SPA написан без routing-а хочется убивать. Это страшно неудобно. Такое ощущение, что такие приложения пишут люди, которые всё остальное время пишут мобильные приложения и просто не понимают, что такое URL

Адрес в адресной строке — это как информация от юзера, так и информация от приложения. По сути это controlled input.

Sign up to leave a comment.

Articles