Frontend разработчик
11K subscribers
1.81K photos
514 videos
50 files
2.79K links
Полезные материалы для фронтендера по HTML, CSS, JS, React.js, Angular.js, Vue.js, TypeScript, Redux, MobX, JavaScript, NodeJS.

По всем вопросам @evgenycarter

РКН clck.ru/3KoFrk
Download Telegram
Совет по CSS 💡

Легко создайте полосатый прогресс-бар без использования сторонних библиотек 🤩

📲 Мы в MAX

👉 @frontend_1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
This media is not supported in your browser
VIEW IN TELEGRAM
Node получил возможность запускать файлы TypeScript напрямую!

📲 Мы в MAX

👉 @frontend_1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍73
Дорогие друзья, с Новым Годом!!!
10🎉6👍2🎄21
Советы по Javascript 💡

📲 Мы в MAX

👉 @frontend_1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5👎1
Перестаньте пихать всё в useEffect. Серьезно.

Вижу это на каждом код-ревью. Джуны (и иногда уставшие мидлы) используют useEffect для синхронизации стейта. Это выстрел себе в ногу.

🔴 Как делать не надо:
У вас есть firstName и lastName, и вы создаете эффект, чтобы обновить fullName.


const [firstName, setFirstName] = useState('Alex');
const [lastName, setLastName] = useState('Dev');
const [fullName, setFullName] = useState('');

useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);



Почему это плохо?

1. Лишний рендер. Компонент рендерится, запускается эффект, обновляется стейт -> компонент рендерится снова.
2. Сложность отладки. Попробуйте отследить цепочку из 5 таких эффектов в большом компоненте.

🟢 Как надо (Derived State):
Вычисляйте значение прямо во время рендера.


const fullName = `${firstName} ${lastName}`;



Если вычисления тяжелые, используйте useMemo. Но в 90% случаев вам не нужен ни эффект, ни стейт.

💡 Правило большого пальца: Если вы можете вычислить что-то из уже имеющихся пропсов или стейта - не создавайте для этого новый стейт.

#react #bestpractices #performance

📲 Мы в MAX

👉 @frontend_1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍81👎1😁1
Знаете ли вы, что можно изменить метод формы в HTML-форме, указав атрибут formMethod на кнопке?

📲 Мы в MAX

👉 @frontend_1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
💻SPA умеют почти все. А вот интерфейс, который живёт в реальном времени, без перезагрузок и костылей, обычно ломает даже уверенных фронтендеров.

📆На открытом вебинаре OTUS вы соберёте мини-биржу на Vue: поднимем локальный WebSocket-сервер с мок-данными, подключим подписку на стрим, выведем список валют, изменения цен и индикаторы роста/падения. Добавим живые графики (ApexCharts или Chart.js) и обновления.

Разберём архитектуру real-time интерфейсов: реактивные данные, управление соединением, обработка событий и потоков, обновления без перезагрузки. Покажем, как после урока заменить мок-источник на реальный WebSocket-поток.

👉Встречаемся 14 января в 20:00 МСК в преддверие старта курса «Vue.js-разработчик». Регистрация открыта: https://vk.cc/cTkUVz

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
👍1
🛡 TypeScript: Почему satisfies круче обычного объявления типа?

Многие по привычке пишут так: const config: Config = { ... }.
Это работает для валидации, но у этого подхода есть минус - Type Widening (расширение типов). Вы теряете точность выведенного типа в угоду соответствию интерфейсу.

Начиная с TS 4.9, оператор satisfies решает эту проблему. Он проверяет, что объект соответствует типу, но сохраняет его точную структуру.

В чем разница?

Классический подход (потеря точности):


type Routes = Record<string, string | string[]>;

// Мы явно указали тип Routes
const nav: Routes = {
home: "/",
admin: ["/users", "/posts"]
};

// TS думает, что nav.home — это 'string | string[]'
// ОШИБКА: Property 'toUpperCase' does not exist on type 'string | string[]'
nav.home.toUpperCase();



Используем satisfies (сохраняем контекст):


const nav = {
home: "/",
admin: ["/users", "/posts"]
} satisfies Routes;

// Теперь TS знает:
// 1. nav соответствует Routes (опечатки не пропустит)
// 2. nav.home — это конкретно string
// 3. nav.admin — это конкретно string[]

// РАБОТАЕТ!
nav.home.toUpperCase();
nav.admin.map(path => path);



Когда использовать?
Всегда, когда вы хотите проверить соответствие контракту (конфиги, темы, палитры цветов), но при этом хотите продолжать работать с конкретными значениями этого объекта, а не с его общим интерфейсом.

📲 Мы в MAX

👉 @frontend_1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍91🔥1
☠️ Баг, который вы не видите, а пользователи ненавидят

Представьте: пользователь заполнил длинную форму регистрации, свернул приложение, чтобы скопировать код из SMS, возвращается... а экран пустой. Данные исчезли. 🤬

Это System-initiated process death. Самая частая причина удаления приложения у пользователей и головная боль разработчиков.

Почему это происходит?
ОС (Android или iOS) всегда не хватает оперативной памяти. Когда ваше приложение уходит в фон, система может «тихо» убить его процесс, чтобы освободить ресурсы для активного окна (например, Камеры).

Ошибка новичка:
«Я храню данные в ViewModel (Android) или в переменной контроллера (iOS), они же живут долго!»
Реальность: ViewModel переживает поворот экрана, но умирает вместе с процессом. Синглтоны тоже сбрасываются.

Как делать правильно (Level Up):

🤖 Android:
Перестаньте полагаться только на поля класса. Используйте SavedStateHandle.
Это специальный механизм внутри ViewModel, который сохраняет небольшие кусочки данных (ID, поисковый запрос, ввод пользователя) в системный бандл. Система бережно восстановит его даже после смерти процесса.
Гуглить: SavedStateHandle, Parcelable.

🍏 iOS (SwiftUI/UIKit):
В SwiftUI для простых данных (например, выбранная вкладка или текст) используйте обертку @SceneStorage. Она автоматически сохраняет и восстанавливает состояние.
Для сложных данных — сохраняйте их в локальную БД (CoreData/Realm/SwiftData) при каждом изменении, а не при закрытии экрана.

🛠 Как проверить себя (Челлендж на 5 минут):
Не верьте эмулятору. Проверьте свой текущий проект прямо сейчас:

1. Запустите приложение и введите данные в любое поле.
2. Сверните приложение (Home).
3. Android: В настройках разработчика включите опцию «Don't keep activities» (Не сохранять действия).
4. iOS: В Xcode нажмите Debug -> Simulate Memory Warning или остановите дебаг и запустите другое тяжелое приложение.
5. Вернитесь в свое приложение.

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

Знали про SavedStateHandle или по старинке сохраняли всё в базу данных? 👇

#android #ios #bugs #middle #architecture #обучение

👉 @developer_mobila
👍7❤‍🔥1😍1
🎨 CSS :has() - это легальный чит-код в верстке

Мы привыкли называть :has() «селектором родителя», но это определение сильно преуменьшает его мощь. Это инструмент, который позволяет отказаться от JS и лишних классов-модификаторов для управления состоянием UI.

Вот два сценария, где :has() меняет правила игры:

1. Контекстная стилизация (без BEM-модификаторов)
Раньше, чтобы изменить стили карточки в зависимости от контента (например, есть ли внутри картинка или бейдж), мы писали JS-проверку или добавляли класс .card--with-image.

Теперь CSS сам смотрит внутрь:


/* Если внутри карточки есть картинка -> меняем грид */
.card:has(img) {
grid-template-columns: 1fr 1fr;
}

/* Если внутри есть сообщение об ошибке -> красим бордер */
.form-group:has(.error-message) {
border-color: red;
background: #fff0f0;
}



2. Селектор «предыдущего соседа»
В CSS всегда можно было стилизовать элемент, идущий после (h2 + p), но никогда - перед. С :has() это стало возможным.

Трюк: мы выбираем элемент, если за ним следует нужный нам сосед.


/* Стилизуем h2, ТОЛЬКО если сразу за ним идет список ul */
h2:has(+ ul) {
margin-bottom: 0; /* Убираем отступ, чтобы "приклеить" к списку */
color: var(--primary);
}



🔥Поддержка в браузерах уже отличная (Baseline 2023). Если вы всё еще пишете useEffect или вешаете классы просто чтобы поменять стиль родителя при фокусе инпута (:focus-within не всегда хватает) или наличии элемента - удаляйте лишний код и берите :has().

📲 Мы в MAX

👉 @frontend_1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥5
This media is not supported in your browser
VIEW IN TELEGRAM
Советы по HTML 💡

Знаете ли вы, что с помощью элемента HTML <dialog> можно легко создать модал?

📲 Мы в MAX

👉 @frontend_1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍63
any - это ложь, которую вы говорите сами себе

Давайте честно: когда у вас горят дедлайны, а TypeScript ругается на несовпадение типов, рука сама тянется написать : any.

«Я потом поправлю», - говорите вы.
Спойлер: не поправите.

Использование any превращает ваш строгий TypeScript проект обратно в анархичный JavaScript, но с лишним этапом сборки. Вы просто отключаете компилятор.

🔥 Почему это плохо:
Вы теряете автокомплит, рефакторинг становится русской рулеткой, а баг undefined is not a function вернется к вам в самый неподходящий момент.

🛡 Что делать вместо any?

1. Если вы реально не знаете, что прилетит:
Используйте unknown. Это безопасный аналог any. Он скажет: "Я не знаю, что это, но я не дам тебе с этим работать, пока ты не проверишь тип".


// Плохо
const processData = (data: any) => {
data.toUpperCase(); // Может упасть, если data — число
}

// Хорошо
const processData = (data: unknown) => {
if (typeof data === 'string') {
data.toUpperCase(); // TS теперь знает, что это строка
}
}




2. Если лень описывать огромный ответ бэкенда:
Используйте утилиты для генерации типов из Swagger/OpenAPI. Не пишите интерфейсы руками, мы же не в каменном веке.

Уважайте свои нервы. Типизируйте нормально.

#typescript #cleancode #safety

📲 Мы в MAX

👉 @frontend_1
👍42
🛑 Хватит проверять isMounted в useEffect

Вы наверняка видели (или писали) такой код, чтобы избежать ошибки "Can't perform a React state update on an unmounted component":


// Антипаттерн
useEffect(() => {
let isMounted = true;
fetchData().then(data => {
if (isMounted) setState(data);
});
return () => { isMounted = false; };
}, []);



Это «мусорный» код. Запрос все равно происходит, трафик тратится, промис висит в памяти.
Вместо ручных флагов используйте стандартный браузерный API - AbortController.

Как сделать правильно:


useEffect(() => {
const controller = new AbortController();

fetch('/api/data', { signal: controller.signal })
.then(res => res.json())
.then(setData)
.catch(err => {
// Важно: не считаем отмену запроса ошибкой
if (err.name !== 'AbortError') {
console.error(err);
}
});

// При размонтировании или перезапуске эффекта запрос реально отменится браузером
return () => controller.abort();
}, []);



🚀 Бонус-фича: Очистка Event Listeners
AbortController умеет удалять и слушатели событий. Больше не нужно выносить функцию-хендлер в отдельную переменную, чтобы передать её в removeEventListener.


const controller = new AbortController();

// Передаем signal в опции
window.addEventListener('resize', (e) => handleResize(e), {
signal: controller.signal
});

// Одним вызовом удаляем слушатель (или группу слушателей с одним сигналом)
controller.abort();



📲 Мы в MAX

👉 @frontend_1
👍5
🏗 Забудьте про z-index: 9999: Нативный <dialog>

Мы годами писали сложные велосипеды для модальных окон или тянули тяжелые библиотеки. Главная боль кастомных решений - это accessibility (focus trap), закрытие по Esc и вечные войны с контекстом наложения (z-index).

HTML-элемент <dialog> теперь стабилен во всех браузерах и решает эти проблемы на уровне движка.

Почему это Game Changer:

1. Top Layer API: При открытии через .showModal(), элемент помещается в специальный «верхний слой» браузера. Ему плевать на overflow: hidden родителя или низкий z-index контейнера. Он всегда будет поверх всего.

2. Встроенная доступность: Браузер сам запирает фокус внутри окна (focus trap) и обрабатывает нажатие Esc.

3. Магия форм: Форма с method="dialog" закрывает модалку при сабмите без единой строчки JS-обработчика.

Пример (Минимум кода):


<dialog id="confirmModal">
<form method="dialog">
<h3>Удалить продакшн?</h3>
<p>Это действие необратимо.</p>

<button value="cancel">Отмена</button>
<button value="confirm" autofocus>Да, удалить</button>
</form>
</dialog>




const modal = document.getElementById('confirmModal');

// 1. Открываем (добавляет backdrop и блокирует остальную страницу)
modal.showModal();

// 2. Слушаем закрытие
modal.addEventListener('close', () => {
if (modal.returnValue === 'confirm') {
runDeleteScript();
}
});



🎨 Как стилизовать фон?
Забудьте про создание отдельного div для затемнения. Используйте псевдоэлемент:


dialog::backdrop {
background: rgb(0 0 0 / 0.5);
backdrop-filter: blur(4px); /* Красивое размытие фона */
}



⚠️ Важный нюанс:
Не путайте методы.

▪️ .show() - просто показывает элемент (позционирование absolute, страница скроллится).
▪️ .showModal() - то, что вам нужно (Top Layer, fixed, блокировка фона).

📲 Мы в MAX

👉 @frontend_1
🔥7👍42🤷‍♂1
14 февраля встречаемся на главной фронтенд-конференции Яндекса — «Я 💛 Фронтенд»: ежегодное мероприятие для тех, кто создает современные интерфейсы. Присоединяйтесь офлайн в Москве или онлайн!

Что вас ждет?

📌 Доклады о применении веб-компонентов, использовании LLM, переходе от традиционной адаптивности к новому подходу, и о том, как встроенные модели меняют архитектуру веб-приложений
📌 Code in the Dark — баттл по верстке на HTML/CSS
📌 CSS арт-челлендж, интерактивная викторина по фронтенд-разработке и другие активности от команд Яндекса

А совсем скоро стартует Capture the Flag — фронтендерский турнир на скорость с наградами для победителей.

Регистрируемся на ивент здесь — там же будут появляться онлайн-активности.
👍21🔥1
🧬 Прощай, JSON.parse(JSON.stringify())

Глубокое копирование объектов (Deep Copy) долго было болью в JS. Чтобы не копировать ссылки, мы либо тянули жирный lodash.cloneDeep, либо использовали популярный хак с JSON.

Проблема хака JSON.parse(JSON.stringify(obj)) в том, что он теряет данные:

1. Date превращается в строку.
2. Set и Map превращаются в пустые объекты {}.
3. undefined просто исчезает.
4. Циклические ссылки вызывают ошибку.

Теперь у нас есть нативный стандарт - structuredClone().

Как это работает:


const original = {
title: "Dev Meeting",
date: new Date(),
members: new Set(['Alex', 'Sam']),
meta: undefined
};

// Старый хак
const jsonCopy = JSON.parse(JSON.stringify(original));

console.log(jsonCopy.date); // Строка "2023-10-...", а не объект Date
console.log(jsonCopy.members); // {}, данные потеряны
console.log(jsonCopy.meta); // Ключ исчез совсем

// structuredClone
const realCopy = structuredClone(original);

console.log(realCopy.date.getFullYear()); // Работает! Это всё ещё Date
console.log(realCopy.members.has('Alex')); // Работает! Это всё ещё Set
console.log(realCopy.meta); // undefined (на месте)



⚠️ Важные ограничения:
structuredClone предназначен для данных.
Он выбросит ошибку DataCloneError, если вы попытаетесь скопировать:

• Функции (методы объекта);
• DOM-элементы;
• Свойства прототипа (копируются только собственные свойства).


📲 Мы в MAX

👉 @frontend_1
🔥6👍2🗿2💩1
📦 Нативная группировка: Object.groupBy

Сколько раз в жизни вы писали reduce для группировки массива объектов по какому-то полю? Или тянули для этого жирный lodash?

Задача: Сгруппировать продукты по категории.

🐢 Как мы делали раньше (Reduce):


const inventory = [
{ name: "Asparagus", type: "vegetables" },
{ name: "Bananas", type: "fruit" },
{ name: "Goat", type: "meat" },
{ name: "Cherries", type: "fruit" },
];

// Читать сложно, легко ошибиться в мутации аккумулятора
const result = inventory.reduce((acc, item) => {
(acc[item.type] ||= []).push(item);
return acc;
}, {});



🚀 Как это делается теперь:


const result = Object.groupBy(inventory, ({ type }) => type);

/* Результат:
{
vegetables: [{ name: "Asparagus", ... }],
fruit: [{ name: "Bananas", ... }, { name: "Cherries", ... }],
meat: [{ name: "Goat", ... }]
}
*/



🧠 Senior-нюанс: Map.groupBy

Обычный Object.groupBy возвращает объект, где ключи всегда приводятся к строкам.
Если вам нужно сгруппировать данные, где ключом выступает объект (например, пользователь или конфиг), используйте Map.groupBy.


const restockOptions = { threshold: 10 };
const urgentOptions = { threshold: 0 };

// Группируем элементы по объектам-конфигам
const result = Map.groupBy(inventory, (item) => {
return item.quantity < 5 ? urgentOptions : restockOptions;
});

// Получаем доступ по ссылке на объект!
result.get(urgentOptions);



Итог: Еще одна причина перестать тащить utility-библиотеки в бандл.

📲 Мы в MAX

👉 @frontend_1
👍4
Как frontend-разработчику получить оффер в Big Tech?

Платят как джуну, а спрашивают как с лида 🙄 Зарплата не растёт, задачи скучные.

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

Стабильность с маленькой зп, или дестрой рынка и выход на максимальную? Синяя или красная таблетка, Нео?! 👾

Меня зовут Тихон, привет! Я — действующий Frontend-разработчик и ментор.

Помогаю устроиться на хорошие позиции в Big Tech и сопровождаю на испытательном сроке.


В своем канале:
👉Разбираю самые популярные и каверзные вопросы на собесах
👉Рассказываю как пройти фильтр HR
👉Борюсь с убеждениями, которые мешают развиваться
👉Делюсь лайфхаками, например как аккуратно “пинговать” рекрутеров

Регулярно публикую полезные материалы:

▪️60 вопросов, которые точно помогут тебе на собеседовании.
▪️Подборка из 100+ каналов с вакансиями для разработчиков
▪️
10 задротских вопросов про JavaScript, после которых ты усомнишься, что вообще знаешь JS. Часть 1
▪️Чек лист проверки своего резюме

Подписывайся, нас уже 4500 🤓: ссылка

Реклама, erid2W5zFK72PEp: ИП Галактионов Тихон Витальевич, ИНН 771618975809
📐 Перестаньте верстать под экран: @container

Мы привыкли делать адаптив через @media, опираясь на размер экрана. Но это ломает компонентный подход.

Представьте карточку товара. На десктопе в основной сетке она широкая, а в сайдбаре - узкая. Если вы используете @media (min-width: 1200px), то карточка в сайдбаре будет "думать", что места много, и развалится, хотя сам сайдбар узкий.

Раньше мы лечили это пропсами isSmall, variant="sidebar" или сложным CSS. Теперь есть нативное решение Container Queries.

В чем суть?
Компонент теперь смотрит не на ширину окна браузера (viewport), а на ширину своего родительского контейнера.

Как использовать:

1. Обозначаем родителя как контейнер
Чтобы дочерние элементы могли "измерять" родителя, нужно явно включить этот режим.


.card-wrapper {
/* Включаем отслеживание изменения размера по горизонтали */
container-type: inline-size;

/* Опционально: даем имя, чтобы обращаться к конкретному контейнеру */
container-name: card-box;
}



2. Пишем стили для ребенка
Вместо @media используем @container.


.card {
display: flex;
flex-direction: column;
}

/* Если РОДИТЕЛЬ (.card-wrapper) шире 400px */
@container card-box (min-width: 400px) {
.card {
flex-direction: row; /* Перестраиваем в горизонталь */
gap: 20px;
}
}



Вы создаете по-настоящему изолированный компонент.
Вы можете кинуть этот компонент в футер, в модалку, в грид на 3 колонки или на весь экран, он сам подстроится под выделенное ему место. Вам больше не нужно знать контекст, где компонент будет использоваться.

📲 Мы в MAX

👉 @frontend_1
👍12