Как стать автором
Обновить

Комментарии 191

Не понял, а почему в истории №2 вообще считалась высота футера до полной загрузки страницы?

Имеется в виду почему не было подписки на событие DOMContentLoaded?

Потому что ReactDOM.render обычно синхронно исполняется. Вон и в популярнейшем create-react-app так и сделано. Если перенести код в обработчик, то тоже работать будет.

Ну серьёзно? Призываете обращаться к "чистому CSS", а сами через JS растягиваете сайдбар на высоту контента? Футер к низу тоже через JS прибиваете?

Что-то с этим фронтендом полный мрак творится... В современный CSS уже столько возможностей впихнули, что использовать JS для управления представлением надо в каких-то исключительных случаях. Но вместо того, чтобы разобраться хотя бы с азами CSS, падаваны фронтенда элементарный лэйаут страницы обсчитывают на JS... Ну ещё бы - они же там не какие-то тупоголовые верстальщики (как мне недавно заявил один товарищ - "10 лет заниматься примитивной вёрсткой и не вырасти до программиста - это надо совсем тупым быть")...

Примерная формула получается такая: 100% - headerHeight - footerHeight

Это из статьи) как бы вы это при помощи css сделали?

есть grid layout, но я не фронтендер)

CSS variables + calc? Grid? Flexbox (row + row { column.sidebar + column.content } + row)?

и чем это решение лучше решения на js?

ну вот навскидку:

1. браузер обработает CSS раньше чем JS (в большинстве случаев)
2. может быть частью темы
3. не тащит за собой половину интернета
4. не ломается при отключенном JS в браузере
5. не ломается из-за раздобланного в другом месте JS
6. не вызывает бесконечных перерисовок

Да, я тоже за то чтобы использовать css всегда, когда только можно, но с подход css-переменными не сработает, если высота хедера и футера вычисляется динамически в зависимости от контента, а с флексами и Grid'ами может потребоваться переписывание стилей (разметки?) всех страниц, на которых есть хедер и футер

В современном CSS у соседних блоков равная высота по умолчанию. Что во флексбоксах, что в гридах. «Что-то делать» надо, если надо что-то другое :)

Кхм... Вы сейчас не шутите?

Это можно сделать... ну пятью способами уж точно. Вот самая простая для понимания принципа, реализация на флексбоксах:

html{
  height:100%;
}
body{
  display:flex;
  flex-flow:column nowrap;
  height:100%;
}
.content{
  flex:1 0 auto;
  display:flex;
  flex-flow:row nowrap;
}

По вашему это решение эквивалентно? Оно действительно учитывает высоту шапки и высоту футера?

А теперь представьте, что ваша страница длиннее одного экрана

Теперь всё это не влезает в экран

А почему вообще программисты лезут в вёрстку? По мне, это разные профессии. Как оно должно выглядеть: верстальщик создает шаблон страницы, программист пишет скрипты, чтобы заполнить шаблон данными. Если вы поставили перед программистом задачу, то почему вы удивляетесь, что он решает её своими средствами, то есть программированием? Ой, программист не любит CSS, как же так вышло? Так и вышло, что CSS специально создавался для того, чтобы им пользовались непрограммисты. А теперь у вас так получается, что программисты обязаны использовать не свойственные им инструменты и кипите возмущением, когда они это делаеют не достаточно эффективно.

Боюсь, потому, что заказчикам надо "все и сразу и побыстрей а еще нарисуй нам макетик веселенький". В идеальном мире макет, фронт и бэк - это три разных человека (а возможно, БД - еще один), но где он, этот идеальный мир :(

Хотят говнокод — получают говнокод, и некого тут обвинять, кроме самих заказчиков.
И ладно бы, но они же других заставляют (по крайней мере, пытаются заставлять) писать этот самый говонокод. Извините, наболело.
Если бы они — заказчики — ещё понимали, что это три, а то и четыре, разных человека, а не один. По моим ощущениям, чуть не половина из них верует в Тыжпрограммиста и считает, что программист должен и серверную часть написать, и морду сверстать (неуачо, тыжпрограммист), и эту морду запрограммировать (хорошо, если не спрограммировать, а то и такие бывают), а потом ещё и сервер с нуля настроить.
А что если я всё это могу, если надо (мне самому) будет?

Как-то давно жена работала дизайнером-верстальщиком в конторе по наружной рекламе. Сделали заказчику макет, он недоволен. Переделали — недоволен. Пришёл сам, начал руководить — всё равно получается не как ему нравится. Заорал "Да у меня племянник на компьютере, дайте мне файл — я ему отдам или сам всё сделаю!"
Отдали ему рабочий файл. День нет, другой, на третий приходит уже тихий: "Надо же, я и не знал, что это так трудно. Сидел-сидел, вертел и так и так — ничего не выходит. Наверное, я чего-то не того хочу." Это, заметим, заказчик умный и честный, а они далеко не всегда такие.

Вы вообще не о том. Фронтенд-программист обязан знать CSS. Поскольку его основная задача - "пилить под ключ" всю клиентскую часть. JS изначально был заточен на тесную поддержку CSS. Как без знания CSS можно вообще "делать морды"? В чём тогда обязанности программиста вообще должны заключаться? Взять данные от api и распихать их по структуре?

Ну представим вашу идеальную картину. Верстальщик сделал шаблон, программист заполнил его данными. Бац, в дизайне нужна всплывашка. Кто её будет делать? Верстальщик её сверстает, а программист навесит обработчик, который будет менять класс? А кто этот класс будет прописывать? А если нужна анимация - кто её прописывать должен? А если какие-то манипуляции с DOM? Нестандартные элементы форм? Здесь граница компетенций сейчас настолько размыта... Как по мне, всё это должен делать один специалист - фронтенд-разработчик. И он должен знать CSS. Пусть не на уровне гуру вёрстки, но уж точно не строить лэйаут документа путём обсчёта css-значений через js.

И что это вообще за тезис: "Так и вышло, что CSS специально создавался для того, чтобы им пользовались непрограммисты."? CSS создавался совершенно не для этого. Как и HTML.

А что случилось с проблемой 1? История прерывается на самом интересном месте!

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

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

Не менеджер, а скрам-мастер. Эти мастера считают себя не менеджерами, а фасилитаторами!

" - Нам с тобой некомфортно.",
" - Ты - токсичный.",
" - Ааааааа.",
и другие комментарии, будут у членов команды.

)

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

Пугающие тенденции. Что за разработчики такие? Недавно считалось "не тру" не знать основ информатики, при этом быть "веб мастером" и называться разработчиком. А теперь чтоли уже и js не зная писать на фреймворках это называется разработчик? Ох.

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

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

Многие из тех, кто называет себя фронтенд-разработчиками, на самом деле, являются <название-любимого-фреймворка>-разработчиками.

Да, встретил недавно одного такого. Для него непосильной задачей оказалась вставка отдаваемого сервером готового куска HTML в контейнер, надо было только JS, чтобы он потом насиловал процессор горами JS-кода, чтобы нарисовать ровно тот же кусок HTML. Пришлось вспоминать молодость, открывать документацию по jQuery и всё делать самому. Мог бы и на чистом JS всё соорудить, но на восстановления такого объёма знаний потребовалось бы куда больше времени, а там был «deadline близко». То есть я, почти 10 лет не трогавший фронтэнд, сделал это всё быстрее, чем как бы фронтэнд-разработчик.

не хотелось бы Вас расстраивать и дальше, но, всё же поделюсь :)

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

Все зависит от того как написать комментарий. Можно вежливо объяснить почему и зачем, а можно грубо. Учитесь софт скиллс.

Спасибо вам Борис огромное за :empty. Никогда не видел интересный пример его практического употребления, моя вина - не искал. Прям разинул рот от удивления. Сколько лишнего 'ternary' в коде! :) Бывает, однако, что мы от мусора не можем избавится. Делаем первую ревизию K.I.S.S. и YAGNI, но заказчик требует добавить/изменить/ дополнить/убрать... и мы делаем это. Потом опять, и опять. В какой-то момент ты осознаёшь что всё надо снести до фундаментa и сделать с начала... но заказчик за такое платить не будет... Потом получается вместо "keep it simple, stupid"... "keep it at least working"...

Как минимум, empty очень удобен в любом проекте, чтобы навешать его на <div id="root"></div>, чтобы показывать загрузчик до загрузки js

Стыдно сказать, я тоже не знал про :empty, хотя caniuse показывает что он давно поддерживается всеми браузерами. Вспомнил пару мест, где :empty очень пригодился бы в прошлых проектах. Хотя сам я не фронтендер )

но заказчик за такое платить не будет...

Да, но это же вам надо как разработчикам, чтобы меньше времени тратить на новые изменения

Я не в теме, поэтому позвольте вопрос: а заказчику нельзя отказать его новым хотелкам за бесплатно? Вы ведь по условному ТЗ работайте. В рамках начального ТЗ исправляем за свой счет, а новые хотелки в кассу. Эти дела прописываются в контракте?

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

Проясните, если можно. А то часто такое читаешь и как понять такие контрактные/ТЗ отношения не знаю..

Для внутренний разработки может быть и так) Тут либо собачиться с коллегами либо увольняться.

Не-не, я про работу с клиентом. Может я запутано спосил, но суть в примере:

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

Но вдруг, клиент попросил нарисовать четвертую таблицу которая не оговаривалась в ТЗ. Еще и бесплатно? Пффф, сорян, надо условно переписывать код. И что, вы это делайте? Я спрашиваю в случае подписаных контрактов, а не мамкиных контор начинающих студентов готовых на всё?

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

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

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

анекдот про NASA, которые потратили миллионы долларов на разработку шариковой ручки для условий невесомости, а советские космонавты просто взяли с собой карандаши.

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

Так что еще вопрос, что лучше - ручка за миллион или карандаш.

это вроде не анекдот, а реальная ситуация, как дело было.

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

правда и русские не совсем идиоты и карандаши в них были восковые.

с другой же стороны НАСА которые «потратили миллион долларов» на самом деле сначала писали фломастерами, а потом заключили контракт с какой-то частной компанией производителем ручек которые действительно за миллион долларов (только свой, коммерческий, а не налогоплательщиков) разработали ручку и сами пришли к ним и единственное условие было что НАСА упоминает о том что в космосе пишут ручками этой компании…

ручки эти кстати потом замени и наши карандаши и американские фломастеры

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

И разрабатывалась она для наживы, а в итоге партия которую купили была маленькая. А анекдот растиражировал Задорнов - (НАСА разработали ... за миллионы долларов...американцы тупые! а наши карандашами писали)

Вот тут подробности этой истории.

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

Я тоже за "чистый" CSS. Но по той же причине, по которой в `<Footer />` попадал то null, то пустой объект, у вас может прилетать строка из одного пробела или просто символ переноса строки, и тут `:empty` не поможет - для его корректной работы ему нужно именно "ничего" `<div class="this-is-empty"></div>`

trim?

В спецификации это уже поправили. Дело за браузерами.

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

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

Лично я с фронтендом не связан никак, но первая мысль, которая пришла в мою голову - это то, что там что-то не так с порядком в истории #2. Если человек не может догнать до этой мысли и не проверит эту гипотеху первым делом, то это очень-очень грустно.

Там кроме грифеля была история страшнее - атмосфера у американсикх кораблей была почти 100% кислородная, карандаш обычный бы загорелся бы от чего попало, да и не было "Обычных" карандашей никогда - маркеры у одной стороны (и до сих пор на МКС юзаются) и восковые карандаши с другой. Сейчас у обоих сторон те самые патентованные ручки "за миллион долларов".

В чистом кислороде не то что карандаши, человеческое тело загорается.

Как-то с трудом верится.

Не загорается, но от кислорода отказались достаточно быстро — после того, как прям на стартовом столе получили well done экипаж. заодно еще и двери сделали открывающимися в другую сторону.

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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

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

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

В общем мнение конечно интересное, но, имхо, излишне пафосно.

сегодня у нас одна логика футера, завтра другая, это каждый раз будем искать где тут empty зарыт?

изменение требований приводит к работе, да, и есть вероятность, что в этой работе придётся рыть, это естественный порядок вещей


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

Если вы тестируете вёрстку/стили на соответствие логике — то точно так же: берёте html, если блок footer пустой, у него должен быть определённый стиль (.footer.border-width > 10px). Больше похоже на изврат, но если есть желание — реализуемо

не стоит путать "естественный порядок вещей" и плохую архитектуру. При хорошей "рыть" надо минимум.

return <div>
    {children}
    {footer && <div ref={footerRef} className="footer">{footer}</div>}
  </div>

а вот эта мешанина из html и js на недоязыке в "компоненте" (который знает и как себя нарисовать, и как себя вести) вас нисколько не смущает с точки зрения архитектуры, SRP?

Ечли я тут много чего смущает, но я редко имею дело с реактом, поэтому код особо не рассматривал.

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

Если бы статья называлась "Ребята, мало кто использует :empty, смотрите как здорово", то нет проблем.

Зря вы jsx недоязыком называете — сейчас это чуть ли не единственный язык шаблонов, который можно статически типизировать (в виде tsx).


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

это чуть ли не единственный язык шаблонов, который можно статически типизировать (в виде tsx)

зачем шаблону знать и думать о типах?


чтобы выбросить исключение, которое поймается выше (там, где и так знают какой тип expected, а какой given)?
чтобы выполнить операции с объектами, которые можно (нужно) выполнить выше уровнем и передать в шаблон уже чистые данные?
можете привести пример, когда статическая типизация в шаблоне даёт очевидное приемущество?


в моём представлении шаблон это то, куда передают значения, а не типы


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

посчитайте сами количество причин для изменений, и равно ли это число единице. если нет — SRP нарушен.

зачем шаблону знать и думать о типах?

Чтобы ловить опечатки в именах атрибутов, свойств и переменных. Чтобы произвольные переменные не превращались в строки сами собой. Чтобы не ловить "TypeError: Cannot read property 'foo' of null" в рантайме.


чтобы выбросить исключение, которое поймается выше (там, где и так знают какой тип expected, а какой given)?

Чтобы при несовпадении expected и given возникла ошибка компиляции. И да, насчёт "и так знают какой тип expected" вы погорячились.


чтобы выполнить операции с объектами, которые можно (нужно) выполнить выше уровнем и передать в шаблон уже чистые данные?

У чистых данные тоже есть тип.

Чтобы не ловить "TypeError: Cannot read property 'foo' of null" в рантайме

А почему вы передаете в шаблон null?


У чистых данные тоже есть тип.

А почему вы передаёте в шаблон данные неправильного типа?


И да, насчёт "и так знают какой тип expected" вы погорячились

Ну так а как так получается, что вы передаёте что-то в шаблон, не зная что этот шаблон ожидает получить (с чем работает)?

Потому что я человек, а люди иногда ошибаются.

зачем шаблону знать и думать о типах?

Вы смеётесь?


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

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


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

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

А в чём разница в приведённом вами куске кода с тем что было бы Angular или Vue? Просто кусок шаблона. Ну будет там *ngIf, это как-то поменяет суть вещей?

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

А как бы вы это сделали?

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

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

А как бы вы это сделали?

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

Этого конечно же в указанной истории никто не делал.

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

Насколько я знаком с Angular, там ровно такая же проблема:

<div class="footer">
   <ng-content select="footer"></ng-content>
</div>

Как вы заранее узнаете, есть ли что-то в ng-content или нет?

Расскажите лучше, бывали ли у вас реальные случаи замедления из-за чрезмерной оптимизации на спичках?

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

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

В этом вопросе я скорее согласен с этими ребятами Why We Memo All the Things.

Наркомания какая-то, если честно. Особенно часть про children, где они предлагают делать так

const Component = React.memo(() => {
   const children = useMemo(() => <div>content</div>, []);
   return <Container>{children}</Container>
});

Вместо того чтобы

function Component() {
   return <Container>
      <div>content</div>
   </Container>
}

Для пятничного поста в "ненормальное программирование" еще бы зашло, но писать такое регулярно - увольте.

Во втором случае memo на Container, очевидно, сломается. О чём они, собственно, и толкуют.

Речь о том, что как в первом случае никто в здравом уме не пишет. Вместо идиоматичного React сгруппированного в одном месте JSX мы получаем нечто странное. За этим лесом из memo становится не видно деревьев (собственно UI кода).

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

Так оно сломаное. Пропусти мемоизацию в одном месте и оно водопадом обрушится до самого конца.
Я прекрасно знаю идеологию реакт: «забей на оптимизацию сейчас, оптимизируй узкое место потом».
Проблема в том, что она не работает. Нет никакого узкого места. Каждый компонент в котором забили на оптимизацию — «чуть-чуть» тормозит. Потом они складываются в кучку и получается Patreon.
Тормоза размазаны по всем компонентам и починить можно, только переписав каждый. Ну или переписав «достаточно», чтоб тормозить стало незаметно. До следующего раза.
Достаточно оптимизировать пару узких мест

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


Т.е. не получится просто пройтись и понавтыкать везде всяких useCallback, useMemo, если это не было продумано с самого начала. Во всяком случае мой опыт такой.


Ну и мемоизация по-умолчанию это не только про производительность. Это про простоту отладки и про простоту реализации сильно перемудрёных компонент.

Согласен, про "пару узких мест" я преувеличил. В реальности это сложнее.

Это про простоту отладки и про простоту реализации сильно перемудрёных компонент.

Интересно, что может быть проще обычных функций, и как memo-обертки упрощают это еще сильнее?

и как memo-обертки упрощают это еще сильнее?

  • Ну скажем dependencies в useEffect. Опираясь на иммутабельность вы можете строить сложные деревья из эффектов.
  • Или скажем взять useState(), он возвращает [st, setSt], где setSt мемоизирован. Как этим можно было бы пользоваться не будь он мемоизированным, а был бы уникален для каждого рендера?

По сути React сам нас мотивирует класть в dependencies ВСЁ, что внутри задействовано. Есть специальные eslint правила. Рекомендуемые к использованию. И там даже есть правило для useEffect (чем несказанно меня удивляет).


Честно говоря я с трудом представляю как можно без тотальной мемоизации написать на хуках приложение. Оно же на изоленте висит. Ладно бы вместо хуков взяли MobX, там свои решения. Но ведь хуки к этому прямо провоцируют.

Если нам нужно вызвать callback из useEffect, то мы заворачиваем его в наш кастомный хук

const memoizedOnChange = useStableEventHandler(onChange);
useEffect(() => {
   if(someCondition) {
      memoizedOnChange();
   }
}, [memoizedOnChange])

Вот тут реализация useStableEventHandler. Основное преимущество – onChange, и всю цепочку зависимостей наверх мемоизировать не надо, мы получим актуальный инстанс в любом случае. Мемоизация скрывается внутри компонента и у его пользователей голова ни о чём не болит

У нас есть похожее решение, только мы пошли дальше.


const storage = useStorage({ a, b, c, d, e }, { f, g });
useEffect(() => {
  storage.*
}, [/* nothing */]);

Где a-e это изменяемые параметры, а f-g это неизменяемые поля для инициализации. Внутри useRef и useLayoutEffect для обновления.


Используем его в ряде случаев, когда это позволяет сильно упростить сложные вещи. Тут главное не заиграться, и помнить что такие финты можно делать только для поведения. Если сам render от этого зависит, то никаких useRef.

Рекомендую поменять вам useEffect на useLayoutEffect во избежание неприятных багов, когда что-то запускается раньше, чем синхронизация состояния произошла. А ещё стоит убрать , [ref] (он там не нужен, он статичен).


А ещё можно убрать зависимость в useEffect. .current = value невесомая операция :) Лишние проверки ни к чему

Да и useCallback выкинуть - в ref положить массив\объект и функцию-обёртку сразу же первым ключом.)

Ну и отдельно про отладку — дебажить код стократно проще когда вместо 150 рендеров глючного компонента (или связки компонент) будет 1. Когда если рендер был, то он был по какой-то объективной причине. И если он очевидно лишний, то выяснив причину лишнего рендера в ней же мы и находим баг. Примерно так обычно было на моём опыте.

В этом вопросе я скорее согласен с этими ребятами [Why We Memo All the Things](Why We Memo All the Things).

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


<button onClick={...}/>

Вот этот onClick не нужно useCallback-ить, т.к. кодовая база усложнится сильно, а оптимизации никакой не будет. Время на removeEventListener и addEventListener потребуется крошечное. Можно и нужно смело это игнорировать.


Ну и как указал justboris, буквально всегда когда есть children или какой другой слот, никто в здравом уме не будет мемоизировать этот prop.


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

Дык это как раз суть их подхода: мемоизируем сейчас, чтоб не выстрелить себе в ногу потом.

Да, в button onClick мемоизация не нужна, но вот при следующей итерации на месте button окажется CoolButton, где она нужна, но её, по недогляду, не будет.

Не согласен. Во-первых для чего нужен code review? Во-вторых даже если не будет, ничего страшного. Прямо вот совсем. Мы даже в случае CoolButton говорим уже о листьях vDom древа. Т.е. там не окажется случайно Excel таблички, адронного коллайдера или ещё чего.


А вот обратная сторона медали заключается в том, что мемоизация бьёт по стоимости кода (её сложнее читать, её сложнее писать, её сложнее рефакторить). И это, имхо, сильно перевешивает гипотетические "потом забудем добавить потеряем 1.5 наносекунды".

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

В целом, я считаю что фреймворк/библиотека должен думать за меня в 99% случаев, оставляя на меня edge-case вещи, действительно требующие осознанного решения. Идиоматичный же реакт заставляет меня либо думать в этих самых 99% случаев, либо забить(тем или иным образом), но чувствовать себя говнокодером(пусть даже и неоправданно).

В целом, я считаю что фреймворк/библиотека должен думать за меня в 99% случаев

Если это не божественный $mol, то звучит утопично. Я пока за что не хватался, везде приходилось включать думалку, иначе были тяжёлые последствия. Возможно React в этом деле особенно преуспел, не знаю.


Вот любят хвалить Svelte. А потом смотришь код и видишь какие-то ручные переприсваивания. Они называют это костылём оптимизацией. Т.е. нужно прямо вникать.


Или вот Vue. Написал вроде решение. А потом видишь что оно ну совсем не масштабируется. Оказывается Vue не умеет в зависимости computed от computed за O(1).


Или вот Knockout. Взял навертел разных видов observable и потом дичайше страдаешь от очень хитрых race condition.


C RxJS прямо сразу падаешь в бездну сложности :)


Ну и так, имхо, куда не плюнь. Не думаю, что есть решения где просто сел и пишешь. Только $mol

"computed от computed за O(1)"

0_o, а это нужно?

Имхо, Vue 2 самое близкое пока, что видел к этому идеалу. (В Vue 3 просрали все полимеры.)

0_o, а это нужно?

В моём случае это было нужно. У меня было много изменяемых observable (5000+) на основе которых считалось очень много всяких вычислений. А эти вычисления выводились в редактируемую таблицу. Каждая ячейка таблицы (скажем 7 * 14) состояла из нескольких компонет, и её содержимое тоже могло содержать список кнопок (скажем до 20, в среднем 1-3). Каждая из этих компонент нуждалась в доступе к вычисленным данным.


Каждому такому компоненту (которых как я отметил МНОГО) нужно что-то показать. Оно лежит в computed-древе, в каком-нибудь computed-leaf-е. Причём доступ нужен сразу к нескольким полям. Все эти data-bind-ы автоматически тоже сами становятся computed-ми (так работает data-binding во vue).


А теперь фокус мокус. Любой computed основанный на этих 5000 observable (т.е. всё древо и все data-bind-ы всех компонент и многое другое) при попытке обратиться к режиме поиска зависимостей (так работает computed) будет это делать за O(n), где n это 5000.


Т.е. оно физически 99.99% времени перекладывает зависимости из одного Set-а в другой Set. Полезной работой почти не занимается. Приложение просто нежизнеспособное.


Решения которые мне пришли в голову все были очень костыльными. Например притащить туда rxJs. Написать свою версию computed в обход Vue. Переписать весь движок вычислений на watchers. Ну и т.д. Всякая жуть.


Но так как это был сжатый в сроках эксперимент я просто выкинул Vue, взял React и за 2-3 дня перевёл всю кодовую базу на JSX. Все вычисления практически не изменились (поменял computed из Vuex на createSelector). Их архитектура вообще не изменилась. В общем переделка свелась к побольшей части к замене Vue шаблонов на JSX.


Итог? Ну оно залетало так что эти вычисления в режиме drag-n-drop можно было делать.


React рулит? Нет. Просто везде есть свои нюансы. Как я выше и написал, нужно прямо вникать в используемую технологию.


Vue мне понравился удобным шаблонизатором. И не понравился всем остальным (особенно этим "3в1"). Vue3 с их хуками выглядит уже аппетитнее. Но честно говоря пока не пробовал. Просто убедился что во Vue3 вышеописанная проблема всё ещё актуальна.

В целом да, согласен, конечно. Но это, вполне очевидно, как раз тот самый 1% edge-case, когда надо подумать. Вполне интересная задачка, понятное узкое место, а не скучная рутина.)

в rxjs это поначалу сложно, а потом просто пишешь и не задумываешься. Хотя мб это и везде так :)

в rxjs это поначалу сложно, а потом просто пишешь и не задумываешься.

Допустим с горем пополам ты напишешь, при помощи этой дичи, да. Мало того что на это без слез не взглянешь, так ещё потом если ты к этому коду вернешься, а уж тем более если кто-то другой вернётся, то пиши пропало.
Чисто write-only и чудовищный bus фактор со старта проекта. Нет уж спасибо)
Пожалуй не зря разумные люди выбирают писать простой и понятный код для любого человека и в любое время к нему вернись, все будет понятно. Делают хорошо и себе и окружающим, бонусом не приговаривают бизнес к скорому переписыванию проекта с нуля)

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

Я еще не видел людей, которые после рх хотели бы вернуться к промисам по доброй воле.

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

О, голубчик, попались, вы-то мне и нужны. Уже третий год не найду ответа на простой вопрос по rxjs. Какой из 10 операторов порекомендуете?

я вам ответил

Посмотрел ваш вариант (https://stackblitz.com/edit/rxjs-wxfjxe?file=index.ts), жесть конечно, вырви глаз и проверки работоспособности вашего «решения» тоже нет.

Вот как как выглядит по настоящему работающее решение здорового человека, с проверками работоспособности и удовлетворяющие все требования задачи — stackblitz.com/edit/typescript-3huxim?file=index.ts

nin-jin

mayorovp
Кстати, забавно, что указанную задачу (эффективную реактивную фильтрацию массива) вообще ни одна библиотека не решает.

^

Так вы совсем другое сделали, ваше "решение" это фильтр по одному параметру, который перед запуском проверяет изменения этого самого параметра во всех итемах.

На самом деле тут инструмент вообще не важен, завтра нарисую чуть по другому, там присутствие rx вообще минимально.

Так вы совсем другое сделали

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

С чего это по одному то? сколько угодно параметров засуньте в функцию фильтра и все будет работать.

Что-то вы переусложнили всё, зачем там вообще autorun внутри reaction, если ваше решение сводится к одному computed?


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

Что-то вы переусложнили всё, зачем там вообще autorun внутри reaction, если ваше решение сводится к одному computed?

Это понятно, чтобы сам алгоритм было видно. А так это в computed помещается и дело с концом — stackblitz.com/edit/typescript-y4hjir?file=index.ts

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

Нет, не при любом изменении, любого элемента в массиве, а только если изменяются конкретно те параметры, которые участвуют в фильтре. Так всё же видно и проверки есть с console.log'ами.
Нет, не при любом изменении, любого элемента в массиве, а только если изменяются конкретно те параметры, которые участвуют в фильтре.

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


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

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

Если вы так настаиваете)) То вот решение — stackblitz.com/edit/typescript-zhgtnu?file=index.ts
теперь все кэшируется и только выполняется пересчет функции только для элемента который изменился.

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


Также надо более активно очищать trackedItems, либо переключиться на WeakMap с FinalizationRegistry.


И да, не забыть использовать функцию filterWrapper, а то вы её написали но не вызвали.


А теперь смотрим на весь этот код и понимаем, что он не является частью библиотеки.

Только надо перенести действия из первого аргумента reaction во второй

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

И да, не забыть использовать функцию filterWrapper, а то вы её написали но не вызвали.

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

А теперь смотрим на весь этот код и понимаем, что он не является частью библиотеки.

Он и не должен быть частью библиотеки.

придумать способ пересчитывать filteredItems после срабатывания внутренней реакции

Это было реализовано в других примерах) Да и смысл если эти данные нужны только по требованию.
Не надо, первый аргумент все равно каждый раз вызывается при изменении в нем данных.

Почему в таком случае вы не autorun использовали?


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

А, вы снаружи эту штуку вызвали. Зря, хотя для примера на коленке сойдёт.


Это было реализовано в других примерах) Да и смысл если эти данные нужны только по требованию.

Э-э-э, нет, это как раз принципиально:


console.log('\r\n----use filter [filterName]----\r\n\r\n');
itemsFilter.filterFn = filterName;

autorun(() => {
  console.log('filteredItems', itemsFilter.filteredItems.length);
}) // 6 элементов

itemsFilter.items[0].name = 'one'; // всё ещё 6 элементов
itemsFilter.items[1].name = 'one'; // упс, а реакции-то нет
itemsFilter.items[1].name = 'two'; // упс, а реакции-то нет
Почему в таком случае вы не autorun использовали?

Изначально писал с reaction, в итоге просто поленился вместо него в итоге autorun воткнуть, но сути это не меняет на работу задачи не влияет.

Э-э-э, нет, это как раз принципиально:

Вы ещё раз проверьте все, там все работает правильно и ровно так, как нужно да и реакции есть разумеется, не знаю почему вы думаете что их нет. Консоль логи для этого там пишутся. И для вас там специально написаны комментарии когда данные реально изменяются и когда нет, так вот когда они реально не изменяются никаких реакций нет и быть не должно.
Как бы вот:
image

Ну да, вы же получаете filteredItems явно, вот оно и "работает". А вы попробуйте выводить filteredItems внутри autorun.


Я же вам привёл тот код, который вы сломали своей реализацией. Вот вам ссылка: https://stackblitz.com/edit/typescript-vni7na?file=index.ts

Ну да, вы же получаете filteredItems явно, вот оно и «работает». А вы попробуйте выводить filteredItems внутри autorun.

Я же вам привёл тот код, который вы сломали своей реализацией.

Ну вот с явными обновлениями и с работающим autorun
stackblitz.com/edit/typescript-wgar48?file=index.ts

Мораль такая, можно разводить демогогию вокруг этой задачи долго, но факт таков — нечего сверх естественного в ней нет и если и правда есть нужда именно в таком поведении это можно реализовать, если не лень. Мне уже лень тут честно экономить на спичках)

Вы заметили, что у вас до строчки console.log(1); массив фильтруется два раза? А знаете почему?


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


Прекратите писать подобное! На этот код больно смотреть.


Нормальный код должен выглядеть вот так:


function filterWrapper(fn: FilterFn<Item>) {
  const trackedItems = new WeakMap<Item, IComputedValue<boolean>>();

  return (item: Item) => {
    let x = trackedItems.get(item);
    if (!x) trackedItems.set(item, x = computed(() => fn(item)));
    return x.get();
  };
}

Зачем тут вообще реакции?

Прекратите писать подобное! На этот код больно смотреть.

Без этого вы не додумались ни до чего сами.

Нормальный код должен выглядеть вот так:

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

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

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

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

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


Вы доказывали, что в mobx задача решается просто, потому и код писать вам.


Кстати, результат всё ещё не оптимален на больших массивах.

Кстати, результат всё ещё не оптимален на больших массивах.

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

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


Кстати, забавно, что указанную задачу (эффективную реактивную фильтрацию массива) вообще ни одна библиотека не решает.

Любой процесс - это последовательное изменение состояний. Вся разница между push (rx, bacon etc) и pull ($mol_mem, mobx etc) подходами организации реактивных потоков данных в том, что первые предполагают статическую конфигурацию потоков, что в реальной жизни не встречается, а даже если и попытаться описать реальное приложение статическими потоками, результат получается крайне не эффективным. Вторые же динамически перестраивают потоки данных, в соответствии с текущим состоянием приложения.

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

В реальной жизни потоки как раз статичны. Задачи типа динамических фильтров это редкость, к ним проще точечно подходить.

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

Рассмотрим такую задачу:


  1. из состояния есть фильтр и номер страницы;
  2. при каждом изменении фильтра номер страницы должен сбрасываться в 1;
  3. при каждом изменении фильтра и номера страницы надо делать два http-запроса для получения отображаемых данных (второй запрос использует данные первого);
  4. слишком часто запросы делать нельзя, также надо отменять прошлые запросы если фильтр или номер страницы изменился.

Где здесь вообще требуется динамическое определение конфигурации потоков? И какими костылями вы будете делать это в pull-подходе?


В rx, к слову, это делается как-то вот так:


filter.pipe(
  switchMap(() => {
    page.next(0);
    return page;
  }),
  debounce(50),
  switchMap(() => request1({ ...filter.value, page: page.value })),
  switchMap(data => request2(data))
)
Рассмотрим такую задачу:

из состояния есть фильтр и номер страницы;
при каждом изменении фильтра номер страницы должен сбрасываться в 1;
при каждом изменении фильтра и номера страницы надо делать два http-запроса для получения отображаемых данных (второй запрос использует данные первого);
слишком часто запросы делать нельзя, также надо отменять прошлые запросы если фильтр или номер страницы изменился.

Вы реализуйте по настоящему эту задачу на rx и скиньте ссылку на код в сэндбоксе, а вам реализуют эту задачу с MobX'ом или любитель $mol'a с $mol'ом.

На mobx я и сам это реализовать могу, только вот в итоге как раз жесть с промисами и получится:


class Foo {
  @observable.ref filter = ...;
  @observable page = 0;
  @observable result = null;

  _reaction = null;
  _abort = null;
  _timeout = null;

  @action
  setFilter(value) {
    this.filter = value;
    this.page = 1;
  }

  constructor() {
    makeObservable(this);

    this._reaction = reaction(() => ({ ...this.filter, page: this.page }, async params => {
      clearTimeout(this._timeout);
      await { then: fn => this._timeout = setTimeout(fn, 50); }

      this._abort?.abort();
      this._abort = new AbortController();
      var data = await request1(params, _abort);
      this.result = await request2(params, _abort);
    });
  }

  dispose() { this._reaction(); }
}

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


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

только вот в итоге как раз жесть с промисами и получится

Я не вижу никакой жести в вашем прототипе, все читается сверху вниз и что делает предельно понятно и наглядно. Можно всё это оформить в классах вспомогательных и скрыть в них детали реализации.

Ну вот rx из таких вспомогательных классов и состоит.

Ну вот rx из таких вспомогательных классов и состоит.

Не надо, вырви глаз лапша не котируется и даже близко не стояла. Мы ведь говорим о человеческом коде(я надеюсь), а не write only одноразовых поделках.

ага, не вижу никакой жести, одна нечитабельная мешанина

ага, не вижу никакой жести, одна нечитабельная мешанина

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

неа.

Вообще непонятен, потому что цепи событий у вас как раз нет. mobx и не работает с ними, он менеджит данные. У него другая абсолютно задача.


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


Да, в реакте mobx действительно эффективно помогает, до поры до времени, просто потому что альтернативы тоже все примерно такие же.

Вы пытаетесь выдумать задачу, в которой RX не так плох, но даже это у вас не получается, ибо:

  • Нет индикатора ожидания, а его добавление в интерактивной форме порождает глитчи и зависания.

  • Нет защиты от смены фильтра (как и любого промежуточного значения) на такой же, из-за чего после каждого оператора надо вставлять distinctUntilChanged.

  • Нет шаринга между разными потребителями - типичная бага по лени/незнанию/недосмотру. Очень весело такое дебажить.

  • Нет обработки исключительных ситуаций, в случае которых все эти "статические" стримы надо создавать заново.

А вот тут всё это есть:

@ $mol_mem filter( next = {} ) {
    return next
}

@ $mol_mem page( next = 0 ) {
    this.filter()
    return next
}

@ $mol_mem ids() {
    this.$.$mol_wait_timeout( 50 )
    return this.$.$my_api.get( 'search', {
        filter: this.filter(),
        page: this.page(),
    } )
}

@ $mol_mem tasks() {
    return this.$.$my_api.get( 'tasks', {
        ids: this.ids(),
    } )
}
Нет индикатора ожидания, а его добавление в интерактивной форме порождает глитчи и зависания.

Это довольно простая задача, решение которой не отличается в rx и mobx. А вот о глитчах и зависаниях, пожалуйста, подробнее.


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

Совершенно необязательно это и правда требуется.


Более того, сравнение объектов по ссылке — мартышкин труд, а глубокое сравнение объектов дорого. Поэтому эффективнее всего недопускать лишних обновлений вместо того чтобы с ними потом героически бороться.


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

Не в любой точке требуется шарить процесс. А если требуется — это всего пара строчек.


Нет обработки исключительных ситуаций, в случае которых все эти "статические" стримы надо создавать заново.

Согласен, но для этого тоже есть операторы.




А теперь по поводу вашего решения. Оно просто не работает (по крайней мере, как вы расписываете).


Рассмотрим следующий сценарий:


  1. пользователь изменил фильтр, ids()=[1,2,3], вызван request2 и вернул исключение (насколько я помню, асинхронность работает у вас именно так);


  2. запрос завершился, data пересчитывается и request2 вернул данные — тут важно, что повторный вызов request2 с теми же самыми параметрами не делает повторного запроса;


  3. пользователь изменил фильтр, но ids() снова [1,2,3] — data, либо вообще не пересчитывается, либо request2 должен вернуть те же самые данные (иначе пункт 2 не сможет работать).



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

Это довольно простая задача, решение которой не отличается в rx и mobx.

А в $mol эту задачу вообще не надо безконца решать.

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

Нарушение инвариантов при конкурентном исполнение задач. Выглядит как появление и пропадание индикатора в произвольные моменты времени. Частая такая смена состояния выглядит как мерцание.

Совершенно необязательно это и правда требуется.

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

Более того, сравнение объектов по ссылке — мартышкин труд, а глубокое сравнение объектов дорого. Поэтому эффективнее всего недопускать лишних обновлений вместо того чтобы с ними потом героически бороться.

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

Не в любой точке требуется шарить процесс. А если требуется — это всего пара строчек.

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

Согласен, но для этого тоже есть операторы.

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

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

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

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

Например, если подписчик заведомо один.


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

Надо уметь работать с любыми api, а не только с "нормальными". Кстати, не помню когда я "нормальное" api последний раз видел.


Но если вам не повезло с сервером — просто убираете мемоизацию первого запроса

О, так у вас можно убрать мемоизацию?


Кстати, а как без мемоизации будет ваш псевдо-поток на исключениях работать?

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

Нормально всё будет работать.

Не могли бы вы объяснить понятнее какой такой магией request2 должен отличить первый вызов от второго?


А то по вашей ссылке какой-то детский рассказ вместо документации.

Поддержу первое, что CSS :empty тут скорее вредит, т.к. размазывает логику между JS и CSS, и в целом это неожиданное поведение, что не только JS управляет тут видимостью. Нарушает one way flow. ИМХО, правильнее было бы все-таки вытащить внутренний флаг видимости из Footer.

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

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

Я обычно сразу пишу чтобы свести пересчеты к минимуму.

Спасибо, Борис.
Я прям чувсвую вашу боль. Сейчас целая проблема найти хороших разработчиков. К сожалению подавляющее большинство на рынке - не разработчики, а то, что я называю advanced user (продвинутые юзеры). Они лишь "умеют" пользоваться библиотеками и фреймворками, при полном отсутствии фундаментальных знаний.

Вот один из ярких примеров.


Там большая часть субтредов прекрасна, где "сеньеры" и "бест практис адвокаты" на серьезных щщах, рассказывают, что они не пользуются "каскадом" и, чтоб не было проблем нужно пользоваться "стандартрными" решениями. (хотя что может быть более стандартным, чем чистый CSS не понятно, но из контекста имеются ввиду styled-components, css библиотеки (tailwind, bootstrap). Некоторые даже обижались :-D

Eсли человек дорос до сеньера и ему ни разу не пригодилось это знание (и он ни разу не наткнулся на него на стековерфлоу), то это знание не нужно, не очевидно и переоценено.

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

Например, проблема == в js просто была решена ===, и варнингом на любое использование ==.

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

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

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

К сожалению ситуация становтится все хуже и хуже. И обилие курсов "стань программистом за 24 часа" лишь усугубляют проблему. А сейчас еще Git Copilot выстрелит, вот мы насмотримся говнокода.

Программистов всего 4% больше не станет, правило 80/20. Из 20% населения земли только 20% могут реально программировать, а остальные обезьянничать.

Почему контейнер содержит внутри себя футер, по сути нарушая принцип единственной ответственности?

Поддерживаю. Почему-то никто тут не начал с логики. А вместо этого обсуждают как лучше в бизнес слое высоту футера считать.

SRP можно очень широко трактовать. Если в дальнейшем предполагается, что правки будут одновременно вноситься и в компонент и в футер, то все сделано правильно.

Пришёл новый человек в проект, стоит задача поправить футер, вы сразу поймёте что надо в контейнер лезть? Как не трактуйте, зачем 2 разные вещи клеить в 1, также можно и всё в 1 компоненте сделать не разбивая на части ?

А где тут, собственно, нарушение SRP-то?


Я вижу у контейнера единственную ответственность — отобразить этот самый контейнер, с переданным содержимым и переданным футером.

Почему не передать футер внутрь так например:

<Container> ... <Footer/></Container> ?

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

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

И в чём проблема, это сделать так как я показал? У меня Footer это отдельный компонент который может всё вышеперечисленное содержать

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


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


Вариантов несколько:
<Container>Content<Container>
<Footer>Content</Footer>
второй
<Container>Content<Container>
<Footer>
<Container>Content<Container>
</Footer>
и т.д.

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

Мне кажется вся эта ветка спор ни о том. Переименуйте этот несчастный <Container/> в <Layout/> у которого будут content: React.FC, footer: React.FC, header: React.FC. Теперь вроде всё "легально", но проблема остаётся.


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


И вот как-то простых решений кроме "заставить кого-нибудь знать больше, чем хотелось бы" или "поменять к чёрту ТЗ" нет :)

Теперь добавим скруглённые углы, и вспомним про опциональность футера:



У вас без дополнительных хаков выйдет как слева, а надо — как справа.


Опять всё сломалось...

Вариантов также нормально это решить уйма. Да хоть через тот же css. Этот спор может затянуться надолго, давайте остановимся, пусть каждый останется при своём

Ну нет, это архитектурная проблема, она не решается через CSS.

Ага, или у контейнера какой-то общий стиль с футером. Например растягивание на всю ширину экрану с соответствующим бэкграундом. А непосредственно сам футер и контентная часть имеют фиксированную ширину и центруются (обычно очень распространённое явление). В таком случае и футер, и контент надо оборачивать в какой-то блок. Надо смотреть конкретный дизайн. В общем же случае - лучше всё-таки иметь дополнительную "обёртку", чем не иметь. Так что претензия высосана из пальца.

История #1: за чистый CSS


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

Другой разработчик пришел на code review и заметил, что этот код работает только при первом рендере

useEffect(() => {
    const hasContent = footerRef.current.childNodes.length > 0;
    footerRef.current.style.display = hasContent ? 'block' : 'none';
  });

а может этот код будет работать каждый кадр?

История #2: как загружать скрипты

да как угодно, только то что указано в примере - явно указание на то, что ваш разработчик обладает крайне низкой компетенцией

надеются на помощь html-webpack-plugin и т.п.

потому что html-webpack-plugin имеет inject: 'body' опцию и сам вставит все скрипты в конец.

Если же так хочется контролировать инжекты, то вот, в порядке бреда:

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title><%= htmlWebpackPlugin.options.title %></title>
  <% for (var css in htmlWebpackPlugin.files.css) { %>
    <link rel="preload" as="style" type="text/css" crossorigin="anonymous" href="<%= htmlWebpackPlugin.files.css[css] %>" onload="this.rel='stylesheet'"/>
  <% } %>
</head>
<body>
<!-- какой-то код -->
<% for (var js in htmlWebpackPlugin.files.js) { %>
  <script async src="<%= htmlWebpackPlugin.files.js[js] %>"></script>
<% } %>
</body>

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

История #3: корень всех зол

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

Вы берете оптимизацию в разрезе отдельно, а смотреть надо в целом. Если у вас рендер меньше 16 мсек на страницу, то и бог с ней с мемоизацией. А если у вас 100+ объектов (а вы мап в мапе делаете), да и рендер уже выходит, допустим в 75 мсек? Не надо отвечать, это очередной холиварный вопрос, тут вопрос целесообразности, как я и говорил чуть выше

а может этот код будет работать каждый кадр?

Не совсем. Изменения <Footer/> снизу оно не отловит. Вроде суть проблемы именно в этом.

Кажется дело в том, что сейчас HTML/CSS/JS это в первую очередь compile target, а не основные языки разработки (не для всех, но все же). И этот трюк с :empty воспринимается примерно как "можно же в бинарнике две инструкции поправить и не городить огород в исходниках"

История #1: ":empty", это не "основы", а одна из 100500 фичей CSS. Просто есть два человека, первый придумал ":empty", и был ближе к комитету по стандартам, поэтому фичу затащили в браузер, а другой придумал хуки, или еще что-то, но был в другой тусовке. Основы -- программирование (функции в реактивность в данном случае). И поведение разработчиков смотрится вполне нормально. А то, что мимо шел человек-энциклопедия по CSS -- приятный бонус.

История #2: Ввести правила: Пихать "script" в конец, а не куда попало. Запускать действия по событиям (не запускать вне функций). Низкоуровневые подробности гуглить, если припрет.

Каким-то образом скрипт оказался на странице раньше футера

Правило хорошего тона не использовать блокирующие скрипты, и цепляться к таким вещам как onDOMContentLoaded. А не двигать строки в HTML.


В обоих случаях это порядка одной милисекунды

В реальном календаре там может быть не <span>{day.getDate()}</span>, а куда более сложная конструкция (зависит от дизайна и требований). И сам getCalendarMonth может возвращать более сложную структуру. В итоге если есть компоненты <Week/> и\или <Day/>, которые обёрнуты React.memo(), то речь будет уже идти не об одной милисекунде. К тому же разбор полётов (какой-нибудь хитрый баг) будет проще, если вы не пересоздаёте vDom всего календаря с нуля на каждый чих.


В целом про getCalendarMonth. Я помню как на IE6 подобная штука приводила к 40+ секундным тормозам. В условном "getCalendarMonth" какой-то jQuery библиотеки был Object.assign, который был покрыт полифилом, которые нещадно тормозил. Я конечно в итоге нашёл все "затыки" и всё залетало, но было неприятно обнаружить что никто не сделал нескольких элементарных оптимизаций.


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

Дык. Я думаю все догадались что вы ничего хорошего про это не думаете, но что вы сделали? :) Оборвали рассказ на самом интересном месте. На мой взгляд тут надо лечить причину, а не следствие. Ломать ту логику которая позволяет пробросить завуалированный null в слот.


отому что объект date может быть другим инстансом, содержащим тоже время, а useMemo сравнивает объекты в лоб.

Если это редкий случай, то и плевать. В общем тут нужно видеть код.

Достаточно было просто воспользоваться CSS-селектором :empty

Что-то я проморгал эту фразу (под спойлером была). Не работает :empty в хитрых случаях, насколько я помню. Поэтому пришлось выкинуть это решение в реальном похожем кейсе. Кажется дело было в чём-то вроде display: none или типа того. Но судя по комментариям то же самое и с пробелами.

Про empty:

Такой подход прекрасно попадает под pareto principle – работает для большинства случаев с минимальным оверхедом. Там где не проткатит – так уж и быть, придется расчехлять свои компоненты и передавать явный null вместо неявного.

Про мемоизацию:

В общем тут нужно видеть код.

В общем-то в демке я почти весь код и показал, кроме разве что обработчиков событий и стилей.

Понимаю, что при усложнении кода тут может оказаться проблема (а может и нет, кто же знает). Но фиксить сейчас не существующую проблему – это классическая преждевременная оптимизация

Не в обиду автору будет сказано, но статья конечно из разряда «Какой я молодец, как элегантно поправил глупые ошибки» ) 

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

Я наверное скоро трёхтомник «абсурдных кусков кода» кода смогу издать. И приведённые тут примеры, далеко не верх безрассудства. Бывает на code review и смеяться хочется и плакать.

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

Да и вообще, бывает даже самые матёрые ребята пишут глупости. Я убеждён, что жёсткая архитектура, и грамотное выделение шаблонных моментов - залог здорового кода)

  1. Вы считаете нормальным решением отрендерить лишний дом элемент, чтобы его не показывать?

  2. Вы считаете нормальным потратить в каждом компоненте 1/16 от фрейма анимации впустую?

Думаете это нормально заливать 92 бензин?

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

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

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


В общем ничего нового. Каждой задаче - свое решение. Что не означает, что не нужно работать над своей квалификацией. 

Умеете писать код - это хорошо.

Умеете писать качественный код - просто отлично.

Умеете писать код, цена/качество которого соответствует потребностям - Вы великолепны


Чисто там, где не сорят, а не там, где своё свинство оправдывают отсутствием времени

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

"Просто сделать - сложно, а сложно просто"

Тешить свое самолюбие за деньги заказчика, разве не свинство?

"Просто сделать - сложно, а сложно просто"

Это особенность практически исключительно только Реакта.

Управление сложностью — самый важный технический аспект разработки ПО. По-моему, управление сложностью настолько важно, что оно должно быть Главным Техническим Императивом Разработки” (с) Стив Макконнелл.

Но дело все, конечно, в реакте)

В Реакте как раз управление сложностью и запороли. Но на приветмирах это не заметно.

Вы считаете нормальным решением отрендерить лишний дом элемент, чтобы его не показывать?

Нет, золотая рыбка, $mol я использовать не буду

Вы считаете нормальным потратить в каждом компоненте 1/16 от фрейма анимации впустую?

А к какому случаю из статьи это относится?

Раз уж вы вспомнили про $mol, то на нём убирание футера из рендеринга делается просто:

Foot() {
  return this.foot().length ? super.Foot() : null
}

Это самое простое и очевидное решение. При этом всё будет корректно мемоизировано и реактивно обновляться. Но это же скучно. Куда интересней создавать себе и другим проблемы на ровном месте и писать статьи про 100500 хаков от useEffect до css:empty.

1/16 фрейма - это как раз 1 миллисекунда. 16 таких компонент, "которым оптимизация не нужна" и вы гарантированно получаете просадку fps.

1/16 фрейма - это как раз 1 миллисекунда. 16 таких компонент, "которым оптимизация не нужна" и вы гарантированно получаете просадку fps.

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

Цифры одинаково отвратительные, так как мемоизация фактически не работает. Нормальная мемоизация выглядит как-то так:

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

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

Ну давайте, расскажите , кто должен был научить этих «неучей»? В каких университетах учат css, сколько по времени и где учат js ? На курсах? Дак курсы же зашквар, не? А может вы забыли , как сами изучали это все и прыгали на граблях, и так же вас хейтили знатоков джеквери? Рынок вырос, объём знаний вырос . С какой переодичностью выкатывает хром обновления ? Как часто спеки меняются ? Как быстро развивают фреймворки? Сейчас скажут фреймворки не тру, нужно учить ванилу и чистый CSS , а где ее юзать то, эту ванилу и чистый CSS? Сейчас куда не плюнь, фреймворки , препроцессоры, CsS in JS и тд. Этот надменный тон «илиты» раздражает , вы сами нанимали себе работников , вы их собесили. Если вас бесит, что бизнес готов платить людям с неидеальными знаниями то это дело бизнеса, а не ваши. Идите работать в FAANG, там ваши знания и умения оценят по достоинству, если возьмут .

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

Но некоторые адепты молниеносной разработки незамедлительно кинут неспоримый аргумент, что бизнес не любит ждать. Что скажешь им на это, Борис?

Похоже, статья должна называться "Кажется, вы стали забывать основы фронтенда", т.к. "мы" - это "вы плюс я".

На мой взгляд посыл топика так или иначе сводится к пресловутой бритве Оккама: "лишние сущности", KISS, YAGNI и прочее -- всё это сформулировано сотни лет назад :) Поэтому я выражаю несогласие с заголовком "Кажется, мы стали забывать основы фронтенда".

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

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

Поэтому назвать статью можно было бы в духе того, что "Кажется, нам всё сложнее оптимально разрабатывать фронтенд".

И позволю себе небольшое замечание по технической части. На мой взгляд изменение порядка тегов <script> и <footer> -- весьма хрупкое решение. Порядок строк всегда может случайно нарушиться. В коде нет очевидной причины такой конфигурации, как если бы это была, к примеру, цепочка функций вида y = f(x); z = g(y). А комментарии, как известно, штука о двух концах. Но я ни разу не front-end'ер. Поэтому, к сожалению, не могу предложить своего решения :(

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории