Обычно, в подобных статьях я выступаю на позитивной стороне и говорю, что всё не так уж плохо, что умелыми руками можно делать прекрасные вещи. Однако, недавно со мной произошло несколько историй, которые заставили меня пересмотреть свой взгляд на ситуацию в индустрии.
В этой статье я перескажу истории с некоторыми техническими деталями и порассуждаю, что делать дальше.
История #1: за чистый CSS
Есть у нас компонент Container, у которого может быть опциональный футер. Когда он есть – то мы рендерим дополнительную обертку для него, с отступами и обводками.
function Container({ footer, children }) {
return <div>
{children}
{footer && <div className="footer">{footer}</div>}
</div>
}
В простых вариантах все работает гладко, вот тут <Container footer={null} />
футера не будет, а здесь <Container footer="View more" />
– будет. Проблемы начинаются, когда контент футера динамический: <Container footer={<Footer />}>
. Компонент Footer может вернуть контент, а может и null
, но наше условие {footer && <div />}
об этом не знает, и может иногда рендерить пустой div.
Один разработчик попытался поправить эту ситуацию. Он подумал – а что, если мы проверим содержимое div и спрячем его?
function Container({ footer, children }) {
const footerRef = useRef();
useEffect(() => {
const hasContent = footerRef.current.childNodes.length > 0;
footerRef.current.style.display = hasContent ? 'block' : 'none';
});
return <div>
{children}
{footer && <div ref={footerRef} className="footer">{footer}</div>}
</div>
}
Другой разработчик пришел на code review и заметил, что этот код работает только при первом рендере. Если футер обновляется асинхронно, то useEffect не вызовется, и обновления не произойдет. Разработчики посовещались и решили копать в сторону MutationObserver.
В процессе обсуждений они так же решили уточнить у меня, что я думаю про этот подход. А мне вспомнился анекдот про NASA, которые потратили миллионы долларов на разработку шариковой ручки для условий невесомости, а советские космонавты просто взяли с собой карандаши.
"Простое советское" решение
Достаточно было просто воспользоваться CSS-селектором :empty
.footer:empty {
display: none;
}
Разработчики настолько привыкли решать задачи с помощью JS, что у них даже мысли не возникло, чтобы посмотреть, что там есть в CSS
История #2: как загружать скрипты
Есть у нас ещё один виджет, боковая панель, которая должна растягиваться во всю высоту, но не перекрывать хедер и футер. Примерная формула получается такая: 100% - headerHeight - footerHeight
.
Решение работало гладко на всех страницах, кроме одной. Там почему-то headerHeight
считался правильно, а вот footerHeight
возвращал 0. Разработчик, которому досталась эта задача, поковырял глубже и выяснил, что document.querySelector('footer')
возвращает null
в этом случае, хотя позже футер на странице всё равно загружается. Какая-то мистика, подумал он и решил что надо перехватить момент его появления через MutationObserver. Это другой разработчик, не из первой истории, хотя костыль абсолютно такой же.
Мне это показалось странным, и я решил поискать альтернативное решение. И нашел его, достаточно было поменять местами пару строк кода...
Вот эти строки
Вот HTML этой страницы:
<html>
<head></head>
<body>
<header></header>
<main id="app"></main>
<script src="app.js"></script>
<footer></footer>
</body>
</html>
Каким-то образом скрипт оказался на странице раньше футера. Поскольку он исполняется синхронно, то в момент рендеринга футер еще не прогрузился, поэтому его высота и не считалась. Стоило поменять эти две строчки местами, и всё заработало как надо.
А разработчики, избалованные современными инструментами сборки, уже не пишут HTML руками, надеются на помощь html-webpack-plugin и т.п. Поэтому когда внезапно оказывается нужно написать немного HTML самостоятельно, то они тут же пасуют. Хотя казалось бы, что тут сложного?
История #3: корень всех зол
Реакт версии 16.8 подарил миру hooks API, а вместе с ним и огромное поле раскиданных граблей. Если прочесть документацию и понять что к чему, то писать вроде бы несложно, но из-за наличия хуков useMemo
и useCallback
теперь каждый джуниор мнит себя богом оптимизаций и вставляет их по поводу и без.
Посмотрим на такой пример. Есть компонент календаря, в котором нужно генерировать 2D-массив для отображения дат в текущем месяце. Вот примерный код:
import { getCalendarMonth } from 'mnth';
function Calendar({ date }) {
const month = getCalendarMonth(date);
// код рендеринга условный, просто чтобы показать структуру
return month.map((week) => (
<div>
{week.map((day) => (
<span>{day.getDate()}</span>
))}
</div>
));
}
getCalendarMonth
не особо тяжелая функция, но у разработчика все равно зачесались руки её заоптимизировать:
const month = useMemo(() => getCalendarMonth(date), [date])
Но такая оптимизация не работает, потому что объект date может быть другим инстансом, содержащим то же время, а useMemo сравнивает объекты в лоб. Поэтому нужно извлечь timestamp:
const timestamp = date.getTime();
const month = useMemo(
() => getCalendarMonth(date),
// eslint плагин ругается на то что мы используем не тот объект
// в зависимостях, поэтому нужно добавить исключение
// eslint-disable-next-line react-hooks/exhaustive-deps
[timestamp]
);
Тут возникает вопрос – а принесли ли эти выверты хоть какую-то пользу?
Давайте померим
Я воспроизвел ситуацию в этом демо: https://ethereal-rain-forger.glitch.me
Один список рендерится с мемоизацией, а другой – нет. В консоли пишется время затраченное на рендер. В обоих случаях это порядка одной милисекунды. А если не видно разницы, зачем плодить сущности и устраивать цирк с псевдо-оптимизациями?
Что же делать?
Ситуация удручающая. Разработчики усложняют решения на ровном месте и считают это абсолютно нормальным. Во всех приведенных ситуациях после моих предложений они всё-таки переделали код на более простую версию, но это потому что я на это указал, а сколько еще мест прошли мимо меня. За всем не уследить.
Поэтому если вы начинающий разработчик (да и всем остальным тоже), у меня для вас есть простые советы:
Даже если вы разрабатываете на фреймворке, потратьте время, разберитесь с vanilla js. Посмотрите, как оно ведет там себя под капотом, и будете увереннее разбираться, когда что-то работает не так ожидалось.
Учите CSS. Там есть очень много полезных свойств и селекторов, которые заменят вам тонны JS. "Я использую готовую дизайн-библиотеку" – это не ответ, под капот нужно заглядывать всегда, см. пункт 1.
Развивайте критическое мышление – ваш тимлид/ментор скорее всего научил вас определенным хорошим практикам. Но одно слепо им следовать "а то будет атата" и совсем другое разобраться, почему именно так сложилось, и что именно будет не работать если так не делать.
Помните про YAGNI, KISS и другие принципы. Если простая задача оборачивается запутанным решением, притормозите, посмотрите на неё с другой стороны, может быть вы слишком углубились в одну гипотезу, и забыли о чем-то очевидном.
Ну и помните, состояние ваших проектов зависит только от вас. Все инструменты уже давно имеются – надо только не забывать ими пользоваться.
Комментарии (191)
Mnemonik
25.07.2021 16:55+24Потом приходит менеджер и мягко объясняет вам что вы токсичный член команды, и если оба подхода работают, и разницы между ними нет (миллисекунды) то обижать других членов команды критикуя их решения подрывает командный дух. Такие дела...
in_heb
25.07.2021 21:41+3Не менеджер, а скрам-мастер. Эти мастера считают себя не менеджерами, а фасилитаторами!
MikhailDorozhkin
25.07.2021 17:09+8" - Нам с тобой некомфортно.",
" - Ты - токсичный.",
" - Ааааааа.",
и другие комментарии, будут у членов команды.
)justboris Автор
25.07.2021 17:32+9Конкретно в этой ситуации – нет. Когда я показал как надо, разработчики согласились тоже. Проблема именно в том что они не знали что так можно. Не научили в свое время
wunderwaffel
25.07.2021 22:29+2Пугающие тенденции. Что за разработчики такие? Недавно считалось "не тру" не знать основ информатики, при этом быть "веб мастером" и называться разработчиком. А теперь чтоли уже и js не зная писать на фреймворках это называется разработчик? Ох.
Xuxicheta
26.07.2021 01:55+2Это конечно печально, но бизнесу все равно что там знает разработчик. Главное смог он выполнить задачу или нет. И поддержка, но это часто вторично.
donPedro
26.07.2021 16:04Часто на англоязычных ресурсах вроде того же реддита встречаю вопросы вида "хочу стать реакт-разработчиком, нужно ли для этого учить яваскрипт" и ответы "не обязательно, потом как-нибудь в свободное время почитай" от недавно вступивших в ряды фронтенда
Dolios
26.07.2021 19:37Многие из тех, кто называет себя фронтенд-разработчиками, на самом деле, являются <название-любимого-фреймворка>-разработчиками.
AndreyHenneberg
26.07.2021 23:29-1Да, встретил недавно одного такого. Для него непосильной задачей оказалась вставка отдаваемого сервером готового куска HTML в контейнер, надо было только JS, чтобы он потом насиловал процессор горами JS-кода, чтобы нарисовать ровно тот же кусок HTML. Пришлось вспоминать молодость, открывать документацию по jQuery и всё делать самому. Мог бы и на чистом JS всё соорудить, но на восстановления такого объёма знаний потребовалось бы куда больше времени, а там был «deadline близко». То есть я, почти 10 лет не трогавший фронтэнд, сделал это всё быстрее, чем как бы фронтэнд-разработчик.
Laaru
29.07.2021 01:23не хотелось бы Вас расстраивать и дальше, но, всё же поделюсь :)
По какой-то причине я считаюсь разработчиком, хотя совсем не знаю информатику, в глаза не видел сайта на чистом пхп, а все мои знания - это битрикс, да jQuery, сверх того все мои подтверждения компетенции - это экзамены от битрикс. И вот погляди ж ты, самому смешно, но считаюсь разработчиком, а на столе табличка "программист" :D
rayz_razko
26.07.2021 07:16+2Все зависит от того как написать комментарий. Можно вежливо объяснить почему и зачем, а можно грубо. Учитесь софт скиллс.
haraldsson
25.07.2021 17:41+8Спасибо вам Борис огромное за :empty. Никогда не видел интересный пример его практического употребления, моя вина - не искал. Прям разинул рот от удивления. Сколько лишнего 'ternary' в коде! :) Бывает, однако, что мы от мусора не можем избавится. Делаем первую ревизию K.I.S.S. и YAGNI, но заказчик требует добавить/изменить/ дополнить/убрать... и мы делаем это. Потом опять, и опять. В какой-то момент ты осознаёшь что всё надо снести до фундаментa и сделать с начала... но заказчик за такое платить не будет... Потом получается вместо "keep it simple, stupid"... "keep it at least working"...
megahertz
26.07.2021 06:08+2Как минимум, empty очень удобен в любом проекте, чтобы навешать его на
<div id="root"></div>
, чтобы показывать загрузчик до загрузки js
censor2005
26.07.2021 08:04Стыдно сказать, я тоже не знал про :empty, хотя caniuse показывает что он давно поддерживается всеми браузерами. Вспомнил пару мест, где :empty очень пригодился бы в прошлых проектах. Хотя сам я не фронтендер )
MentalBlood
26.07.2021 09:38но заказчик за такое платить не будет...
Да, но это же вам надо как разработчикам, чтобы меньше времени тратить на новые изменения
Zalechi
27.07.2021 16:02Я не в теме, поэтому позвольте вопрос: а заказчику нельзя отказать его новым хотелкам за бесплатно? Вы ведь по условному ТЗ работайте. В рамках начального ТЗ исправляем за свой счет, а новые хотелки в кассу. Эти дела прописываются в контракте?
Как вообще устроена система. Обидно читать такие вещи, будь-то заказчик ака рабовладелец и капризная дама. Это мне сейчас так, а завтра эдак.. Че за фигня, они чего там в коммунизме живут, или есть нюансы, которые я не курю. Создается впечатление, что вас как лохов используют.
Проясните, если можно. А то часто такое читаешь и как понять такие контрактные/ТЗ отношения не знаю..
dreesh
27.07.2021 17:41Для внутренний разработки может быть и так) Тут либо собачиться с коллегами либо увольняться.
Zalechi
27.07.2021 17:52Не-не, я про работу с клиентом. Может я запутано спосил, но суть в примере:
Есть заказ написать страничку с тремя таблицами. Подписади договор. В процессе работы провели некотрые правки, например изменили толщину рамок таблиц, цвет фона. Это все входит в рамки договора, вопросов нет.
Но вдруг, клиент попросил нарисовать четвертую таблицу которая не оговаривалась в ТЗ. Еще и бесплатно? Пффф, сорян, надо условно переписывать код. И что, вы это делайте? Я спрашиваю в случае подписаных контрактов, а не мамкиных контор начинающих студентов готовых на всё?
dreesh
27.07.2021 22:51Этот вопрос уже к менеджеру который работает с клиентом,а не как ни к розработчику.
Zalechi
28.07.2021 00:17Тю точно, в не мамкиных конторах схема отношений закрученная. Получается определённая эскалация задачь. Вот почему парень выше писал, что срач между коллегами образуется. От оно чё.. Дааа.
В таком случае это задача ведущего клиента менеджера с архитектором разработки прорабатывать ТЗ клиента и эскалирвать исполнителям. Они на ЗП сидят, им пофиг по сути сколько раз какой проект переделывать, чисто обидно, а так..
v1000
25.07.2021 18:37+14анекдот про NASA, которые потратили миллионы долларов на разработку шариковой ручки для условий невесомости, а советские космонавты просто взяли с собой карандаши.
У этого анекдота есть продолжение - карандаш крошится, а графитная пыль, мало того, что в невесомости вредна для экипажа, так за счет своей проводимости может закоротить электронику.
Так что еще вопрос, что лучше - ручка за миллион или карандаш.
Mnemonik
25.07.2021 19:18+14это вроде не анекдот, а реальная ситуация, как дело было.
да, оказалось что графит крошится и в условиях невесомости мелкие острые твёрдые крошки графита которые не падают на месте и лежат, а заплывают везде - и в органы дыхания и управления (а графит токопроводный) - это проблема.
правда и русские не совсем идиоты и карандаши в них были восковые.
с другой же стороны НАСА которые «потратили миллион долларов» на самом деле сначала писали фломастерами, а потом заключили контракт с какой-то частной компанией производителем ручек которые действительно за миллион долларов (только свой, коммерческий, а не налогоплательщиков) разработали ручку и сами пришли к ним и единственное условие было что НАСА упоминает о том что в космосе пишут ручками этой компании…
ручки эти кстати потом замени и наши карандаши и американские фломастеры
Metaller
25.07.2021 19:25+10На самом деле НАСА не заключала контракта ни с какой компанией. Компания (а точнее один человек) на свои деньги разработал ручку, и только потом предложил ее НАСА.
dreesh
25.07.2021 20:55-3И разрабатывалась она для наживы, а в итоге партия которую купили была маленькая. А анекдот растиражировал Задорнов - (НАСА разработали ... за миллионы долларов...американцы тупые! а наши карандашами писали)
naishin
27.07.2021 12:50И такие ручки сейчас можно купить, и они офигенные. Пишут вверх ногами и почти на любой поверхности. На стекле, например.
monochromer
25.07.2021 19:41+10Я тоже за "чистый" CSS. Но по той же причине, по которой в `<Footer />` попадал то
null
, то пустой объект, у вас может прилетать строка из одного пробела или просто символ переноса строки, и тут `:empty` не поможет - для его корректной работы ему нужно именно "ничего" `<div class="this-is-empty"></div>`
Aphanasiy
25.07.2021 22:09+1У анекдота про ручки и карандаши есть продолжение. Стружка от грифеля, да и сами грифели иногда ломались и попадали либо космонавтам в лёгкие, либо в особо чувствительные части корабля.
С другой стороны этот анекдот в свете вскрывшегося факта тоже классно применяется. Хорошие разработчики потратят время, чтобы разобраться в нутре тех технологий, которые они используют, а плохие будут тыкаться в дебаг и долго-долго тупить.
Лично я с фронтендом не связан никак, но первая мысль, которая пришла в мою голову - это то, что там что-то не так с порядком в истории #2. Если человек не может догнать до этой мысли и не проверит эту гипотеху первым делом, то это очень-очень грустно.
ainu
26.07.2021 01:19+1Там кроме грифеля была история страшнее - атмосфера у американсикх кораблей была почти 100% кислородная, карандаш обычный бы загорелся бы от чего попало, да и не было "Обычных" карандашей никогда - маркеры у одной стороны (и до сих пор на МКС юзаются) и восковые карандаши с другой. Сейчас у обоих сторон те самые патентованные ручки "за миллион долларов".
Xuxicheta
26.07.2021 01:52+3В чистом кислороде не то что карандаши, человеческое тело загорается.
Как-то с трудом верится.
vorphalack
26.07.2021 04:08Не загорается, но от кислорода отказались достаточно быстро — после того, как прям на стартовом столе получили well done экипаж. заодно еще и двери сделали открывающимися в другую сторону.
garwall
26.07.2021 09:08ну у кислорода по тому времени плюсов было изрядно - нужно впятеро меньшее давление в капсуле, значит, лучше допуски по прочности, меньший груз брать и т.д.
VolodjaT
26.07.2021 09:42+1действительно некоторое время был кислород при пониженым давлением (до первого пожара)
Ndochp
26.07.2021 19:36Вы прям случаи такие знаете? крошки графита замкнут не больше чем слюна в выдохе.
AndreyHenneberg
26.07.2021 23:39Ну и вентиляция никуда не девалась и гоняла воздух через фильтры, потому что с человека много чего сыплется. Внезапно. Тем более, что воздух, в отсутствии тяготения, сам по себе перемешивается слабо, вот и приходится гонять вентиляторами.
Xuxicheta
25.07.2021 22:13+7Зашел на кликбейтный заголовок, а тут внутри два с половиной трюка. Причем тут основы фронтенда непонятно.
Ну и вообще, для человека из Ангуляра первые два решения смотрятся диковато. Потому что данные первичны, а отображение вторично, и не нужно из нисходящего потока кашу устраивать и прыгать туда сюда. Тем более сегодня у нас одна логика футера, завтра другая, это каждый раз будем искать где тут empty зарыт? А как вы это тестить будете с точки зрения логики? А если там комментарий или пробел?
Ну и насчет последнего, вы каждый раз милисекунды замеряете, чтоб ненароком лишнюю оптимизацию не вставить, которая все равно не на что не влияет? Не проще ли сразу оптимизировать, раз это не требует никаких затрат, и не возвращаться к этому более?
В общем мнение конечно интересное, но, имхо, излишне пафосно.
laatoo
26.07.2021 01:31+1сегодня у нас одна логика футера, завтра другая, это каждый раз будем искать где тут empty зарыт?
изменение требований приводит к работе, да, и есть вероятность, что в этой работе придётся рыть, это естественный порядок вещей
А как вы это тестить будете с точки зрения логики?
Если вы тестируете вёрстку/стили на соответствие логике — то точно так же: берёте html, если блок footer пустой, у него должен быть определённый стиль (.footer.border-width > 10px). Больше похоже на изврат, но если есть желание — реализуемо
Xuxicheta
26.07.2021 01:34+1не стоит путать "естественный порядок вещей" и плохую архитектуру. При хорошей "рыть" надо минимум.
laatoo
26.07.2021 01:40+1return <div> {children} {footer && <div ref={footerRef} className="footer">{footer}</div>} </div>
а вот эта мешанина из html и js на недоязыке в "компоненте" (который знает и как себя нарисовать, и как себя вести) вас нисколько не смущает с точки зрения архитектуры, SRP?
Xuxicheta
26.07.2021 01:45+3Ечли я тут много чего смущает, но я редко имею дело с реактом, поэтому код особо не рассматривал.
Больше всего меня задел именно тон автора, дескать мои костыли перекостыляют ваши, хотя есть явное ощущение что эта вся кухня вообще излишняя.
Если бы статья называлась "Ребята, мало кто использует :empty, смотрите как здорово", то нет проблем.
mayorovp
26.07.2021 10:18+1Зря вы jsx недоязыком называете — сейчас это чуть ли не единственный язык шаблонов, который можно статически типизировать (в виде tsx).
И компонент тут зря взят в кавычки — при компонентном разбиении совершенно нормально, когда компонент знает и как себя рисовать и как вести. И это даже SRP удовлетворяет, если не забывать про дальнейшую декомпозицию.
laatoo
26.07.2021 11:19это чуть ли не единственный язык шаблонов, который можно статически типизировать (в виде tsx)
зачем шаблону знать и думать о типах?
чтобы выбросить исключение, которое поймается выше (там, где и так знают какой тип expected, а какой given)?
чтобы выполнить операции с объектами, которые можно (нужно) выполнить выше уровнем и передать в шаблон уже чистые данные?
можете привести пример, когда статическая типизация в шаблоне даёт очевидное приемущество?в моём представлении шаблон это то, куда передают значения, а не типы
при компонентном разбиении совершенно нормально, когда компонент знает и как себя рисовать и как вести. И это даже SRP удовлетворяет, если не забывать про дальнейшую декомпозицию
посчитайте сами количество причин для изменений, и равно ли это число единице. если нет — SRP нарушен.
mayorovp
26.07.2021 12:29+2зачем шаблону знать и думать о типах?
Чтобы ловить опечатки в именах атрибутов, свойств и переменных. Чтобы произвольные переменные не превращались в строки сами собой. Чтобы не ловить
"TypeError: Cannot read property 'foo' of null"
в рантайме.чтобы выбросить исключение, которое поймается выше (там, где и так знают какой тип expected, а какой given)?
Чтобы при несовпадении expected и given возникла ошибка компиляции. И да, насчёт "и так знают какой тип expected" вы погорячились.
чтобы выполнить операции с объектами, которые можно (нужно) выполнить выше уровнем и передать в шаблон уже чистые данные?
У чистых данные тоже есть тип.
laatoo
26.07.2021 12:37-3Чтобы не ловить "TypeError: Cannot read property 'foo' of null" в рантайме
А почему вы передаете в шаблон null?
У чистых данные тоже есть тип.
А почему вы передаёте в шаблон данные неправильного типа?
И да, насчёт "и так знают какой тип expected" вы погорячились
Ну так а как так получается, что вы передаёте что-то в шаблон, не зная что этот шаблон ожидает получить (с чем работает)?
faiwer
26.07.2021 19:43+1зачем шаблону знать и думать о типах?
Вы смеётесь?
- Чтобы не указать не существующий prop
- Чтобы не передать в существующий prop неприемлемое значение
- Чтобы при рефакторинге компонента у вас автоматически на этапе компиляции сломался весь код выше, который использует этот компонент, теперь уже, не совсем правильно
По сути для дела ровно для тех же целей, что и в случае обычных TS файлов. Нет решительно никакой разницы шаблон это или руками написанный TS код. Это в любом случае будет кусок JS кода, который может содержать баги.
Вообще неожиданно видеть такой вопрос от ангулярщика.
Xuxicheta
27.07.2021 12:04+1Про ангуляр это было от меня, и типизацию в шаблонах я всеми руками поддерживаю. Да и вообще, нет никаких шаблонов, что в реакте что в ангуляре это транспилируемый js и его нужно типизировать.
faiwer
26.07.2021 19:41+1А в чём разница в приведённом вами куске кода с тем что было бы Angular или Vue? Просто кусок шаблона. Ну будет там
*ngIf
, это как-то поменяет суть вещей?
justboris Автор
26.07.2021 11:47Ну и вообще, для человека из Ангуляра первые два решения смотрятся диковато.
А как бы вы это сделали?
Ну и насчет последнего, вы каждый раз милисекунды замеряете, чтоб ненароком лишнюю оптимизацию не вставить
Дело в том, что мемоизация – это не волшебный соус, который можно просто добавить, и всё станет сразу быстрее. Наоборот, это очень хрупкая конструкция, которую нужно очень тщательно тюнинговать и профайлить (в какой-то момент вычисление ключа кэша может оказаться дороже вычисления заново). Этого конечно же в указанной истории никто не делал.
Xuxicheta
27.07.2021 12:10А как бы вы это сделали?
Скажем так, в существующем коде я возможно влепил бы костыль, похожий на ваш, но если смотреть с точки зрения архитектуры, то я всегда знаю имеет футер контент или не имеет и просто уберу его из рендера. Внутри самого же футера, поскольку он отвечает за себя, а не где-то в лайаутном контейнере.
Этого конечно же в указанной истории никто не делал.
Потому что профайлить чтобы выискивать ничтожную разницу никто не будет. Вот если начнет тормозить, тогда да, но не по каждому чиху же.
Расскажите лучше, бывали ли у вас реальные случаи замедления из-за чрезмерной оптимизации на спичках?justboris Автор
27.07.2021 19:00+1Насколько я знаком с Angular, там ровно такая же проблема:
<div class="footer"> <ng-content select="footer"></ng-content> </div>
Как вы заранее узнаете, есть ли что-то в ng-content или нет?
Расскажите лучше, бывали ли у вас реальные случаи замедления из-за чрезмерной оптимизации на спичках?
Замедления в скорости разработки – конечно были. В этом и суть, что такие псевдо-оптимизации добавляют команде мартышкин труд по их поддержке.
Aetae
28.07.2021 00:22+1По факту в реакте практически невозможно без профилирования сказать полезна ли мемоизация в конкретном случае, т.к. из-за архитектурных особенностей всё зависит по большей части не от человеческой логики, а от глубокой логики js-движка.
В этом вопросе я скорее согласен с этими ребятами Why We Memo All the Things.justboris Автор
28.07.2021 00:29Наркомания какая-то, если честно. Особенно часть про children, где они предлагают делать так
const Component = React.memo(() => { const children = useMemo(() => <div>content</div>, []); return <Container>{children}</Container> });
Вместо того чтобы
function Component() { return <Container> <div>content</div> </Container> }
Для пятничного поста в "ненормальное программирование" еще бы зашло, но писать такое регулярно - увольте.
Aetae
28.07.2021 00:38Во втором случае memo на Container, очевидно, сломается. О чём они, собственно, и толкуют.
justboris Автор
28.07.2021 00:48Речь о том, что как в первом случае никто в здравом уме не пишет. Вместо идиоматичного React сгруппированного в одном месте JSX мы получаем нечто странное. За этим лесом из memo становится не видно деревьев (собственно UI кода).
Достаточно оптимизировать пару узких мест, а не заниматься имитацией бурной деятельности и чинить несломанное.
Aetae
28.07.2021 01:03+2Так оно сломаное. Пропусти мемоизацию в одном месте и оно водопадом обрушится до самого конца.
Я прекрасно знаю идеологию реакт: «забей на оптимизацию сейчас, оптимизируй узкое место потом».
Проблема в том, что она не работает. Нет никакого узкого места. Каждый компонент в котором забили на оптимизацию — «чуть-чуть» тормозит. Потом они складываются в кучку и получается Patreon.
Тормоза размазаны по всем компонентам и починить можно, только переписав каждый. Ну или переписав «достаточно», чтоб тормозить стало незаметно. До следующего раза.
faiwer
28.07.2021 11:10+1Достаточно оптимизировать пару узких мест
В сложных приложениях вот эта стадия "давайте теперь оптимизируем узкие места" легко может разбиться об "блииин, нам теперь что весь проект переписывать?". Ну просто потому что сложный граф связей, который с самого начала архитектурно не был задуман как что-то что можно подвергнуть мемоизации, внезапно, нужно мемоизировать сразу почти всё и почти везде. Ну потому что дедка за репку, бабка за дедку…
Т.е. не получится просто пройтись и понавтыкать везде всяких
useCallback
,useMemo
, если это не было продумано с самого начала. Во всяком случае мой опыт такой.Ну и мемоизация по-умолчанию это не только про производительность. Это про простоту отладки и про простоту реализации сильно перемудрёных компонент.
justboris Автор
28.07.2021 14:00Согласен, про "пару узких мест" я преувеличил. В реальности это сложнее.
Это про простоту отладки и про простоту реализации сильно перемудрёных компонент.
Интересно, что может быть проще обычных функций, и как memo-обертки упрощают это еще сильнее?
faiwer
28.07.2021 15:09+1и как memo-обертки упрощают это еще сильнее?
- Ну скажем
dependencies
вuseEffect
. Опираясь на иммутабельность вы можете строить сложные деревья из эффектов. - Или скажем взять
useState()
, он возвращает[st, setSt]
, гдеsetSt
мемоизирован. Как этим можно было бы пользоваться не будь он мемоизированным, а был бы уникален для каждого рендера?
По сути React сам нас мотивирует класть в
dependencies
ВСЁ, что внутри задействовано. Есть специальные eslint правила. Рекомендуемые к использованию. И там даже есть правило дляuseEffect
(чем несказанно меня удивляет).Честно говоря я с трудом представляю как можно без тотальной мемоизации написать на хуках приложение. Оно же на изоленте висит. Ладно бы вместо хуков взяли MobX, там свои решения. Но ведь хуки к этому прямо провоцируют.
justboris Автор
28.07.2021 15:21Если нам нужно вызвать callback из useEffect, то мы заворачиваем его в наш кастомный хук
const memoizedOnChange = useStableEventHandler(onChange); useEffect(() => { if(someCondition) { memoizedOnChange(); } }, [memoizedOnChange])
Вот тут реализация useStableEventHandler. Основное преимущество – onChange, и всю цепочку зависимостей наверх мемоизировать не надо, мы получим актуальный инстанс в любом случае. Мемоизация скрывается внутри компонента и у его пользователей голова ни о чём не болит
faiwer
28.07.2021 15:26У нас есть похожее решение, только мы пошли дальше.
const storage = useStorage({ a, b, c, d, e }, { f, g }); useEffect(() => { storage.* }, [/* nothing */]);
Где a-e это изменяемые параметры, а f-g это неизменяемые поля для инициализации. Внутри
useRef
иuseLayoutEffect
для обновления.Используем его в ряде случаев, когда это позволяет сильно упростить сложные вещи. Тут главное не заиграться, и помнить что такие финты можно делать только для поведения. Если сам render от этого зависит, то никаких
useRef
.
faiwer
28.07.2021 15:28Рекомендую поменять вам
useEffect
наuseLayoutEffect
во избежание неприятных багов, когда что-то запускается раньше, чем синхронизация состояния произошла. А ещё стоит убрать, [ref]
(он там не нужен, он статичен).А ещё можно убрать зависимость в
useEffect
..current = value
невесомая операция :) Лишние проверки ни к чему
Aetae
28.07.2021 18:15Да и useCallback выкинуть - в ref положить массив\объект и функцию-обёртку сразу же первым ключом.)
- Ну скажем
faiwer
28.07.2021 15:11+1Ну и отдельно про отладку — дебажить код стократно проще когда вместо 150 рендеров глючного компонента (или связки компонент) будет 1. Когда если рендер был, то он был по какой-то объективной причине. И если он очевидно лишний, то выяснив причину лишнего рендера в ней же мы и находим баг. Примерно так обычно было на моём опыте.
faiwer
28.07.2021 11:05+2В этом вопросе я скорее согласен с этими ребятами [Why We Memo All the Things](Why We Memo All the Things).
Ребята несколько перегибают палку. Есть очевидные места где точно не нужна мемоизация. Например тут:
<button onClick={...}/>
Вот этот
onClick
не нужноuseCallback
-ить, т.к. кодовая база усложнится сильно, а оптимизации никакой не будет. Время наremoveEventListener
иaddEventListener
потребуется крошечное. Можно и нужно смело это игнорировать.Ну и как указал justboris, буквально всегда когда есть
children
или какой другой слот, никто в здравом уме не будет мемоизировать этотprop
.В HoC чаще всего мемоизация это марштышкин труд. Но в общем и целом мы действуем примерно также — на большинстве уровней мемоизация по-умолчанию.
Aetae
28.07.2021 12:08Дык это как раз суть их подхода: мемоизируем сейчас, чтоб не выстрелить себе в ногу потом.
Да, в button onClick мемоизация не нужна, но вот при следующей итерации на месте button окажется CoolButton, где она нужна, но её, по недогляду, не будет.
faiwer
28.07.2021 13:38+1Не согласен. Во-первых для чего нужен code review? Во-вторых даже если не будет, ничего страшного. Прямо вот совсем. Мы даже в случае CoolButton говорим уже о листьях vDom древа. Т.е. там не окажется случайно Excel таблички, адронного коллайдера или ещё чего.
А вот обратная сторона медали заключается в том, что мемоизация бьёт по стоимости кода (её сложнее читать, её сложнее писать, её сложнее рефакторить). И это, имхо, сильно перевешивает гипотетические "потом забудем добавить потеряем 1.5 наносекунды".
Aetae
28.07.2021 15:33+1Я мог бы согласиться, но каждый выбор мемоизировать или нет заставляет меня думать: "подгружать" контекст, подгружать особенности, анализировать необходимость, делать выбор. Каждый раз когда я думаю о таких мусорных, не относящихся к конкретной задаче, вещах, я трачу своё время, и, главное, своё "процессорное время". Пускай в каждом конкретном случае это немного, но в целом - это заметно сказывается на продуктивности. Гораздо приятнее или мемоизировать всегда, или "мемоизировать потом", потому что это можно сделать навыком, не требующим мышления.
В целом, я считаю что фреймворк/библиотека должен думать за меня в 99% случаев, оставляя на меня edge-case вещи, действительно требующие осознанного решения. Идиоматичный же реакт заставляет меня либо думать в этих самых 99% случаев, либо забить(тем или иным образом), но чувствовать себя говнокодером(пусть даже и неоправданно).
faiwer
28.07.2021 15:53В целом, я считаю что фреймворк/библиотека должен думать за меня в 99% случаев
Если это не божественный $mol, то звучит утопично. Я пока за что не хватался, везде приходилось включать думалку, иначе были тяжёлые последствия. Возможно React в этом деле особенно преуспел, не знаю.
Вот любят хвалить Svelte. А потом смотришь код и видишь какие-то ручные переприсваивания. Они называют это
костылёмоптимизацией. Т.е. нужно прямо вникать.Или вот Vue. Написал вроде решение. А потом видишь что оно ну совсем не масштабируется. Оказывается Vue не умеет в зависимости computed от computed за O(1).
Или вот Knockout. Взял навертел разных видов observable и потом дичайше страдаешь от очень хитрых race condition.
C RxJS прямо сразу падаешь в бездну сложности :)
Ну и так, имхо, куда не плюнь. Не думаю, что есть решения где просто сел и пишешь. Только $mol
Aetae
28.07.2021 16:12-1"computed от computed за O(1)"
0_o, а это нужно?
Имхо, Vue 2 самое близкое пока, что видел к этому идеалу. (В Vue 3 просрали все полимеры.)
faiwer
28.07.2021 16:320_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 вышеописанная проблема всё ещё актуальна.
Aetae
28.07.2021 17:03В целом да, согласен, конечно. Но это, вполне очевидно, как раз тот самый 1% edge-case, когда надо подумать. Вполне интересная задачка, понятное узкое место, а не скучная рутина.)
Xuxicheta
31.07.2021 22:38в rxjs это поначалу сложно, а потом просто пишешь и не задумываешься. Хотя мб это и везде так :)
MaZaAa
01.08.2021 11:46-2в rxjs это поначалу сложно, а потом просто пишешь и не задумываешься.
Допустим с горем пополам ты напишешь, при помощи этой дичи, да. Мало того что на это без слез не взглянешь, так ещё потом если ты к этому коду вернешься, а уж тем более если кто-то другой вернётся, то пиши пропало.
Чисто write-only и чудовищный bus фактор со старта проекта. Нет уж спасибо)
Пожалуй не зря разумные люди выбирают писать простой и понятный код для любого человека и в любое время к нему вернись, все будет понятно. Делают хорошо и себе и окружающим, бонусом не приговаривают бизнес к скорому переписыванию проекта с нуля)Xuxicheta
01.08.2021 18:55Все ровно наоборот. Это без рх люди пишут дичь, на которую без слез не взглянешь. А с помощью этой либы можно удобно упаковать асинхронные взаимодействия в просто и понятный код, практически декларативный формат.
Я еще не видел людей, которые после рх хотели бы вернуться к промисам по доброй воле.
Да, надо научиться видеть смотреть на операции как на поток данных, выучить хотя бы самый популярный десяток операторов и научиться самому делать операторы, это не так сложно.
nin-jin
01.08.2021 20:11О, голубчик, попались, вы-то мне и нужны. Уже третий год не найду ответа на простой вопрос по rxjs. Какой из 10 операторов порекомендуете?
markelov69
02.08.2021 00:52Посмотрел ваш вариант (https://stackblitz.com/edit/rxjs-wxfjxe?file=index.ts), жесть конечно, вырви глаз и проверки работоспособности вашего «решения» тоже нет.
Вот как как выглядит по настоящему работающее решение здорового человека, с проверками работоспособности и удовлетворяющие все требования задачи — stackblitz.com/edit/typescript-3huxim?file=index.ts
nin-jin
mayorovpКстати, забавно, что указанную задачу (эффективную реактивную фильтрацию массива) вообще ни одна библиотека не решает.
^
Xuxicheta
02.08.2021 01:16Так вы совсем другое сделали, ваше "решение" это фильтр по одному параметру, который перед запуском проверяет изменения этого самого параметра во всех итемах.
На самом деле тут инструмент вообще не важен, завтра нарисую чуть по другому, там присутствие rx вообще минимально.
markelov69
02.08.2021 09:46Так вы совсем другое сделали
Я сделал ровно то, что нужно было в требованиях. И работает это именно так, как нужно.это фильтр по одному параметру
С чего это по одному то? сколько угодно параметров засуньте в функцию фильтра и все будет работать.
mayorovp
02.08.2021 09:52Что-то вы переусложнили всё, зачем там вообще autorun внутри reaction, если ваше решение сводится к одному computed?
И нет, ни тот ни другой вариант не являются эффективными, поскольку при любом изменении любого элемента массива происходит повторная фильтрация массива целиком.
markelov69
02.08.2021 10:05Что-то вы переусложнили всё, зачем там вообще autorun внутри reaction, если ваше решение сводится к одному computed?
Это понятно, чтобы сам алгоритм было видно. А так это в computed помещается и дело с концом — stackblitz.com/edit/typescript-y4hjir?file=index.tsИ нет, ни тот ни другой вариант не являются эффективными, поскольку при любом изменении любого элемента массива происходит повторная фильтрация массива целиком.
Нет, не при любом изменении, любого элемента в массиве, а только если изменяются конкретно те параметры, которые участвуют в фильтре. Так всё же видно и проверки есть с console.log'ами.
mayorovp
02.08.2021 10:11+1Нет, не при любом изменении, любого элемента в массиве, а только если изменяются конкретно те параметры, которые участвуют в фильтре.
Но при изменении параметра, который участвует в фильтре, у пятого элемента массива вы будете заново проверять все остальные элементы, вот я к чему.
Причём если решить эту проблему, то получится сэкономить гораздо больше времени чем экономится от динамического нахождения зависимостей.
markelov69
02.08.2021 10:47Но при изменении параметра, который участвует в фильтре, у пятого элемента массива вы будете заново проверять все остальные элементы, вот я к чему.
Если вы так настаиваете)) То вот решение — stackblitz.com/edit/typescript-zhgtnu?file=index.ts
теперь все кэшируется и только выполняется пересчет функции только для элемента который изменился.
mayorovp
02.08.2021 11:46Ну, как-то так. Только надо перенести действия из первого аргумента reaction во второй, придумать способ пересчитывать filteredItems после срабатывания внутренней реакции, а лучше — использовать computed вместо всей этой радости.
Также надо более активно очищать trackedItems, либо переключиться на WeakMap с FinalizationRegistry.
И да, не забыть использовать функцию filterWrapper, а то вы её написали но не вызвали.
А теперь смотрим на весь этот код и понимаем, что он не является частью библиотеки.
markelov69
02.08.2021 12:07Только надо перенести действия из первого аргумента reaction во второй
Не надо, первый аргумент все равно каждый раз вызывается при изменении в нем данных.И да, не забыть использовать функцию filterWrapper, а то вы её написали но не вызвали.
Смотрите внимательнее, я же говорю, там все работает и все с проверками, все используется разумеется.А теперь смотрим на весь этот код и понимаем, что он не является частью библиотеки.
Он и не должен быть частью библиотеки.придумать способ пересчитывать filteredItems после срабатывания внутренней реакции
Это было реализовано в других примерах) Да и смысл если эти данные нужны только по требованию.
mayorovp
02.08.2021 12:30Не надо, первый аргумент все равно каждый раз вызывается при изменении в нем данных.
Почему в таком случае вы не 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'; // упс, а реакции-то нет
markelov69
02.08.2021 12:51-1Почему в таком случае вы не autorun использовали?
Изначально писал с reaction, в итоге просто поленился вместо него в итоге autorun воткнуть, но сути это не меняет на работу задачи не влияет.Э-э-э, нет, это как раз принципиально:
Вы ещё раз проверьте все, там все работает правильно и ровно так, как нужно да и реакции есть разумеется, не знаю почему вы думаете что их нет. Консоль логи для этого там пишутся. И для вас там специально написаны комментарии когда данные реально изменяются и когда нет, так вот когда они реально не изменяются никаких реакций нет и быть не должно.
Как бы вот:
mayorovp
02.08.2021 13:00Ну да, вы же получаете filteredItems явно, вот оно и "работает". А вы попробуйте выводить filteredItems внутри autorun.
Я же вам привёл тот код, который вы сломали своей реализацией. Вот вам ссылка: https://stackblitz.com/edit/typescript-vni7na?file=index.ts
markelov69
02.08.2021 13:22Ну да, вы же получаете filteredItems явно, вот оно и «работает». А вы попробуйте выводить filteredItems внутри autorun.
Я же вам привёл тот код, который вы сломали своей реализацией.
Ну вот с явными обновлениями и с работающим autorun
stackblitz.com/edit/typescript-wgar48?file=index.ts
Мораль такая, можно разводить демогогию вокруг этой задачи долго, но факт таков — нечего сверх естественного в ней нет и если и правда есть нужда именно в таком поведении это можно реализовать, если не лень. Мне уже лень тут честно экономить на спичках)
mayorovp
02.08.2021 14:08Вы заметили, что у вас до строчки
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(); }; }
Зачем тут вообще реакции?
markelov69
02.08.2021 14:24-1Прекратите писать подобное! На этот код больно смотреть.
Без этого вы не додумались ни до чего сами.Нормальный код должен выглядеть вот так:
Жаль что вы сами до всего этого не додумались изначально, пришлось вас к этому подталкивать раз за разом. В противном случае написали бы сразу рабочее решение и дело с концом.
Вот ваши словаНо при изменении параметра, который участвует в фильтре, у пятого элемента массива вы будете заново проверять все остальные элементы, вот я к чему.
Причём если решить эту проблему, то получится сэкономить гораздо больше времени чем экономится от динамического нахождения зависимостей.
Взяли бы да и написали код сразу, и показали как надо, вместо если бы да кабы.
mayorovp
02.08.2021 19:43Вообще-то я сразу додумался туда computed поставить, вот только я не нахожу подобное решение простым — и тот факт, что вы так и не додумались до него, это подтверждает.
Вы доказывали, что в mobx задача решается просто, потому и код писать вам.
Кстати, результат всё ещё не оптимален на больших массивах.
markelov69
02.08.2021 23:54Кстати, результат всё ещё не оптимален на больших массивах.
Ну когда речь именно о динамической фильтрации со всевозможными условиями, тем более на больших массивах абсолютно оптимальных результат быть и не может в принципе, тут нет ничего удивительного, только компромиссы.
mayorovp
01.08.2021 23:53Ну так вы же специально задаёте такой вопрос, который вообще не в области ответственности Rx. Rx управляет процессами, а не изменением состояния.
Кстати, забавно, что указанную задачу (эффективную реактивную фильтрацию массива) вообще ни одна библиотека не решает.
nin-jin
02.08.2021 07:49-1Любой процесс - это последовательное изменение состояний. Вся разница между push (rx, bacon etc) и pull ($mol_mem, mobx etc) подходами организации реактивных потоков данных в том, что первые предполагают статическую конфигурацию потоков, что в реальной жизни не встречается, а даже если и попытаться описать реальное приложение статическими потоками, результат получается крайне не эффективным. Вторые же динамически перестраивают потоки данных, в соответствии с текущим состоянием приложения.
Эффективная фильтрация - это уже другая задача, которая в общем случае, если и решается, то за счёт крайне высокой сложности решения. Тут же задача попроще - не делать перефильтрацию, когда в этом нет смысла.
Xuxicheta
02.08.2021 10:08В реальной жизни потоки как раз статичны. Задачи типа динамических фильтров это редкость, к ним проще точечно подходить.
nin-jin
02.08.2021 12:07-2Везде, где есть фильтры, они динамические. Что такое статичный фильтр я даже представить не могу. Но в мире RX с динамикой плохо, поэтому вы занимаетесь самообманом. Не надо так.
mayorovp
02.08.2021 10:08+1Рассмотрим такую задачу:
- из состояния есть фильтр и номер страницы;
- при каждом изменении фильтра номер страницы должен сбрасываться в 1;
- при каждом изменении фильтра и номера страницы надо делать два http-запроса для получения отображаемых данных (второй запрос использует данные первого);
- слишком часто запросы делать нельзя, также надо отменять прошлые запросы если фильтр или номер страницы изменился.
Где здесь вообще требуется динамическое определение конфигурации потоков? И какими костылями вы будете делать это в pull-подходе?
В rx, к слову, это делается как-то вот так:
filter.pipe( switchMap(() => { page.next(0); return page; }), debounce(50), switchMap(() => request1({ ...filter.value, page: page.value })), switchMap(data => request2(data)) )
markelov69
02.08.2021 10:53Рассмотрим такую задачу:
из состояния есть фильтр и номер страницы;
при каждом изменении фильтра номер страницы должен сбрасываться в 1;
при каждом изменении фильтра и номера страницы надо делать два http-запроса для получения отображаемых данных (второй запрос использует данные первого);
слишком часто запросы делать нельзя, также надо отменять прошлые запросы если фильтр или номер страницы изменился.
Вы реализуйте по настоящему эту задачу на rx и скиньте ссылку на код в сэндбоксе, а вам реализуют эту задачу с MobX'ом или любитель $mol'a с $mol'ом.
mayorovp
02.08.2021 11:35+1На 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 существует, хотя бы в экспериментальном виде, иначе бы жести было ещё больше.
markelov69
02.08.2021 11:44только вот в итоге как раз жесть с промисами и получится
Я не вижу никакой жести в вашем прототипе, все читается сверху вниз и что делает предельно понятно и наглядно. Можно всё это оформить в классах вспомогательных и скрыть в них детали реализации.
markelov69
02.08.2021 12:15-1Ну вот rx из таких вспомогательных классов и состоит.
Не надо, вырви глаз лапша не котируется и даже близко не стояла. Мы ведь говорим о человеческом коде(я надеюсь), а не write only одноразовых поделках.
MaZaAa
02.08.2021 12:16-3ага, не вижу никакой жести, одна нечитабельная мешанина
Вам ли рассуждать о нечитаемой мешанине, посмотрите на код который вы пишете и ужаснитесь, там вы как раз все это и увидите. А тот код который написан выше реально прост и понятен прямо сходу, и не надо рвать волосы на голове и уговаривать пару бутылок водки чтобы распутать цепь событий.
Xuxicheta
02.08.2021 12:27неа.
Вообще непонятен, потому что цепи событий у вас как раз нет. mobx и не работает с ними, он менеджит данные. У него другая абсолютно задача.
Это хорошая, интересная библиотека по своей концепции, но мало пригодная для реального применения. Я долго пытался ее приспособить к проекту и пришел к выводу что он mobx для меня бесполезен, т.к. покрываемые им задачи легко решатся и без него, а вот непокрываемых остается целый слой, которые придется разруливать событиями, промисами и таким вот менеджментом абортов.
Да, в реакте mobx действительно эффективно помогает, до поры до времени, просто потому что альтернативы тоже все примерно такие же.
nin-jin
02.08.2021 12:30-1Вы пытаетесь выдумать задачу, в которой 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(), } ) }
mayorovp
02.08.2021 12:58Нет индикатора ожидания, а его добавление в интерактивной форме порождает глитчи и зависания.
Это довольно простая задача, решение которой не отличается в rx и mobx. А вот о глитчах и зависаниях, пожалуйста, подробнее.
Нет защиты от смены фильтра (как и любого промежуточного значения) на такой же, из-за чего после каждого оператора надо вставлять distinctUntilChanged.
Совершенно необязательно это и правда требуется.
Более того, сравнение объектов по ссылке — мартышкин труд, а глубокое сравнение объектов дорого. Поэтому эффективнее всего недопускать лишних обновлений вместо того чтобы с ними потом героически бороться.
Нет шаринга между разными потребителями — типичная бага по лени/незнанию/недосмотру. Очень весело такое дебажить.
Не в любой точке требуется шарить процесс. А если требуется — это всего пара строчек.
Нет обработки исключительных ситуаций, в случае которых все эти "статические" стримы надо создавать заново.
Согласен, но для этого тоже есть операторы.
А теперь по поводу вашего решения. Оно просто не работает (по крайней мере, как вы расписываете).
Рассмотрим следующий сценарий:
-
пользователь изменил фильтр, ids()=[1,2,3], вызван request2 и вернул исключение (насколько я помню, асинхронность работает у вас именно так);
-
запрос завершился, data пересчитывается и request2 вернул данные — тут важно, что повторный вызов request2 с теми же самыми параметрами не делает повторного запроса;
-
пользователь изменил фильтр, но ids() снова [1,2,3] — data, либо вообще не пересчитывается, либо request2 должен вернуть те же самые данные (иначе пункт 2 не сможет работать).
В итоге, повторный запрос на сервер не делается, хотя нет никаких оснований ожидать что данные на сервере не изменились.
-
nin-jin
02.08.2021 13:25-1Это довольно простая задача, решение которой не отличается в rx и mobx.
А в $mol эту задачу вообще не надо безконца решать.
А вот о глитчах и зависаниях, пожалуйста, подробнее.
Нарушение инвариантов при конкурентном исполнение задач. Выглядит как появление и пропадание индикатора в произвольные моменты времени. Частая такая смена состояния выглядит как мерцание.
Совершенно необязательно это и правда требуется.
Требуется почти всегда. Пользователь фактически ничего не поменял, а мы зачем-то шлём запросы.
Более того, сравнение объектов по ссылке — мартышкин труд, а глубокое сравнение объектов дорого. Поэтому эффективнее всего недопускать лишних обновлений вместо того чтобы с ними потом героически бороться.
Именно для этого всякие distinctUntilChanged и нужны, чтобы останавливать поток данных на пол пути. И чем раньше остановишь, тем меньше потом с ним бороться. А в самом начале стрима вы не можете знать, что где-то посередине сгенерируется эквивалентное значение.
Не в любой точке требуется шарить процесс. А если требуется — это всего пара строчек.
Я подчеркну ключевой момент: по лени/незнанию/недосмотру. Ну и я бы посмотрел, где это шарить не нужно, а нужно запускать стрим с самого начала со всеми промежуточными вычислениями для каждого подписанта.
Согласен, но для этого тоже есть операторы.
Которые пересоздают пол приложения, ибо финализированный стрим уже не оживить.
В итоге, повторный запрос на сервер не делается, хотя нет никаких оснований ожидать что данные на сервере не изменились.
В нормальных апи сервер присылает уведомления, когда что-то меняется, а не ожидает, что его будут долбить одинаковыми запросами. Но если вам не повезло с сервером - просто убираете мемоизацию первого запроса и получаете исполнение обоих запросов каждый раз.
mayorovp
02.08.2021 14:11Я подчеркну ключевой момент: по лени/незнанию/недосмотру. Ну и я бы посмотрел, где это шарить не нужно, а нужно запускать стрим с самого начала со всеми промежуточными вычислениями для каждого подписанта.
Например, если подписчик заведомо один.
В нормальных апи сервер присылает уведомления, когда что-то меняется, а не ожидает, что его будут долбить одинаковыми запросами.
Надо уметь работать с любыми api, а не только с "нормальными". Кстати, не помню когда я "нормальное" api последний раз видел.
Но если вам не повезло с сервером — просто убираете мемоизацию первого запроса
О, так у вас можно убрать мемоизацию?
Кстати, а как без мемоизации будет ваш псевдо-поток на исключениях работать?
nin-jin
02.08.2021 14:46-1Когда подписчик один шаринг просто ничего не делает. Поэтому во всех остальных либах шаринг просто неотключаем.
mayorovp
02.08.2021 19:44-1Не могли бы вы объяснить понятнее какой такой магией request2 должен отличить первый вызов от второго?
А то по вашей ссылке какой-то детский рассказ вместо документации.
sky2high0
31.07.2021 21:50Поддержу первое, что CSS :empty тут скорее вредит, т.к. размазывает логику между JS и CSS, и в целом это неожиданное поведение, что не только JS управляет тут видимостью. Нарушает one way flow. ИМХО, правильнее было бы все-таки вытащить внутренний флаг видимости из Footer.
Не поддержу про оптимизации. Тут автор статьи прав. Не надо плодить сущности без необходимости. Это усложняет, утяжеляет код без какой-либо причины. А еще мемоизацию можно неверно реализовать и породить кучу багов.
Xuxicheta
31.07.2021 22:42Возможно стоит наборот, не оптимизировать все подряд без причины. Но это чревато накоплением микротормозов. Не знаю, непривычны мне эти реактовские заморочки, чтобы на каждой мелочи микросекунды замерять.
Я обычно сразу пишу чтобы свести пересчеты к минимуму.
IvanKalinin
26.07.2021 00:22+3Спасибо, Борис.
Я прям чувсвую вашу боль. Сейчас целая проблема найти хороших разработчиков. К сожалению подавляющее большинство на рынке - не разработчики, а то, что я называю advanced user (продвинутые юзеры). Они лишь "умеют" пользоваться библиотеками и фреймворками, при полном отсутствии фундаментальных знаний.
Вот один из ярких примеров.
Там большая часть субтредов прекрасна, где "сеньеры" и "бест практис адвокаты" на серьезных щщах, рассказывают, что они не пользуются "каскадом" и, чтоб не было проблем нужно пользоваться "стандартрными" решениями. (хотя что может быть более стандартным, чем чистый CSS не понятно, но из контекста имеются ввиду styled-components, css библиотеки (tailwind, bootstrap). Некоторые даже обижались :-Dprincessmilana
26.07.2021 02:27+3Eсли человек дорос до сеньера и ему ни разу не пригодилось это знание (и он ни разу не наткнулся на него на стековерфлоу), то это знание не нужно, не очевидно и переоценено.
Плюс, это большая заслуга фреймворков, что люди пишут рабочий код, не разбираясь в неочевидностях используемых инструментов.
Например, проблема == в js просто была решена ===, и варнингом на любое использование ==.IvanKalinin
26.07.2021 06:41+2Вы правда не видите проблему?
Как можно дорости до сеньора и не иметь базовых, фундаментальрых понятий? Это все равно что стать главным инженером АЭС не зная основ физики, ведь она переоценена.В конечном итоге, ваша цель, не важно какой фреймвор используете - доставить HTML и CSS до браузера. И как вы собираетесь это дебажить, или оптимизировать, не понимая как это работает.
Получается действительно "продвинутый юзер", а не сеньор девелопер, который выучил команды, которые нужны в конкретный момент карьеры, не углублясь в саму суть как это работает и почему именно так.
К сожалению ситуация становтится все хуже и хуже. И обилие курсов "стань программистом за 24 часа" лишь усугубляют проблему. А сейчас еще Git Copilot выстрелит, вот мы насмотримся говнокода.
dreesh
26.07.2021 08:15+1Программистов всего 4% больше не станет, правило 80/20. Из 20% населения земли только 20% могут реально программировать, а остальные обезьянничать.
gian_tiaga
26.07.2021 01:13Почему контейнер содержит внутри себя футер, по сути нарушая принцип единственной ответственности?
Xuxicheta
26.07.2021 01:39-2Поддерживаю. Почему-то никто тут не начал с логики. А вместо этого обсуждают как лучше в бизнес слое высоту футера считать.
megahertz
26.07.2021 06:16SRP можно очень широко трактовать. Если в дальнейшем предполагается, что правки будут одновременно вноситься и в компонент и в футер, то все сделано правильно.
gian_tiaga
26.07.2021 13:47-1Пришёл новый человек в проект, стоит задача поправить футер, вы сразу поймёте что надо в контейнер лезть? Как не трактуйте, зачем 2 разные вещи клеить в 1, также можно и всё в 1 компоненте сделать не разбивая на части ?
mayorovp
26.07.2021 10:20+4А где тут, собственно, нарушение SRP-то?
Я вижу у контейнера единственную ответственность — отобразить этот самый контейнер, с переданным содержимым и переданным футером.
gian_tiaga
26.07.2021 13:48-1Почему не передать футер внутрь так например:
<Container> ... <Footer/></Container>
?Выше по этому поводу написал, смысл тогда разбиения на компоненты?
mayorovp
26.07.2021 15:56Потому что футер должен быть соответствующим образом оформлен. В тексте поста там просто div вокруг футера — но я легко могу представить ситуацию, в которой на этот div навешиваются дополнительные стили. Например, там может быть другой фон. Или рамка.
gian_tiaga
26.07.2021 17:22И в чём проблема, это сделать так как я показал? У меня Footer это отдельный компонент который может всё вышеперечисленное содержать
mayorovp
26.07.2021 18:33+2Проблема в том, что у вас футер перемешан с контентом, а может понадобиться их разделить.
Например, при добавлении контейнеру полей у вас неизбежно получится как на картинке слева, а не как справа — и это придётся исправлять отрицательным отступом, что само по себе грязный хак и ведёт к другим проблемам.
gian_tiaga
26.07.2021 19:12-2Вариантов несколько:
<Container>Content<Container>
<Footer>Content</Footer>
второй
<Container>Content<Container>
<Footer>
<Container>Content<Container>
</Footer>
и т.д.И это как раз даёт больше гибкости и преимущества разделения на компоненты, нежели компонент контейнер будет в себе держать всё
faiwer
26.07.2021 19:35+2Мне кажется вся эта ветка спор ни о том. Переименуйте этот несчастный
<Container/>
в<Layout/>
у которого будутcontent: React.FC
,footer: React.FC
,header: React.FC
. Теперь вроде всё "легально", но проблема остаётся.Т.е. описанная проблема никуда не девается. Она о том что когда "сверху" неизвестно, будет ли что-то снизу. А при этом "снизу" хочется ничего не знать про "сверху". В итоге получается такая вот ерунда.
И вот как-то простых решений кроме "заставить кого-нибудь знать больше, чем хотелось бы" или "поменять к чёрту ТЗ" нет :)
mayorovp
26.07.2021 21:30Теперь добавим скруглённые углы, и вспомним про опциональность футера:
У вас без дополнительных хаков выйдет как слева, а надо — как справа.
Опять всё сломалось...
gian_tiaga
26.07.2021 21:37-2Вариантов также нормально это решить уйма. Да хоть через тот же css. Этот спор может затянуться надолго, давайте остановимся, пусть каждый останется при своём
Spaceoddity
27.07.2021 01:03+1Ага, или у контейнера какой-то общий стиль с футером. Например растягивание на всю ширину экрану с соответствующим бэкграундом. А непосредственно сам футер и контентная часть имеют фиксированную ширину и центруются (обычно очень распространённое явление). В таком случае и футер, и контент надо оборачивать в какой-то блок. Надо смотреть конкретный дизайн. В общем же случае - лучше всё-таки иметь дополнительную "обёртку", чем не иметь. Так что претензия высосана из пальца.
skeevy
26.07.2021 11:31+4История #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 мсек? Не надо отвечать, это очередной холиварный вопрос, тут вопрос целесообразности, как я и говорил чуть вышеfaiwer
26.07.2021 19:32а может этот код будет работать каждый кадр?
Не совсем. Изменения
<Footer/>
снизу оно не отловит. Вроде суть проблемы именно в этом.
grgd
26.07.2021 11:31+1Кажется дело в том, что сейчас HTML/CSS/JS это в первую очередь compile target, а не основные языки разработки (не для всех, но все же). И этот трюк с :empty воспринимается примерно как "можно же в бинарнике две инструкции поправить и не городить огород в исходниках"
rewlad-dev
26.07.2021 18:08+2История #1: ":empty", это не "основы", а одна из 100500 фичей CSS. Просто есть два человека, первый придумал ":empty", и был ближе к комитету по стандартам, поэтому фичу затащили в браузер, а другой придумал хуки, или еще что-то, но был в другой тусовке. Основы -- программирование (функции в реактивность в данном случае). И поведение разработчиков смотрится вполне нормально. А то, что мимо шел человек-энциклопедия по CSS -- приятный бонус.
История #2: Ввести правила: Пихать "script" в конец, а не куда попало. Запускать действия по событиям (не запускать вне функций). Низкоуровневые подробности гуглить, если припрет.
faiwer
26.07.2021 19:28Каким-то образом скрипт оказался на странице раньше футера
Правило хорошего тона не использовать блокирующие скрипты, и цепляться к таким вещам как
onDOMContentLoaded
. А не двигать строки в HTML.В обоих случаях это порядка одной милисекунды
В реальном календаре там может быть не
<span>{day.getDate()}</span>
, а куда более сложная конструкция (зависит от дизайна и требований). И самgetCalendarMonth
может возвращать более сложную структуру. В итоге если есть компоненты<Week/>
и\или<Day/>
, которые обёрнутыReact.memo()
, то речь будет уже идти не об одной милисекунде. К тому же разбор полётов (какой-нибудь хитрый баг) будет проще, если вы не пересоздаёте vDom всего календаря с нуля на каждый чих.В целом про
getCalendarMonth
. Я помню как на IE6 подобная штука приводила к 40+ секундным тормозам. В условном "getCalendarMonth" какой-то jQuery библиотеки былObject.assign
, который был покрыт полифилом, которые нещадно тормозил. Я конечно в итоге нашёл все "затыки" и всё залетало, но было неприятно обнаружить что никто не сделал нескольких элементарных оптимизаций.В процессе обсуждений они так же решили уточнить у меня, что я думаю про этот подход.
Дык. Я думаю все догадались что вы ничего хорошего про это не думаете, но что вы сделали? :) Оборвали рассказ на самом интересном месте. На мой взгляд тут надо лечить причину, а не следствие. Ломать ту логику которая позволяет пробросить завуалированный
null
в слот.отому что объект date может быть другим инстансом, содержащим тоже время, а useMemo сравнивает объекты в лоб.
Если это редкий случай, то и плевать. В общем тут нужно видеть код.
faiwer
26.07.2021 19:48+1Достаточно было просто воспользоваться CSS-селектором :empty
Что-то я проморгал эту фразу (под спойлером была). Не работает
:empty
в хитрых случаях, насколько я помню. Поэтому пришлось выкинуть это решение в реальном похожем кейсе. Кажется дело было в чём-то вродеdisplay: none
или типа того. Но судя по комментариям то же самое и с пробелами.justboris Автор
26.07.2021 21:13+1Про empty:
Такой подход прекрасно попадает под pareto principle – работает для большинства случаев с минимальным оверхедом. Там где не проткатит – так уж и быть, придется расчехлять свои компоненты и передавать явный null вместо неявного.
Про мемоизацию:
В общем тут нужно видеть код.
В общем-то в демке я почти весь код и показал, кроме разве что обработчиков событий и стилей.
Понимаю, что при усложнении кода тут может оказаться проблема (а может и нет, кто же знает). Но фиксить сейчас не существующую проблему – это классическая преждевременная оптимизация
Neki
27.07.2021 01:26+1Не в обиду автору будет сказано, но статья конечно из разряда «Какой я молодец, как элегантно поправил глупые ошибки» )
А вот итоговые советы очень даже полезные. Плюсую. Ещё бы они находили тех, кому адресованы. А не тем у кого и так наболели.
Я наверное скоро трёхтомник «абсурдных кусков кода» кода смогу издать. И приведённые тут примеры, далеко не верх безрассудства. Бывает на code review и смеяться хочется и плакать.
Тут главное не забывать к своим творениям относиться так же критично, как к чужим. Я вот уже сколько лет этим занимаюсь, а периодически ловлю себя на попытке нарушить свои же правила.
Да и вообще, бывает даже самые матёрые ребята пишут глупости. Я убеждён, что жёсткая архитектура, и грамотное выделение шаблонных моментов - залог здорового кода)
nin-jin
27.07.2021 05:48Вы считаете нормальным решением отрендерить лишний дом элемент, чтобы его не показывать?
Вы считаете нормальным потратить в каждом компоненте 1/16 от фрейма анимации впустую?
Neki
27.07.2021 09:18+2Думаете это нормально заливать 92 бензин?
Ну а серьезно, я считаю что нормальным оценивать каждый случай отдельно.
Если ваш заказчик хочет "большой долгоиграющий проект" и готов оплачивать качество человеко-часов. То может быть и правда стоит искать каждый скрытый в DOM элемент и оптимизировать ресурсы, затрачиваемые на его отрисовку. Тут все еще очень сильно зависит, от того насколько корректно Вы можете обрисовать заказчику ситуацию.
Но если Ваш заказчик хочет простенький сайт визитку, и для его бизнеса важны каждые 100 рублей. Думаю ваше усердие не будет оценено должным образом.
В общем ничего нового. Каждой задаче - свое решение. Что не означает, что не нужно работать над своей квалификацией.Умеете писать код - это хорошо.
Умеете писать качественный код - просто отлично.
Умеете писать код, цена/качество которого соответствует потребностям - Вы великолепны
nin-jin
27.07.2021 10:36Чисто там, где не сорят, а не там, где своё свинство оправдывают отсутствием времени
Neki
27.07.2021 12:43+1Что же Вы думаете, что плохой код пишут осознанно, чтобы навредить проекту? Разработчики получает моральное удовлетворение, когда считаю написанный код качественным.
"Просто сделать - сложно, а сложно просто"
Тешить свое самолюбие за деньги заказчика, разве не свинство?
nin-jin
28.07.2021 06:20"Просто сделать - сложно, а сложно просто"
Это особенность практически исключительно только Реакта.
Neki
28.07.2021 08:13+1“Управление сложностью — самый важный технический аспект разработки ПО. По-моему, управление сложностью настолько важно, что оно должно быть Главным Техническим Императивом Разработки” (с) Стив Макконнелл.
Но дело все, конечно, в реакте)
nin-jin
28.07.2021 08:30+1В Реакте как раз управление сложностью и запороли. Но на приветмирах это не заметно.
justboris Автор
27.07.2021 12:29+2Вы считаете нормальным решением отрендерить лишний дом элемент, чтобы его не показывать?
Нет, золотая рыбка, $mol я использовать не буду
Вы считаете нормальным потратить в каждом компоненте 1/16 от фрейма анимации впустую?
А к какому случаю из статьи это относится?
nin-jin
28.07.2021 06:33-1Раз уж вы вспомнили про $mol, то на нём убирание футера из рендеринга делается просто:
Foot() { return this.foot().length ? super.Foot() : null }
Это самое простое и очевидное решение. При этом всё будет корректно мемоизировано и реактивно обновляться. Но это же скучно. Куда интересней создавать себе и другим проблемы на ровном месте и писать статьи про 100500 хаков от useEffect до css:empty.
1/16 фрейма - это как раз 1 миллисекунда. 16 таких компонент, "которым оптимизация не нужна" и вы гарантированно получаете просадку fps.
justboris Автор
28.07.2021 09:371/16 фрейма - это как раз 1 миллисекунда. 16 таких компонент, "которым оптимизация не нужна" и вы гарантированно получаете просадку fps.
Это в случае, если оптимизация гарантированно улучшает ситуацию. На самом деле этого нет. Моя демка показывает, что цифры там и там одинаковые
nin-jin
28.07.2021 10:52Цифры одинаково отвратительные, так как мемоизация фактически не работает. Нормальная мемоизация выглядит как-то так:
jowoodik
27.07.2021 18:47Прикольная статейка, заставляет остановиться и задуматься а туда ли мы все бежим…
Но как по мне это просто вопрос в глубине изучения, сам же автор пишет что разработчиков «не научили в своё время». Просто может «это» время ещё не настало для конкретных разработчиков. Умение выбирать простое и одновременно эффективное решение приходит с опытом, оно не появляется по умолчанию у разработчиков
omgpiu
28.07.2021 08:57+2Ну давайте, расскажите , кто должен был научить этих «неучей»? В каких университетах учат css, сколько по времени и где учат js ? На курсах? Дак курсы же зашквар, не? А может вы забыли , как сами изучали это все и прыгали на граблях, и так же вас хейтили знатоков джеквери? Рынок вырос, объём знаний вырос . С какой переодичностью выкатывает хром обновления ? Как часто спеки меняются ? Как быстро развивают фреймворки? Сейчас скажут фреймворки не тру, нужно учить ванилу и чистый CSS , а где ее юзать то, эту ванилу и чистый CSS? Сейчас куда не плюнь, фреймворки , препроцессоры, CsS in JS и тд. Этот надменный тон «илиты» раздражает , вы сами нанимали себе работников , вы их собесили. Если вас бесит, что бизнес готов платить людям с неидеальными знаниями то это дело бизнеса, а не ваши. Идите работать в FAANG, там ваши знания и умения оценят по достоинству, если возьмут .
Xuxicheta
31.07.2021 22:46+1На самом деле даже опытные разработчики порой забывают какие-то простейшие вещи, и лиды нередко упускают очевиднейшие решения. Это нормально. А указанные автором вещи в продакшене вообще никто не заметит.
4reddy
28.07.2021 11:55Но некоторые адепты молниеносной разработки незамедлительно кинут неспоримый аргумент, что бизнес не любит ждать. Что скажешь им на это, Борис?
Fodin
03.08.2021 17:47-1Похоже, статья должна называться "Кажется, вы стали забывать основы фронтенда", т.к. "мы" - это "вы плюс я".
V_oron
05.08.2021 17:50На мой взгляд посыл топика так или иначе сводится к пресловутой бритве Оккама: "лишние сущности", KISS, YAGNI и прочее -- всё это сформулировано сотни лет назад :) Поэтому я выражаю несогласие с заголовком "Кажется, мы стали забывать основы фронтенда".
Примеры выше -- это лишь очередные частные случаи (о которых, безусловно, полезно и нужно всё время напоминать!). И эти примеры лишний раз показывают, что во все времена человек остаётся человеком. Со всеми вытекающими особенностями мышления и психики.
Предложенные в посте тезисы применимы к коду на любом языке, к использованию любых инструментов в любой деятельности. Чем сложнее инструмент, тем больше комбинаций его настроек и способов его эксплуатации. Тем сложнее использовать инструмент оптимально.
Поэтому назвать статью можно было бы в духе того, что "Кажется, нам всё сложнее оптимально разрабатывать фронтенд".
И позволю себе небольшое замечание по технической части. На мой взгляд изменение порядка тегов
<script>
и<footer>
-- весьма хрупкое решение. Порядок строк всегда может случайно нарушиться. В коде нет очевидной причины такой конфигурации, как если бы это была, к примеру, цепочка функций видаy = f(x); z = g(y)
. А комментарии, как известно, штука о двух концах. Но я ни разу не front-end'ер. Поэтому, к сожалению, не могу предложить своего решения :(
mSnus
Не понял, а почему в истории №2 вообще считалась высота футера до полной загрузки страницы?
justboris Автор
Имеется в виду почему не было подписки на событие DOMContentLoaded?
Потому что ReactDOM.render обычно синхронно исполняется. Вон и в популярнейшем create-react-app так и сделано. Если перенести код в обработчик, то тоже работать будет.
Spaceoddity
Ну серьёзно? Призываете обращаться к "чистому CSS", а сами через JS растягиваете сайдбар на высоту контента? Футер к низу тоже через JS прибиваете?
Что-то с этим фронтендом полный мрак творится... В современный CSS уже столько возможностей впихнули, что использовать JS для управления представлением надо в каких-то исключительных случаях. Но вместо того, чтобы разобраться хотя бы с азами CSS, падаваны фронтенда элементарный лэйаут страницы обсчитывают на JS... Ну ещё бы - они же там не какие-то тупоголовые верстальщики (как мне недавно заявил один товарищ - "10 лет заниматься примитивной вёрсткой и не вырасти до программиста - это надо совсем тупым быть")...
Clasen01
Это из статьи) как бы вы это при помощи css сделали?
dreesh
есть grid layout, но я не фронтендер)
shybovycha
CSS variables +
calc
? Grid? Flexbox (row + row { column.sidebar + column.content } + row
)?Clasen01
и чем это решение лучше решения на js?
shybovycha
ну вот навскидку:
1. браузер обработает CSS раньше чем JS (в большинстве случаев)
2. может быть частью темы
3. не тащит за собой половину интернета
4. не ломается при отключенном JS в браузере
5. не ломается из-за раздобланного в другом месте JS
6. не вызывает бесконечных перерисовок
lesivOlexandr
Да, я тоже за то чтобы использовать css всегда, когда только можно, но с подход css-переменными не сработает, если высота хедера и футера вычисляется динамически в зависимости от контента, а с флексами и Grid'ами может потребоваться переписывание стилей (разметки?) всех страниц, на которых есть хедер и футер
SelenIT3
В современном CSS у соседних блоков равная высота по умолчанию. Что во флексбоксах, что в гридах. «Что-то делать» надо, если надо что-то другое :)
Spaceoddity
Кхм... Вы сейчас не шутите?
Это можно сделать... ну пятью способами уж точно. Вот самая простая для понимания принципа, реализация на флексбоксах:
Clasen01
По вашему это решение эквивалентно? Оно действительно учитывает высоту шапки и высоту футера?
Spaceoddity
Зачем вам вообще учитывать высоту хэдера и футера?))
https://codepen.io/nanto-work/pen/vYmpaqb
goodMICE
А теперь представьте, что ваша страница длиннее одного экрана
Spaceoddity
И что это изменит? https://codepen.io/nanto-work/pen/vYmpaqb
goodMICE
Теперь всё это не влезает в экран
fishHook
А почему вообще программисты лезут в вёрстку? По мне, это разные профессии. Как оно должно выглядеть: верстальщик создает шаблон страницы, программист пишет скрипты, чтобы заполнить шаблон данными. Если вы поставили перед программистом задачу, то почему вы удивляетесь, что он решает её своими средствами, то есть программированием? Ой, программист не любит CSS, как же так вышло? Так и вышло, что CSS специально создавался для того, чтобы им пользовались непрограммисты. А теперь у вас так получается, что программисты обязаны использовать не свойственные им инструменты и кипите возмущением, когда они это делаеют не достаточно эффективно.
Fr0sT-Brutal
Боюсь, потому, что заказчикам надо "все и сразу и побыстрей а еще нарисуй нам макетик веселенький". В идеальном мире макет, фронт и бэк - это три разных человека (а возможно, БД - еще один), но где он, этот идеальный мир :(
fishHook
Хотят говнокод — получают говнокод, и некого тут обвинять, кроме самих заказчиков.
AndreyHenneberg
И ладно бы, но они же других заставляют (по крайней мере, пытаются заставлять) писать этот самый говонокод. Извините, наболело.
AndreyHenneberg
Если бы они — заказчики — ещё понимали, что это три, а то и четыре, разных человека, а не один. По моим ощущениям, чуть не половина из них верует в Тыжпрограммиста и считает, что программист должен и серверную часть написать, и морду сверстать (неуачо, тыжпрограммист), и эту морду запрограммировать (хорошо, если не спрограммировать, а то и такие бывают), а потом ещё и сервер с нуля настроить.
S-e-n
А что если я всё это могу, если надо (мне самому) будет?
valera545
Как-то давно жена работала дизайнером-верстальщиком в конторе по наружной рекламе. Сделали заказчику макет, он недоволен. Переделали — недоволен. Пришёл сам, начал руководить — всё равно получается не как ему нравится. Заорал "Да у меня племянник на компьютере, дайте мне файл — я ему отдам или сам всё сделаю!"
Отдали ему рабочий файл. День нет, другой, на третий приходит уже тихий: "Надо же, я и не знал, что это так трудно. Сидел-сидел, вертел и так и так — ничего не выходит. Наверное, я чего-то не того хочу." Это, заметим, заказчик умный и честный, а они далеко не всегда такие.
Spaceoddity
Вы вообще не о том. Фронтенд-программист обязан знать CSS. Поскольку его основная задача - "пилить под ключ" всю клиентскую часть. JS изначально был заточен на тесную поддержку CSS. Как без знания CSS можно вообще "делать морды"? В чём тогда обязанности программиста вообще должны заключаться? Взять данные от api и распихать их по структуре?
Ну представим вашу идеальную картину. Верстальщик сделал шаблон, программист заполнил его данными. Бац, в дизайне нужна всплывашка. Кто её будет делать? Верстальщик её сверстает, а программист навесит обработчик, который будет менять класс? А кто этот класс будет прописывать? А если нужна анимация - кто её прописывать должен? А если какие-то манипуляции с DOM? Нестандартные элементы форм? Здесь граница компетенций сейчас настолько размыта... Как по мне, всё это должен делать один специалист - фронтенд-разработчик. И он должен знать CSS. Пусть не на уровне гуру вёрстки, но уж точно не строить лэйаут документа путём обсчёта css-значений через js.
И что это вообще за тезис: "Так и вышло, что CSS специально создавался для того, чтобы им пользовались непрограммисты."? CSS создавался совершенно не для этого. Как и HTML.