Когда я решил сменить профессию мне было 27. А обучение заняло около 9 месяцев. При этом, как я написал в статье, несколько месяцев у меня были заняты обучением полностью, когда мне отдавали мои переработки. В эти месяцы я учился 7 дней в неделю с перерывами на еду и сон.
Со своей стороны я все же считаю, что функция может быть хуком. Во-первых, в React в первую очередь она мне нужна именно для использования в компонентах. Честно, не очень могу с ходу припомнить, когда последний раз использовал debounce вне компонента. А вот внутри них постоянно. Во-вторых, возможность переложить на хук контроль за состоянием монтирования компонента для отложенной функции — удобный способ переиспользовать стандартное поведение. Именно это и делает функцию хуком в данном случае. Использование возможностей React внутри себя. В данном случае — умение работать с состоянием рендера.
Когда я говорил о мемоизации, я имел ввиду мемоизацию хука из примера. Ваш пример работает именно благодаря ней. Хук из статьи работает и без нее. Как если бы мы могли сделать вызов функции debounce() в теле компонента без каких либо дополнительных телодвижений. Мемоизацию можно сделать дополнительно сверху. Но только в случаях, когда нам будет нужна ссылочная стабильность.
С точки зрения производительности не думаю, что хук из статьи или Ваше решение хоть как-то значимо отличаются, чтобы нужно было об этом переживать.
По поводу хуков debounce. Команда React уже делает свою реализацию в виде хука useDeferredValue. Пока для работы со значениями и в экспериментальном режиме. Но именно в виде хука.
Как я и сказал, Ваше решение имеет место быть. Но это другое решение, с другим подходом. Мне удобно, когда хук сам проследит за отменой вызова и мне не придётся каждый раз писать бойлерплейт код для контроля за состоянием монтирования. И мне удобно, что я могу сразу вызвать функцию в любом месте компонента и предварительно не оборачивать ее в вызов useCallback. Для меня так код становиться немного чище и проще. Это опциональное действие, которое остается на мое усмотрение.
По поводу размонтирования, синхронных вызовов и проблем в дизайне. Только переданный в debounce() коллбэк синхронный. А вот его вызов уже становится асинхронным по своей сути, через переданную задержку отложенного вызова. И за эту задержку пользователь может успеть всякое, особенно если мы ставим более или менее значительную задержку по каким-то причинам. Хорошим тоном будет всегда следить за таким отложенным вызовом и отменять его при размонтировании.
По поводу проблем с оборачиванием хука в useCallback. Их нет.
Если нужно, можно спокойно написать:
И это будет прекрасно работать. Визуально эта конструкция полностью эквивалентна использованию debounce() с let внутри. Ну, только название у хука чуточку длиннее. Но это решаемо. :) Про массив зависимостей и предупреждения линтера согласен. Об этом я писал в статье. За этим желательно следить всегда и не пытаться обмануть линтер.
И еще хотелось бы добавить по поводу мемоизации. В случае с ней, я стараюсь придерживаться известного принципа: «преждевременная оптимизация — корень всех зол». Если при работе приложения и его компонентов не возникает реальных проблем с производительностью, то я никогда заранее не использую техники мемоизации. Это также позволяет не писать лишний код и не добавлять сложности в текущую логику работы компонентов. Их использованию всегда предшествует какой-то реальный практический кейс, который явно влияет на производительность. Кейс, который можно измерить и сравнить с работой после мемоизации. И если разница действительно ощутима и имеет практическую пользу – тогда можно посмотреть в сторону мемоизации. Это очень часто можно увидеть еще на раннем этапе разработки. В частности, со случаями, когда хорошо видно, что ссылочная стабильность поможет нам избежать лишних затратных вычислений и отрисовок. Иногда эти техники помогают улучшить уже работающие части.
Сам React предполагает, что не нужно везде и всегда делать мемоизации. Это подход самого фреймворка. Об этом есть хорошие материалы, например интересная статья: When to useMemo and useCallback. React старается сделать все достаточно быстрым и хорошо работающим в большинстве случаев использования. И до определенного момента нам не нужно думать об этом. Если же мы упираемся в базовые возможности, у нас есть некоторые инструменты по повышению производительности в узких местах. А там уже нужно смотреть и проверять, что и как можно оптимизировать. Если же где-то мы можем использовать useRef при разработке компонента, то для начала стоит попробовать его. В частности, хук в статье следует именно этим принципам.
Очень жалко, что Вы не прочитали статью и комментарии к ней. Многое уже было бы более ясно и какие-то описанные пункты отпали бы сами собой. :)
Теперь давайте поговорим подробно по каждому из пунктов Вашего комментария:
Пункт 1. Как Вы сами заметили во втором своем пункте, если нам нужен доступ к чему-то из текущего экземпляра компонента, мы этот вариант просто не можем сразу использовать, как есть. Нужно делать дополнительные действия и тут можно сразу перейти к пункту 2.
Пункт 2. Сразу отмечу, в чем одно из предназначений хука — нам не нужно разделять случаи использования debounce() и думать о том, используем ли мы что-то из экземпляра компонента, будем ли это потом делать в дальнейшем и как. У нас есть унифицированный подход, который мы можем использовать и в дальнейшем менять с минимальными трудозатратами.
В Вашем примере useMemo используется не для того, для чего он предназначен. useMemo нужен мемоизации тяжелых вычислений и оптимизации производительности. В данном случае семантически правильно использовать useCallback и к тому же нам не придётся делать дополнительную обертку над debounce().
Теперь сравним, что же мы получаем. С хуком в статье мы имеем решение в одну строчку кода, которая декорирует нашу функцию и сразу дает на выходе тот результат, который нам нужен.
В предложенном варианте нам нужно каждый раз оборачивать в 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.
Хороший и правильный вопрос. Это как раз из тех моментов, о которых стоит помнить при использовании этой техники или любой другой техники отзывчивых шрифтов. В моем случае все свёрстано таким образом, что вереска не едет, но все увеличивается пропорционально изменившемуся масштабу. Для моего приложения и моих пользователей это приемлемое поведение, но если вы считаете, что ваши пользователи будут пользоваться увеличением экрана, то возможно эта адаптивная техника не подходит. Постараюсь подробно описать в следующей статье в больших деталях. Спасибо за вопрос.
Когда я решил сменить профессию мне было 27. А обучение заняло около 9 месяцев. При этом, как я написал в статье, несколько месяцев у меня были заняты обучением полностью, когда мне отдавали мои переработки. В эти месяцы я учился 7 дней в неделю с перерывами на еду и сон.
Со своей стороны я все же считаю, что функция может быть хуком. Во-первых, в React в первую очередь она мне нужна именно для использования в компонентах. Честно, не очень могу с ходу припомнить, когда последний раз использовал
debounceвне компонента. А вот внутри них постоянно. Во-вторых, возможность переложить на хук контроль за состоянием монтирования компонента для отложенной функции — удобный способ переиспользовать стандартное поведение. Именно это и делает функцию хуком в данном случае. Использование возможностей React внутри себя. В данном случае — умение работать с состоянием рендера.Когда я говорил о мемоизации, я имел ввиду мемоизацию хука из примера. Ваш пример работает именно благодаря ней. Хук из статьи работает и без нее. Как если бы мы могли сделать вызов функции
debounce()в теле компонента без каких либо дополнительных телодвижений. Мемоизацию можно сделать дополнительно сверху. Но только в случаях, когда нам будет нужна ссылочная стабильность.С точки зрения производительности не думаю, что хук из статьи или Ваше решение хоть как-то значимо отличаются, чтобы нужно было об этом переживать.
По поводу хуков
debounce. Команда React уже делает свою реализацию в виде хука useDeferredValue. Пока для работы со значениями и в экспериментальном режиме. Но именно в виде хука.Как я и сказал, Ваше решение имеет место быть. Но это другое решение, с другим подходом. Мне удобно, когда хук сам проследит за отменой вызова и мне не придётся каждый раз писать бойлерплейт код для контроля за состоянием монтирования. И мне удобно, что я могу сразу вызвать функцию в любом месте компонента и предварительно не оборачивать ее в вызов
useCallback. Для меня так код становиться немного чище и проще. Это опциональное действие, которое остается на мое усмотрение.По поводу размонтирования, синхронных вызовов и проблем в дизайне. Только переданный в
debounce()коллбэк синхронный. А вот его вызов уже становится асинхронным по своей сути, через переданную задержку отложенного вызова. И за эту задержку пользователь может успеть всякое, особенно если мы ставим более или менее значительную задержку по каким-то причинам. Хорошим тоном будет всегда следить за таким отложенным вызовом и отменять его при размонтировании.По поводу проблем с оборачиванием хука в
useCallback. Их нет.Если нужно, можно спокойно написать:
И это будет прекрасно работать. Визуально эта конструкция полностью эквивалентна использованию
debounce()с let внутри. Ну, только название у хука чуточку длиннее. Но это решаемо. :) Про массив зависимостей и предупреждения линтера согласен. Об этом я писал в статье. За этим желательно следить всегда и не пытаться обмануть линтер.И еще хотелось бы добавить по поводу мемоизации. В случае с ней, я стараюсь придерживаться известного принципа: «преждевременная оптимизация — корень всех зол». Если при работе приложения и его компонентов не возникает реальных проблем с производительностью, то я никогда заранее не использую техники мемоизации. Это также позволяет не писать лишний код и не добавлять сложности в текущую логику работы компонентов. Их использованию всегда предшествует какой-то реальный практический кейс, который явно влияет на производительность. Кейс, который можно измерить и сравнить с работой после мемоизации. И если разница действительно ощутима и имеет практическую пользу – тогда можно посмотреть в сторону мемоизации. Это очень часто можно увидеть еще на раннем этапе разработки. В частности, со случаями, когда хорошо видно, что ссылочная стабильность поможет нам избежать лишних затратных вычислений и отрисовок. Иногда эти техники помогают улучшить уже работающие части.
Сам React предполагает, что не нужно везде и всегда делать мемоизации. Это подход самого фреймворка. Об этом есть хорошие материалы, например интересная статья: When to useMemo and useCallback. React старается сделать все достаточно быстрым и хорошо работающим в большинстве случаев использования. И до определенного момента нам не нужно думать об этом. Если же мы упираемся в базовые возможности, у нас есть некоторые инструменты по повышению производительности в узких местах. А там уже нужно смотреть и проверять, что и как можно оптимизировать. Если же где-то мы можем использовать
useRefпри разработке компонента, то для начала стоит попробовать его. В частности, хук в статье следует именно этим принципам.Очень жалко, что Вы не прочитали статью и комментарии к ней. Многое уже было бы более ясно и какие-то описанные пункты отпали бы сами собой. :)
Теперь давайте поговорим подробно по каждому из пунктов Вашего комментария:
Пункт 1. Как Вы сами заметили во втором своем пункте, если нам нужен доступ к чему-то из текущего экземпляра компонента, мы этот вариант просто не можем сразу использовать, как есть. Нужно делать дополнительные действия и тут можно сразу перейти к пункту 2.
Пункт 2. Сразу отмечу, в чем одно из предназначений хука — нам не нужно разделять случаи использования
debounce()и думать о том, используем ли мы что-то из экземпляра компонента, будем ли это потом делать в дальнейшем и как. У нас есть унифицированный подход, который мы можем использовать и в дальнейшем менять с минимальными трудозатратами.В Вашем примере
useMemoиспользуется не для того, для чего он предназначен.useMemoнужен мемоизации тяжелых вычислений и оптимизации производительности. В данном случае семантически правильно использоватьuseCallbackи к тому же нам не придётся делать дополнительную обертку надdebounce().То есть, можно написать так:
Теперь сравним, что же мы получаем. С хуком в статье мы имеем решение в одну строчку кода, которая декорирует нашу функцию и сразу дает на выходе тот результат, который нам нужен.
В предложенном варианте нам нужно каждый раз оборачивать в
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.
Хороший и правильный вопрос. Это как раз из тех моментов, о которых стоит помнить при использовании этой техники или любой другой техники отзывчивых шрифтов. В моем случае все свёрстано таким образом, что вереска не едет, но все увеличивается пропорционально изменившемуся масштабу. Для моего приложения и моих пользователей это приемлемое поведение, но если вы считаете, что ваши пользователи будут пользоваться увеличением экрана, то возможно эта адаптивная техника не подходит. Постараюсь подробно описать в следующей статье в больших деталях. Спасибо за вопрос.
Скажу только, что в моей ситуации такой подход я применить не смогу точно. Подробности постараюсь описать в следующей публикации.