Перейти к содержимому

Блог

Tag b or not b...

I’ve been refactoring our codebase, and it seems like my colleagues from the past were very fond of the <b> tag. They’ve used it a lot. I am not a big fan, especially of code like this:

.component b {
font-weight: normal;
}

But maybe there is some reason why these guys applied it everywhere? Let’s research a little!

<b> now

I knew that <b> was for Bold. As I found out, now this is not the case. The HTML5 specification says:

The b element represents a span of text to which attention is being drawn for utilitarian purposes without conveying any extra importance and with no implication of an alternate voice or mood, such as key words in a document abstract, product names in a review, actionable words in interactive text-driven software, or an article lede.

And adds:

The b element should be used as a last resort when no other element is more appropriate.

For me, it is a really confusing tag now: there are some ideas about appearance (and browsers support it), but <b> is not about appearance. Issues with this tag are presented in the W3C article “Using <b> and <i> elements”.

I plan to get rid of <b> in most places of my code and try to use h1-h6, em, strong or mark — a pretty wide choice!

Когда ре-рендер нужен, но никак не случается

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

Итак, что было? В нашем App.tsx:

<Router history={history}>
<UuiContext.Provider value={services}>
<Header />
<Routes />
<Footer />
</UuiContext.Provider>
</Router>

В Header.tsx — навигация:

// компонент из библиотеки, у него есть подсветка активного пункта меню.
// заглядывает в uuiContext от UuiContext.Provider и проверят, активная ли линка через history
<MainMenu>
<MainMenuButton caption='Home' link={{pathname: '/' } />
<MainMenuButton caption='Dashboard' link={{pathname: '/dashboard' } />
</MainMenu>

И вроде всё (ну, почти всё) работает: по кликам в MainMenu урл обновляется, контент перерисовывается. Вот только нет выделения активного элемента в MainMenu.

Начинаю разбираться, почему — ставлю точку остановы в фукнцию в MainMenu.tsx, где определяется, активная ли линка. И тут начинается интересное: при переходе по кнопкам и обновлении урла функция не вызывается.

Добавляю console.log(‘render’) в Header.tsx. Что ж, он не пере-рендеривается, так что логично, что ничего не происходит. Но ведь мы же вроде обновляем историю, а в uuiContext.uuiRouter.history — как раз всё обновляется… Почему же дочерние элементы UuiContext.Provider не обновляются?

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

После блуждания по контекстам, в мою голову пришла мысль: содержимое страницы обновляется за счет изменения актуального Route — поэтому часть в Routes перерендеривается нормально. Что, если Header положить внутрь каждой страницы, соответствующей роуту? Это помогло, однако в таком случае Header каждый раз при изменении роута unmount и mount. Выглядит некрасиво и излишне.

И вот тут наконец хороший вопрос пришел в голову: как React Router определяет, что надо что-то пере-рендерить? И вот что я выяснила:

  • мы обновляем history
  • history object имеет постоянную ссылку
  • useHistory поэтому все время будет возвращать один и тот же объект, поэтому useEffect c history.location не отработает при изменении location
  • зато вот useLocation и useParams следят за изменениями локации, поэтому как только мы их используем — наш компонент начинает пере-рендериваться при изменениях

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

Миф про ре-рендеры

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

<Component levels={[1, 2, 3]} />

Ведь массив будет пересоздаваться в JSX при каждом ре-рендере, то есть пропсы будут меняться и запускать ненужные ре-рендеры Component?

И вы наверняка слышали, что нужно использовать useMemo (const levels = useMemo(() => [1, 2, 3], [])) или объявить массив вне компонента?

Я слышала это много раз. И следовала этому принципу.

Даже когда я столкнулась с кейсами, что при передаче в пропсы ref объекта и обновлении этого объекта потом — компонент не перерисовывается, я всё равно продолжала избегать передачи вот таких массивов и объектов как пропсов. Я просто не соединила в своей голове эти две простые вещи: если обновление пропсов должно приводить к ре-рендеру, тогда это должно работать и с ref, не так ли?

Сейчас я читалю Advanced React от Nadia Makarevich, и автор пишет, что одно из главных заблуждений:

Миф про ре-рендеры: Компонент перерисовывается, когда изменились пропсы.

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

В контексте ре-рендеров, изменились пропсы компонента или нет важно только в одном случае: если этот компонент обернут в HOC React.memo.

Посмотрите на примере с кодом: https://codesandbox.io/p/sandbox/props-rerender-dcq97c.

И всё! Нужно поменять способ мышления — перенести фокус с изменения пропсов на анализ изменений стейта.

P.S. Сама я для константных объектов / массивов предпочитаю создавать constants.ts и объявлять их там, но это дело личного вкуса и никак не связано с ре-рендерами.

Любите решать задачи на интервью?

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

Поэтому когда я увидела афишу лекции «Красивые математические задачи с айтишных собеседований» с преподавателем математики Бориса Трушина (известен своим youtube-каналом, где доходчиво объясняет математику), собралась и пошла.

Было интересно, разобрали 7 задач (парочка из них никогда не была замечена на собеседованиях, но и пусть). Записываю задачи и то, что может помочь найти решение в других задачах будущей мне :)

Красные и белые колпаки

Царь решил сыграть в игру со своими мудрецами: собрал 30 мудрецов, на каждого надел красный или белый колпак. Каждый мудрец видит все остальные колпаки, но не знает цвета своего колпака. Мудрецы не могут никак подавать сигналы другим о цвете их колпаков. Они сидят в комнате, и каждую минуту туда заходит проверяющий: как только хоть один мудрец правильно назовет цвет своего колпака — все будут свободны. Если называешь неправильно — казнь. Если ни один мудрец не назовет правильно цвет своего колпака в течение дня — все будут казнены.

Пока мудрецы сидели, мимо двери проходил человек и сказал: “О, среди вас как минимум 1 белый колпак!”. Через какое-то время все люди в белых колпаках встали и правильно назвали свой цвет.

Как так получилось?

Подход

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

Обращаем внимание на то, что поменяло общее знание (когда проходящий сказал, что есть как минимум 1 белый колпак).

Когда разобьётся яйцо?

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

(если яйцо не разбилось — его можно продолжать использовать; если разбилось — нельзя использовать, естественно)

Подход

Когда одно яйцо разбилось, вторым мы можем только перебирать подряд этажи. Можно попробовать разделить более-менее равномерно этажи на одинаковые диапазоны, и так проверять.

Но при одинаковом диапазоне (проверили сначала n, затем 2n, затем 3n) — для второго яйца количество попыток не сокращается (n - 1). Это хотелось бы оптимизировать.

Опять пробуем представить идеальный вариант: в последней попытке нам нужно проверить 1 этаж, и дальше уже от этого думаем.

Как доехать с пустым баком?

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

Машина с пустым баком на старте. Мы можем заглянуть во все канистры.

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

(очевидно, заливая бензин из всех встреченных по пути канистр)

Подход

Рекомендуется рисовать. Я в такие задачки не очень умею, если что, объяснение вот здесь.

Бесконечный поезд

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

Подход

Объяснение, а вообще снова решаем для одного вагона, потом для двух, трех и тд. Надо придумать, что станет для нас маркером посещенности вагона.

Тюрьма и лампочка

В тюрьме 100 заключённых сидят по одиночным камерам. Начальник тюрьмы решил организовать такую игру. Их по одному иногда будут приводить в комнату, где нет ничего, кроме одной лампочки, которую заключённому разрешается включить или выключить. Гарантируется, что рано или поздно каждый из заключенных побывает в комнате с лампочкой сколько угодно раз. В любой момент заключённый, приведённый в комнату с лампочкой, может объявить, что все заключенные уже побывали в комнате хотя бы по одному разу. Если он прав, то всех отпустят, если нет — казнят. Заключенных собрали вместе в этой комнате, объявили правила, разрешили договориться, придумать стратегию, и, уходя, оставить выключатель в том положении, в котором захотят. Придумайте стратегию, которая позволит им освободиться

Подход

Объяснение, две вещи над которым надо подумать:

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

Как у вас с памятью?

Часто ли вы занимаетесь утечками памяти? Честно говоря, я — нет, но тема мне интересна. Каждый раз, когда я копаюсь в проблемах, связанных с памятью в веб-приложениях, я обнаруживаю, что надо задействовать мозг по максимуму: вспомнить, как устроена работа с памятью в JS, разобраться со всеми этими данными из Memory вкладки в DevTools, четко представлять, какие данные и когда нужны для приложения.

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

Посмотрите Memory Leaks в гайдах — это отобранные ссылки со статьями, как работает GC, как лучше отлаживаться в DevTools. Также планирую со временем добавить практические советы.