tgindex
Катерина | Про Frontend

Катерина | Про Frontend

Статистика

⚡️ Пишу обучающие статьи по Frontend-разработке; ⚡️ Делюсь личным опытом, как профессионально, так и в личном формате. YouTube: https://www.youtube.com/@katerina_profrontend Life: https://t.me/life_nanivskaya Связь: @katrin_nanivskaya

Последний пост
22 февр.
Последнее чтение
15:14
Постов за неделю
0
Всего постов
91
Тип
открытый
Язык
русский
Категория
Видео
В каталоге с
12 авг.
Подписчики
2 913
−10 за 4 дн.
Сутки
−3
−0,10%
Неделя
 
Месяц
 
Просмотров на пост
1 490
40 постов
Вовлечённость
51,2%
к подписчикам
Постов в день
0,0
всего 91
Упоминаний
2
каналов
Охват размещения
оценка
1/24сутки в ленте
1/48двое суток
1/72трое суток

Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.

Посты

  • 22 февр.2 3924516

    Когда height: 100% чуть-чуть поломало вёрстку 🙈 Я делала простую сетку: контейнер display: grid, строки — grid-template-rows: repeat(n, auto), в одной ячейке был элемент, которому я прописала height: 100%, чтобы он «растянулся красиво». В Chrome всё было отлично, а вот в Safari макет разъехался. ☹️ Сначала показалось: «ну вот, опять баг браузера». Но если копнуть глубже, окажется, что никакой мистики: всё упирается в одно правило CSS. Что же случилось? height: 100% — это 100% от высоты родителя. И здесь есть ключевой момент: процентная высота работает только если у родителя есть определённая высота — то есть та, которую можно вычислить без учёта содержимого. А что у нас? Auto-строка в grid как раз получает свою высоту по содержимому во время layout, поэтому её высота заранее неизвестна. В итоге получаем цикл: ✔️ строка ждёт размера ребёнка, ✔️ ребёнок просит 100% от строки, ✔️ но строка ещё не знает своей высоты — и всё зависает. ➡️ Проценту просто не от чего считаться. Почему в разных браузерах результат отличается? Разные движки могут по-разному разрешать такие размерные циклы. В одних случаях результат выглядит «нормально», в других — проблема становится заметной. Как итог, это не баг конкретного браузера. Это различия реализации алгоритма вычисления размеров в ситуациях, где возникает зависимость. А как же решить эту проблему? Нужно дать проценту опору — то есть сделать высоту родителя определённой. 👍

  • 20 февр.1 8403515

    Разбираемся с Proxy в JavaScript 👨‍🏫 Proxy — посредник между кодом и объектом. Он перехватывает операции (чтение, запись, вызов, перечисление) и даёт тебе шанс изменить реакцию на эти действия — через набор обработчиков (так называемых ловушек) const p = new Proxy(target, handler); Как же это работает? 1️⃣ Создаёте реальный объект target; 2️⃣ Пишете handler — объект с функциями-перехватчиками (get, set, apply и т.д.); 3️⃣ new Proxy(target, handler) возвращает прокси: при любой операции сначала вызывается соответствующая ловушка, и она решает — выполнить стандартное действие (через Reflect) или изменить его; Ключевые моменты: ⏺️ get — перехватывает чтение свойства; ⏺️ set — перехватывает запись и может валидировать/блокировать значение; ⏺️ apply — оборачивает вызов функции; ⏺️ ownKeys/getOwnPropertyDescriptor — влияют на Object.keys и сериализацию. Небольшой пример: const obj = { name: "Катя" }; const p = new Proxy(obj, { get(t, prop){ return prop in t ? Reflect.get(t, prop) : "—нет—"; }, set(t, prop, val){ if(prop === "age" && typeof val !== "number") throw TypeError("age must be number"); return Reflect.set(t, prop, val); } }); Этот Proxy делает объект «умным»: при чтении возвращает значение по умолчанию для отсутствующих свойств, а при записи проверяет, чтобы age был числом. Что важно помнить? ✔️ Не скрывайте non-configurable свойства и не добавляйте поля в нерасширяемый объект — движок бросит TypeError; ✔️ Используйте Reflect.* для корректного делегирования (особенно для геттеров/прототипов); ✔️ Избегайте Proxy в «горячих» циклах — это может снизить производительность. В итоге, Proxy даёт единую точку контроля над всеми взаимодействиями с объектом, но применять его стоит осознанно. Он особенно оправдан там, где нужна централизованная логика — реактивность, валидация, логирование или виртуальные свойства. 😁 При этом в большинстве повседневных задач он может оказаться избыточным. Но понимать, как он устроен, всё равно важно: лучше знать о таком инструменте и не использовать его, чем однажды столкнуться с ним в коде и не понимать, что происходит. 👍

  • 17 февр.1 4483110

    Compression Streams API — нативный gzip/deflate прямо в браузере 🤔 Недавно снова вспомнила про Compression Streams API — и это тот случай, когда платформа уже умеет то, ради чего раньше тянули библиотеку. 🤭 Это встроенное потоковое API для сжатия и распаковки данных в форматах gzip и deflate. Работает поверх Streams, то есть данные обрабатываются по частям, без загрузки всего в память. Если коротко: теперь можно сжимать и распаковывать данные прямо в клиенте — без сторонних зависимостей. 🧩 Что важно запомнить? ✔️ Форматы: gzip, deflate; ✔️ Классы: CompressionStream и DecompressionStream; ✔️ Работает поверх Streams — данные идут по частям, без загрузки всего в память; ✔️ Используется через pipeThrough() — вписывается в любую стрим-цепочку ReadableStream → CompressionStream → WritableStream — и всё это идёт «на лету». 🧩 Пример: сжимаем в gzip async function compress(text) { const encoder = new TextEncoder(); const stream = new Blob([encoder.encode(text)]).stream(); const compressed = stream .pipeThrough(new CompressionStream("gzip")); return await new Response(compressed).blob(); } 🧩 Где это полезно? ⏺️ сжимать данные перед отправкой на сервер; ⏺️ осохранять/обрабатывать большие JSON / CSV / логи; ⏺️ уменьшать зависимость от библиотек — особенно при больших файлах Из минусов: поддерживаются только gzip и deflate (без ZIP-архивов), нельзя настраивать уровень сжатия, а в старых браузерах API может быть недоступно. 😕 В итоге, это удобная и незаметная фича платформы — для большинства простых задач сжатия её хватает. 👍

  • 9 февр.1 508319

    Почему бизнес-логику не стоит писать в UI-компонентах 🧐 Часто вижу один и тот же паттерн: небольшой компонент, пара условий — и через несколько фич он превращается в монстра, который и отображает, и решает, и общается с API, и ещё немного «магии» делает. 🤯 В итоге вместо чистого UI у нас — куча скрытой логики, которую потом тяжело поддерживать. 🧩 Как же так получается? Сначала всё выглядит логично: дедлайн, фича маленькая — проще вставить обработку прямо в компонент. Потом «потом вынесем» не наступает, и компонент растёт: ➖ вызывает API и трансформирует ответы; ➖решает, какие шаги процесса выполнять; ➖содержит ветвления бизнес-правил; ➖становится источником копипаста при переиспользовании. 🧩 А в чём же здесь проблема? ☑️ Роли смешиваются. Компонент должен показывать интерфейс, а не управлять процессом. Когда UI начинает «думать», система становится довольно хрупкой; ☑️ Переиспользование погибает. Нужна та же логика в другом месте — копипаст, рассинхронизация, баги; ☑️ Изменения рискованны. Поправил отображение — сломал процесс; поменял правило — неожиданно упало UI; ☑️ Тестирование усложняется. 🧩 Почему же пишут логику в компонентах? ⏺️ Быстрее на старте; ⏺️ "Кажется" избыточным выносить «для одной фичи»; ⏺️ Команда устала/нет архитектурного стандарта. Но чаще бывает так, что «маленькая фича» обычно вырастает, и технический долг накапливается. 😥 🧩 Как же лучше стараться организовать код? Разделение обязанностей простое и эффективное: ✔️ UI-компоненты — отвечают за отображение и реагирование на пользовательские действия; ✔️ Бизнес-логика — в сервисах, сторе илимодулях; ✔️ Интеграция — компоненты вызывают сервисы/экшены и получают уже готовые данные/состояния. Примерный поток: компонент → action/use-case → сервис → репозиторий/API → сервис → use-case → компонент (обновлённый state). 🧩 Как же можно себя проверить? Если из компонента убрать шаблон, и оставшийся код всё ещё имеет смысл сам по себе — значит в компонент протекла бизнес-логика. Это сигнал, чтобы задуматься о рефакторинге. Как итог, UI — это слой отображения, бизнес-логика — слой принятых решений. Смешивать их удобно только на старте, но в долгой перспективе это почти всегда приводит к росту сложности, хрупкости системы и технического долга. 🙈

  • 6 февр.1 334232

    Как неочевидные импорты превращают проект в клубок зависимостей 😨 На бумаге — простое правило: модуль импортирует только то, что ему положено. На практике — его иногда нарушают. И последствия оказываются внезапными и дорогими. Пару раз в жизни я меняла локальный метод в сервисе, будучи уверена, что трогаю только свой модуль. Но в проде падала функциональность в совершенно другой части продукта. 😅 Причина оказалась одна: неочевидный прямой импорт, который связал, казалось бы, независимые куски архитектуры. Вывод: правило «не импортировать всё подряд» — не архитектурная прихоть. Это практический способ держать систему предсказуемой, управляемой и масштабируемой. Эта пара реальных инцидентов объясняет, почему правило «не импортировать всё подряд» — не абстрактная архитектурная прихоть, а практический способ удерживать систему предсказуемой, управляемой и масштабируемой. Почему свободные импорты опасны ⏺️ Скрытые зависимости. Усложняют оценку влияния правки — локальная правка может иметь глобальные последствия; ⏺️ Рост затрат на тестирование. Чтобы воспроизвести баг, нужно поднимать большие части системы; ⏺️ Размытое ownership. Никто не уверен, кто отвечает за изменение — баги «прыгают» между командами; ⏺️ Страх рефакторить. Любое изменение становится рискованным и медленным. В больших системах главное — сохранять локальность изменений. Если небольшая правка начинает ломать удалённые части продукта, это почти всегда сигнал о проблемах в границах модулей. Контроль импортов — один из самых простых и эффективных способов вернуть системе предсказуемость и сделать развитие продукта управляемым. 👍

  • 2 февр.1 4862811

    «Хочу стиль для p в header, но не хочу повышать специфичность» 😎 Иногда самая тривиальная задача — задать стиль для p внутри header — перерастает в бесконечную гонку селекторов. Вы пишете header p { … }, кто-то добавляет .text { … }, вы усложняете селекторы, и проект медленно погружается в «адские» веса CSS. 😞 К счастью, есть аккуратный и очень полезный приём — использовать :where() 🧩 Что делает :where(): :where() — это CSS-псевдокласс, который принимает список селекторов и всегда имеет нулевую специфичность, независимо от того, что внутри. 💡 Это значит: даже если вы положите внутрь #id .class element, итоговый «вес» всё равно ноль — для всей конструкции :where(). 🔍 Небольшое напоминание про специфичность: Специфичность — это вес селектора. Упрощённо: inline > ID > класс/псевдокласс > элементы / псевдоэлементы. Её используют алгоритмы CSS для выбора победителя при конфликте: ⏺️ p → специфичность (0,0,0,1) ⏺️ :where(header) p → :where(header) даёт 0, p даёт 1 → итог (0,0,0,1) ⏺️ header p → header + p → (0,0,0,2) — уже селектор тяжелее ➡️ p и :where(header) p имеют одинаковую специфичность — при конфликте побеждает правило, объявлённое позже. Как итог, используйте :where() кконтекстное правило по умолчанию без повышения специфичности: reset/normalize, базовая типографика, умолчания внутри layout-контейнеров. Это позволяет задавать нужный контекст, не ломая каскад и не раздувая селекторы. 👍

  • 29 янв.1 4082714

    Связность и сцепленность: как написать код, который не боишься менять 🧩 Представьте проект как дом. Комнаты — это модули и компоненты. В хорошем доме каждая комната имеет своё назначение: кухня для готовки, спальня для сна, ванная для гигиены. Если в одной комнате стоит плита, кровать и стиральная машина, жить неудобно — это низкая связность: элементы внутри модуля занимаются разными вещами. 😏 Двери и коридоры — это интерфейсы и API между модулями. Хорошие двери простые и предсказуемые: можно менять содержимое одной комнаты, не затрагивая остальные. Плохие — это отсутствие нормальных дверей, когда приходится ходить «через стену»: любое изменение тянет за собой цепочку правок. Это и есть высокая сцепленность. Идеальный дом — чёткие комнаты и аккуратные двери. В коде это означает: модуль делает одну задачу и как можно меньше знает о других. Такой код проще тестировать, менять и развивать. ☑️ Запоминаем: Связность (cohesion) — насколько элементы внутри одного модуля/класса/сервиса заняты общей задачей. Высокая связность = модуль делает одно дело и делает его хорошо. Сцепленность (coupling) — насколько модуль зависит от других. Низкая сцепленность = можно менять модуль без лавины правок в остальной системе. Идеал: высокая связность + низкая сцепленность. 🧩 В чём основное преимущество? ☑️ Поддержка кода становится дешевле и быстрее; ☑️ Изменения локализуются — меньше побочных эффектов; ☑️ Архитектура остаётся гибкой: можно заменять реализации, не ломая систему; ☑️ И, конечно, тестировать проще — модуль с одной задачей легче мокать и покрывать тестами. Небольшой пример на картинке выше 👆 И, конечно, хорошая архитектура — это не идеология, а инструмент: она экономит время, упрощает поддержку и делает изменения безопасными. Но всегда могут возникать исключения из правил: иногда нужно пожертвовать чистотой ради срочного решения или прототипа. Важно, чтобы такие компромиссы были осознанными и временными, а не становились привычкой. 😊

  • 27 янв.1 2292516

    Разбираемся с Feature-based архитектурой 🤩 Feature-based архитектура организует код по бизнес-фичам, а не по техническим слоям, благодаря чему изменения локализуются, команды работают автономнее, а кодовая база остаётся понятной по мере роста продукта. Feature-based архитектура строится вокруг идеи локализации контекста: каждая фича оформляется как самостоятельный модуль, внутри которого находится всё необходимое для её работы — UI, состояние, бизнес-логика, взаимодействие с API и тесты. За счёт этого разработчику не нужно переходить между десятками папок, чтобы внести изменение, потому что весь связанный код лежит в одном месте и читается как единое целое. 👍 Такой подход особенно хорошо масштабируется вместе с командой. Когда фичи изолированы, несколько разработчиков или команд могут параллельно работать над разными частями продукта, почти не мешая друг другу, а сами фичи со временем можно выделять в отдельные пакеты или сервисы без болезненных рефакторингов. Дополнительный эффект — упрощение тестирования и рефакторинга, поскольку границы ответственности явно выражены, а поведение фичи можно проверять в изоляции. ❗️❗️❗️❗️ При этом feature-based архитектура не работает «сама по себе» — ей нужны правила. Ключевое требование — чёткие границы и публичные контракты. Фичи не должны импортировать внутренности друг друга напрямую, а взаимодействие между ними должно происходить только через явно объявленный публичный API. Общий код выносится в shared, но он обязан оставаться узким и стабильным: если shared начинает расти без ограничений, он быстро превращается в скрытый монолит и разрушает изоляцию фич. 👩‍💻 Минимальный пример того, как может выглядеть одна фича на фронтенде: src/ features/ Cart/ components/ hooks/ api.ts index.ts // публичный API фичи README.md shared/ ui/ utils/ Файл index.ts играет ключевую роль: он явно определяет, что именно фича разрешает использовать извне, а всё остальное остаётся внутренней деталью реализации. А файл README с кратким описанием назначения фичи и её публичного API дополнительно снижает порог входа для новых разработчиков и помогает сохранять архитектурные границы. 😁 ‼️ Важно понимать и ограничения подхода. Для маленьких проектов feature-based архитектура может быть избыточной, потому что требует дисциплины, документации и автоматизации. Но для растущих продуктов она становится инструментом управления сложностью: изменения становятся локальными и предсказуемыми, кодовая база — более читаемой, а команды — более автономными. Как итог, feature-based архитектура — это не про папки, а про мышление фичами, явные границы и контроль связности. При правильных правилах и автоматизации она позволяет системе расти, не теряя понятности и скорости разработки. 👍

  • 21 янв.1 301297

    Разбираемся с CSR (Client-Side Rendering) 👨‍🏫 Client-Side Rendering (CSR) — это подход к рендерингу, при котором сервер отдаёт минимальный HTML-каркас, а весь интерфейс и основная логика формируются уже в браузере пользователя с помощью JavaScript. Браузер загружает скрипты, выполняет код и строит DOM на клиенте. Главное преимущество CSR — интерактивность после инициализации. Интерфейс начинает работать плавно и отзывчиво: легко реализуются сложные виджеты, динамическая персонализация и переходы без перезагрузки страницы. Однако за эту гибкость приходится платить — основная нагрузка по построению интерфейса ложится на устройство пользователя 😐 Пока браузер скачивает, парсит и выполняет JavaScript, страница может либо отображаться частично, либо оставаться «белой». Даже если UI уже виден, он часто остаётся слабо интерактивным: клики и ввод обрабатываются с задержкой. Особенно заметно это на слабых устройствах и в медленных сетях. ☹️ Стоит отметить влияние CSR на индексирование и предпросмотр. Если значимая часть контента формируется только на клиенте, поисковым системам и социальным сетям сложнее корректно проанализировать страницу и сформировать сниппеты. Как итог, CSR лучше всего подходит для приложений с богатой интерактивностью — дашбордов, админ-панелей и внутренних сервисов, где пользователь проводит много времени и ценит плавность работы после загрузки. 👍 Для контентных и маркетинговых страниц чаще выбирают другие подходы, о которых поговорим чуть позже 😉 Чтобы снизить влияние недостатков CSR, обычно используют: ⏺️ разбиение бандлов, ⏺️ ленивую загрузку кода, ⏺️ и, конечно, внимательно следят за объёмом и временем выполнения JavaScript. Именно такие оптимизации позволяют сохранить гибкость клиентского рендеринга, не жертвуя пользовательским опытом. 👍

  • 19 янв.1 323219

    INP: как измеряется отзывчивость страницы 🤨 Иногда сайт загружается быстро, контент появляется почти сразу, но пользоваться им всё равно не очень приятно. Клик срабатывает с задержкой, поле ввода реагирует не сразу, интерфейс как будто «думает». Формально всё загрузилось, но ощущение отзывчивости — так себе. 😥 Именно такие ситуации описывает INP (Interaction to Next Paint). INP — это метрика, которая показывает, насколько быстро интерфейс реагирует на действия пользователя. Она измеряет задержку между пользовательским действием (клик, тап, ввод) и моментом, когда браузер завершает рендеринг следующего кадра, который отражает результат этого действия. Проще говоря, сколько времени проходит от действия до визуального отклика. Важно понимать, что INP не оценивает одно конкретное взаимодействие, а берёт 75-й перцентиль всех взаимодействий за время жизни страницы. То есть метрика показывает, какой опыт чаще всего испытывает пользователь, а не самый удачный или самый плохой единичный случай. С 2024 года INP входит в Core Web Vitals и заменяет FID. Причина проста: FID учитывал только первое взаимодействие, а INP охватывает все действия пользователя, давая более полное представление о реальной отзывчивости страницы. ‼️ На практике высокий INP почти всегда возникает из-за перегрузки основного потока браузера. Если пользователь взаимодействует с интерфейсом, а браузер занят выполнением длинных задач, то обработка событий замедляется, и визуальный отклик появляется с задержкой. Чаще всего INP ухудшают: ⏺️ длинные задачи в основном потоке; ⏺️ сложные обработчики событий; ⏺️ синхронные вычисления при клике или вводе; ⏺️ большое количество стороннего JavaScript; ⏺️ тяжёлые обновления DOM. Улучшение INP обычно не требует каких-то экзотических решений — в большинстве случаев достаточно сократить и дробить длинные задачи, упростить логику обработчиков событий, вынести тяжёлые вычисления в Web Workers, отложить загрузку не критичного кода и уменьшить влияние сторонних скриптов. 👍 Полезные ссылки: ⛓ Подробнее в Web Dev ⛓ Документация MDN

  • 14 янв.1 470369

    Как сделать «скрытый, но находимый» контент? 🔍 Представьте: у вас длинная страница с FAQ, аккордеонами и сворачиваемыми разделами. Пользователь нажимает Ctrl/Cmd+F, вводит слово — и... браузер ничего не показывает, потому что нужный текст спрятан в свернутом аккордеоне. Раздражает? 😔 Для таких случаев есть hidden="until-found" — атрибут, который позволяет визуально скрывать контент, но оставлять его доступным для поиска на странице. Если браузер находит совпадение, он автоматически раскрывает нужный блок и даёт возможность синхронизировать интерфейс через событие beforematch. По сути, это прогрессивное улучшение: ✔️ браузер поддерживает фичу → поиск работает «магически»; ✔️ не поддерживает → интерфейс остаётся полностью рабочим. Главный плюс подхода — компактный интерфейс без потери находимости контента. Пользователь по-прежнему может быстро найти нужный фрагмент обычным поиском, даже если он спрятан в аккордеоне. Если браузер не умеет раскрывать контент автоматически, ничего страшного не происходит. Достаточно проверить поддержку события beforematch и включить запасной сценарий — например, раскрывать все секции или использовать собственный поиск. if (!('onbeforematch' in document)) { // fallback-логика } ‼️ Нюансы, о которых стоит помнить ⏺️ beforematch удобно использовать для синхронизации аккордеона и прокрутки к найденному месту; ⏺️ hidden="until-found" — это не display: none: поведение layout-API (getBoundingClientRect, content-visibility, contain) может отличаться, лучше протестировать; ⏺️ Не все браузеры поддерживают данную возможность, поэтому для браузеров без поддержки стоит заранее продумать fallback: явное раскрытие важных секций или альтернативный поиск. 😁 Полезные ссылки: ⛓ Подробнее в Chrome for Developers ⛓ Потыкать демо (CodePen)

  • 30 дек.1 65877

    Сейчас все подводят итоги, и я долго думала — писать ли тоже. Чем честнее смотришь на год, тем легче принять: он был непростым для канала. Активность немного просела, многие планы отложились, а фокус всё чаще уходил в личные дела. Иногда просто не хватало времени и сил проявляться так, как хотелось — и это тоже часть истории, хотя принять это было непросто. 😒 Вместе с тем год подарил много хорошего: были разобраны интересные темы, снято несколько видео, а также получен важный опыт. 👏 Были публикации, которые действительно зацепили вас — и это согревает душу. Даже ошибки и паузы оказались ценными: они напомнили, что главное — сохранять интерес и возвращаться к делу снова и снова. ☺️ Огромное спасибо каждому, кто был рядом — читал, комментировал, делился мыслями или просто наблюдал. Ваши слова и реакции часто значат больше, чем кажется, и именно они помогают каналу жить дальше. Сейчас я ухожу в полноценный отдых: нужна пауза, чтобы перезарядиться, привести мысли в порядок и придумать новые форматы без давления «срочно и прямо сейчас». Искренне желаю и вам найти такую возможность — остановиться, восстановить силы и уделить время тому, что по-настоящему наполняет. Пусть завершение года будет спокойным и тёплым: с простыми радостями, тихими вечерами и душевными разговорами. Берегите себя, своё здоровье, отдохните полноценно, без спешки — и пусть в новом году появится больше вдохновения, маленьких побед и интересных планов. Спасибо, что вы со мной. ❤️ С наступающим и хорошего отдыха всем нам! ✨

  • 27 дек.1 664299

    Распределение условных типов и infer в TypeScript 🤩 В TypeScript условные типы по умолчанию распространяются по объединениям (A | B). Это часто полезно, но иногда приводит к неожиданным результатам. Простое и надёжное решение — спрятать параметр в кортеж: [T] extends [X] ? ... 🧩 Проблема: Условный тип вида T extends X ? A : B при подстановке T = A | B автоматически разбивается и проверяется для каждого члена объединения отдельно. Такой механизм называется distributive conditional types. type IsString<T> = T extends string ? "yes" : "no"; type R = IsString<string | number>; // "yes" | "no" Ожидание «всё или ничего» (весь тип — строка?) нарушается: вместо одного ответа получаем объединение ответов для частей. 😥 🧩 Решение: Чтобы проверить весь тип одним выражением (не по частям), нужно «спрятать» T внутри другой структуры — самый простой приём: кортеж. type IsStringStrict<T> = [T] extends [string] ? "yes" : "no"; type A = IsStringStrict<string>; // "yes" type B = IsStringStrict<number>; // "no" type C = IsStringStrict<string | number>; // "no" <- проверка по всему типу Распределение происходит только при проверке «голого» T. Оборачивая T в [T], мы запрещаем TypeScript разбирать объединение и заставляем его проверять тип целиком. 🧩 Практический выбор: ✔️ Нужна логика для каждого члена union? — оставить поведение по умолчанию (распространение полезно); ✔️ Нужна единая проверка для всего типа? — спрятать T ([T] extends [...] ? ...) и получить один итоговый результат. Как итог, осознание того, что условные типы по умолчанию работают с union поэлементно, а не целиком, заметно упрощает работу с типами. Приём с [T] позволяет явно задать намерение, а именно проверить весь тип сразу и избежать неожиданных объединений в результате. 👍

  • 25 дек.1 360275

    Обработка комбинаций клавиш 🧑‍💻 Комбинация клавиш используют для обычных вещей: сохранить данные, перейти между экранами, ускорить работу. В JavaScript это реализуется довольно просто, если понимать, как работают события клавиатуры. Немного теории ✔️ Комбинация клавиш — это одно событие keydown, внутри которого мы проверяем: ⏺️ какую клавишу нажали; ⏺️ зажаты ли модификаторы (Ctrl, Shift, Alt, Meta); ✔️ В браузере нет отдельного события “Ctrl+S” — всё делается через условия. Ключевые свойства события ✔️ event.key — символ или имя клавиши ("a", "Enter"); ✔️ event.code — физическая клавиша ("KeyA", "Enter"); ✔️ event.ctrlKey, event.shiftKey, event.altKey, event.metaKey — модификаторы. Простой пример: Ctrl + S document.addEventListener('keydown', (event) => { if (event.ctrlKey && event.code === 'KeyS') { event.preventDefault(); // при необходимости отменяем дефолтное поведение // наша логика } }); На что стоит обратить внимание? ✨ Не перехватывайте хоткеи при вводе текста в input и textarea; ✨ На macOS вместо Ctrl часто используют Meta (Cmd); ✨ Удержание клавиши вызывает повторные события keydown; ✨ Для хоткеев лучше использовать event.code, чтобы не зависеть от раскладки. И, конечно же, не злоупотребляйте глобальными хоткеями — они должны дополнять интерфейс, а не заменять его. 😁

  • 23 дек.1 304236

    firstValueFrom vs lastValueFrom 🤨 В RxJS мы часто работаем с Observables — потоками данных. Иногда нужно получить одно значение из Observable как Promise, чтобы использовать async/await. Для этого есть firstValueFrom и lastValueFrom.😉 Немного теории: ✔️ firstValueFrom(obs$) — возвращает Promise, который резолвится при первом эмите и автоматически отписывается; ✔️ lastValueFrom(obs$) — возвращает Promise, который резолвится только после завершения Observable и содержит последний эмит до завершения. 👀 Что важно учитывать? 1️⃣ Если поток завершился без эмитов, оба метода reject’ят промис. Чтобы этого избежать, можно передать defaultValue: await firstValueFrom(obs$, { defaultValue: fallback }); await lastValueFrom(obs$, { defaultValue: fallback }); defaultValue сработает только если поток завершился без эмитов. Он не спасёт от ошибок или от вечного ожидания. 2️⃣ lastValueFrom может никогда не разрешиться на бесконечных потоках (например, interval() без ограничения). Поэтому его можно и нужно защищать (take, takeUntil, timeout и т. п.). Примеры: // const observable$ = of(10, 20, 30); const result = await firstValueFrom(observable$); // ограничить количество эмитов const safe$ = source$.pipe(take(5)); const last = await lastValueFrom(safe$); // или добавить таймаут const lastWithTimeout = await lastValueFrom(source$.pipe(timeout({ each: 3000 })), { defaultValue: null }); 🤓 Когда и что выбрать? ⏺️ firstValueFrom — когда нужен первый результат (подходит и для бесконечных потоков); ⏺️ lastValueFrom — только если поток гарантированно конечен или вы явно ограничили его/поставили таймаут. ➡️ Если нужна реактивная обработка нескольких значений — не превращайте Observable в Promise; используйте подписки, async-pipe или RxJS-операторы.

  • 20 дек.1 3072915

    Когда нужен приоритет загрузки 🤔 Ленивая загрузка (loading="lazy") удобна: экономит трафик и ускоряет загрузку страницы за счёт отложенной загрузки картинок, которые пока не видны пользователю. Однако если вы не укажете приоритет для критичных картинок, браузер сам примет решение о порядке загрузки — а его алгоритмы могут не совпасть с вашими целями. ☹️ В результате изображение или шапка могут загрузиться позже, чем нужно: это ухудшит LCP и создаст ощущение «медленной» страницы; при отсутствии заданных размеров это ещё и может привести к CLS. 🧩 Что делать? ✔️ Для некритичных изображений — смело ставьте loading="lazy". Это экономит трафик и не вредит UX; ✔️ Для критичных изображений на первом экране — НЕ полагайтесь только на loading="lazy": ✨ уберите loading="lazy", или ✨ явно укажите приоритет (fetchpriority="high"), или ✨ предзагрузите ресурс (<link rel="preload" as="image" href="…"> ✔️ Всегда задавайте width/height или используйте aspect-ratio — это защитит от CLS; ✔️ Используйте srcset/sizes (и современные форматы WebP/AVIF) — чтобы браузер мог выбрать оптимальный файл для устройства; ✔️ Проверяйте LCP в реальных условиях — локальные тесты не всегда отражают боевое поведение. 🧩 Пример: <!-- Важное изображение --> <link rel="preload" as="image" href="/image.jpg"> <img src="/image.jpg" width="1200" height="600" fetchpriority="high" alt="..."> <!-- Некритичное изображение --> <img src="/thumb.jpg" loading="lazy" width="400" height="300" alt="..."> 🧩 Несколько частых ошибок: ⏺️ Применять loading="lazy" ко всем изображениям подряд — и потерять контроль над LCP; ⏺️ Положиться только на fetchpriority и забыть про preload и адаптивные форматы; ⏺️ Предзагружать слишком много ресурсов — это может ухудшить общую загрузку. Как итог, ленивую загрузку стоит применять выборочно. Для картинок первого экрана давайте явный приоритет (preload или fetchpriority) или вовсе не лениво загружайте их; для всего остального — смело лениво загружайте. Так вы получите и экономию трафика, и предсказуемую, быструю загрузку для пользователя. 👍

  • 17 дек.1 6289

    видео или голосовое, без подписи

  • 17 дек.1 7389

    видео или голосовое, без подписи

  • 17 дек.1 3099

    видео или голосовое, без подписи

  • 17 дек.1 083329

    Cumulative Layout Shift: почему страница «прыгает» и как это остановить 🤓 Cumulative Layout Shift (CLS) — это метрика, которая показывает, насколько часто и насколько сильно элементы страницы неожиданно смещаются после первого рендера. Браузер для каждого такого сдвига оценивает, какая часть экрана была затронута и как далеко элементы уехали, а затем суммирует эти значения. В итоге CLS отражает не скорость загрузки, а стабильность интерфейса во времени. 📚 Откуда берётся CLS Почти всегда причина одна и та же — неопределённость размеров. Если в момент первого расчёта layout браузер не знает, сколько места займёт элемент, он не резервирует пространство. Когда элемент появляется или меняет свои размеры, браузеру приходится перестраивать макет, и пользователь видит «прыжок». На практике чаще всего виноваты изображения, но не только они. 😁 Подробности на картинках выше! Как итог, CLS возникает не потому, что ресурсы загружаются медленно, а потому что браузеру слишком поздно сообщают геометрию интерфейса. ➡️ width/height для <img>, aspect-ratio для контейнеров, резервирование слотов под шрифты, рекламу и динамическое изменение превращают догадки браузера в знание. А когда браузер знает размеры заранее, странице просто некуда прыгать. 👍

Катерина | Про Frontend — tgindex