Обновить
60
0
Игорь@CodeShaman

Программист

Отправить сообщение

Когда я решил сменить профессию мне было 27. А обучение заняло около 9 месяцев. При этом, как я написал в статье, несколько месяцев у меня были заняты обучением полностью, когда мне отдавали мои переработки. В эти месяцы я учился 7 дней в неделю с перерывами на еду и сон.

Со своей стороны я все же считаю, что функция может быть хуком. Во-первых, в React в первую очередь она мне нужна именно для использования в компонентах. Честно, не очень могу с ходу припомнить, когда последний раз использовал debounce вне компонента. А вот внутри них постоянно. Во-вторых, возможность переложить на хук контроль за состоянием монтирования компонента для отложенной функции — удобный способ переиспользовать стандартное поведение. Именно это и делает функцию хуком в данном случае. Использование возможностей React внутри себя. В данном случае — умение работать с состоянием рендера.


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


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


По поводу хуков debounce. Команда React уже делает свою реализацию в виде хука useDeferredValue. Пока для работы со значениями и в экспериментальном режиме. Но именно в виде хука.


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


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


По поводу проблем с оборачиванием хука в useCallback. Их нет.
Если нужно, можно спокойно написать:


  // Отложенное логирование
  const debouncedValueLogging = useCallback(
    useDebouncedFunction(newValue => valueLogging(newValue), 300),
    []
  );

И это будет прекрасно работать. Визуально эта конструкция полностью эквивалентна использованию debounce() с let внутри. Ну, только название у хука чуточку длиннее. Но это решаемо. :) Про массив зависимостей и предупреждения линтера согласен. Об этом я писал в статье. За этим желательно следить всегда и не пытаться обмануть линтер.

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


Сам React предполагает, что не нужно везде и всегда делать мемоизации. Это подход самого фреймворка. Об этом есть хорошие материалы, например интересная статья: When to useMemo and useCallback. React старается сделать все достаточно быстрым и хорошо работающим в большинстве случаев использования. И до определенного момента нам не нужно думать об этом. Если же мы упираемся в базовые возможности, у нас есть некоторые инструменты по повышению производительности в узких местах. А там уже нужно смотреть и проверять, что и как можно оптимизировать. Если же где-то мы можем использовать useRef при разработке компонента, то для начала стоит попробовать его. В частности, хук в статье следует именно этим принципам.

Очень жалко, что Вы не прочитали статью и комментарии к ней. Многое уже было бы более ясно и какие-то описанные пункты отпали бы сами собой. :)


Теперь давайте поговорим подробно по каждому из пунктов Вашего комментария:


Пункт 1. Как Вы сами заметили во втором своем пункте, если нам нужен доступ к чему-то из текущего экземпляра компонента, мы этот вариант просто не можем сразу использовать, как есть. Нужно делать дополнительные действия и тут можно сразу перейти к пункту 2.


Пункт 2. Сразу отмечу, в чем одно из предназначений хука — нам не нужно разделять случаи использования debounce() и думать о том, используем ли мы что-то из экземпляра компонента, будем ли это потом делать в дальнейшем и как. У нас есть унифицированный подход, который мы можем использовать и в дальнейшем менять с минимальными трудозатратами.


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


То есть, можно написать так:


const debouncedFoo = useCallback(debounce(foo, timeout), [])

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


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


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


Если же нам нужна ссылочная стабильность в каком-то конкретном случае, мы используем useCallback() и это нормально. Именно по этой причине сам хук не определяет за нас, нужно ли нам это или нет. По опыту использования могу сказать, что очень часто функции обернутые в debounce() этой ссылочной стабильности не требовали.


Для примера давайте взглянем даже на код в статье. Функции обернутые в debounce() используются внутри функции-хэндлера handleChange(). То есть в первую очередь нам нужно сделать handleChange()ссылочно-стабильной. Именно ее мы передаем в пропс onChange. Для этого мы обернем ее в useCallback, обернем все наши отложенные функции в useCallback.


И тогда мы получим… а что же мы получим? А по сути мы не получим ничего. Единственное, для чего нам нужно была бы в этом случае ссылочная стабильность — это предотвратить ненужные ререндеры компонента, куда мы передали в пропсах нашу handleChange(). Но вот засада, тут все равно будет происходит ререндер при каждом движении ползунка, потому что нам нужно пересовать компонент слайдера из-за измененного значения положения ползунка. Сам родительский компонент RangeSlider не предполагает, что он будет отрендерен таким образом, что это не потребует ререндера нашего слайдера.


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


Пункт 3. Думаю здесь ответ дан в предыдущем пункте.


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


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


Предложенное Вами решение имеет место быть. Но это не эквивалентные решения с разным подходом. И это нужно понимать.

Компонент слайдера из material-ui взят исключительно для наглядности примера. Это может быть и созданный кем-то другим слайдер, без подобного свойства, как onChangeCommitted. И у onChangeCommitted все таки немного другая логика работы. Он реагирует на событие mouseup и после него вызывает callback-функцию. И тут уже нужно смотреть, устраивает нас именно такое поведение или мы хотим реагировать в том числе и на остановку движения ползунка на длительный промежуток. В данном случае учебный пример подразумевает, что мы хотим реагировать на остановки ползунков.

Самостоятельная реализация таких вещей очень хорошо прокачивает понимание и навык. Полностью согласен.


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

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


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


Как известно, React и его создатели очень много вдохновения черпают в OCaml и любят функциональный подход. В обсуждениях вопроса о том, на сколько функциональны хуки сломано не мало копий. Кто-то называет это чистой процедурщиной. Кто-то с этим не согласен. Уходить в эти дебри сразу с самого начала объяснения хуков — точно не стоит.
Как говорил Кхал Дрого из известного сериала: "Это вопрос для мудрых мужчин с тощими руками." :)


Мне кажется, для начального понимания хуков нужно еще раз освежить в голове изучающих такую концепцию, как композиция. С этой точки зрения хуки — очень понятная идея. Мы берем функцию и в нее страиваем другие функции. Или выносим часть переиспользуемого функционала в отдельные хуки. По сути те же функции, но с особенностями. А их в свою очередь переиспользуем в других функциях. Хуки, в свою очередь, отличаются от обычных функций тем, что могут использовать внутренние механизмы React.


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


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

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


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

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


По поводу useCallback полностью согласен. Как и в том, что операция действительно не бесплатная сама по себе. Не добавлять useCallback в этом случае полностью осознанное решение. Этот момент я не стал упоминать в этой статье еще и по той причине, что объем показался итак достаточно значительным и не хотелось перегружать материал еще и хуком useCallback. Хотелось поговорить об этом отдельно чуть позже и более подробно. Спасибо за комментарий.

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

Да, конечно. Вы правы. Это я не поправил в переводе, хотя нужно было. Диапазон будет до 20px. Еще одна ошибка автора оригинальной статьи. Исправлю, спасибо!

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


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


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


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


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


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


Я не призываю этот кейс игнорировать, но не нужно из него делать такую проблему. Реальность такова, что в данный момент в интернете большинство крупных порталов не думают об этом. Даже Хабр об этом не думает, где мы все постоянно читаем статьи. Просто как факт.
Опять же, хорошо это, плохо ли. Я не могу это сказать категорично, у меня нет данных для подобного утверждения. Сколько это процентов их пользователей? Насколько это вообще значимое число от аудитории портала? Какое число в среднем по интернету пользуется этой опцией? Просто говорить, что все эти сайты разрабатывают недалекие и некомпетентные люди, которые по глупости своей игнорируют проблему, я тем более говорить не стану. И если бы это стояло так остро, то ситуация по этому поводу явно звучала бы чаще, в том числе и на хабре. И определенно такие крупные сайты что-то бы да сделали с этой ситуацией, чтобы не терять значительную часть своей аудитории. Все таки ради каких-то единичных процентов даже IE поддерживают и его пользователей.

Начну с конца. Во первых масштабирование жестом никуда не пропадает не только на мобильном браузере, но и, допустим, на Maс OS при приближении жестом на тачпаде. Речь идет только про приближение через ctrl + (command +). И оно не плывет, а изменяет поведение. Если кто-то считает, что для его пользователей это очень нужная и необходимая функция, то не стоит эту технику использовать. Об этом прямо написано.


Во вторых про "перестать работать". Я не очень представляю, как это вообще возможно. Какие-то браузеры внезапно откажутся от поддержки функции calc или vw? С тем же успехом можно говорить, что тогда браузеры внезапно откажутся поддерживать rem, em или скажем функцию hsl. Этот утверждение выглядит очень надуманным.


Ну и в третьих, как я и написал в статье, даже Хабр многое решает за пользователя. В данном случае, какой шрифт будет у него на странице. Как он принял это решение (по фазе луны или по сиюминутному порыву дизайнерского вдохновения) — полностью на его совести. Хорошо это или плохо, каждый решает сам. Я не хочу выносить категоричное суждение на этот счет. Слишком огромно количество действительно крупных и реально хороших проектов, которые поступают так же. В данный момент на просторах интернета это очень распространенная ситуация.

Правильно ли я понял, что имеется ввиду настройка (например) в настольной версии гугл хром: Внешний вид -> размер шрифта? Если да, то к сожалению ее игнорирует хабр, на котором мы сейчас находимся. Или допустим возьмем такой сайт, как google. Попробуйте поставить очень крупный шрифт в настройках и перейти на google.com. На основной странице вы увидите увеличенные буквы, но как только вы наберете что-то в поиске и нажмете enter, страница с выдачей окажется игнорирующей эту настройку. Если пойти еще дальше и перейти в Настроить шрифты -> Размер шрифта и там выставить максимальные 72, то страница с выдачей поведет себя еще интересней. Сами шрифты проигнорируют эту настройку, а вот все расстояния на странице будут сильно увеличены.


Там есть еще одна настройка — Минимальный размер шрифта. На нее будет реагировать и хабр и сайт с этой техникой. Если указанный в ней минимальный размер шрифта будет больше, чем тот, что на посещаемом сайте. Но если размер шрифта на сайте больше, то хабр это тоже проигнорирует. Как и гугл. Но это логично исходя из названия настройки.

Пожалуйста! Приятно, что статья приносит пользу читателям.

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

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

Но какое это имеет отношение к системным настройкам шрифта в системе пользователя? Возьмём к примеру IOS и системные настройки Text Size. Изменение системного Text Size в любую сторону никак не влияет на размеры шрифтов на веб-страницах в браузерах IOS.

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

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

Информация

В рейтинге
Не участвует
Откуда
Москва, Москва и Московская обл., Россия
Работает в
Дата рождения
Зарегистрирован
Активность