Frontender's notes [ru]
32.5K subscribers
680 photos
52 videos
3.51K links
Ведущий канал о современном фронтенде: статьи, новости, практики, вайбкодинг и автоматизация фронта ИИ-агентами.

Личный блог автора - @just_genych
По вопросам рекламы или разработки - @g_abashkin
Download Telegram
Forwarded from xCode Journal
🤣 Инновации подъехали, забирайте

✖️ xCode Journal
Please open Telegram to view this post
VIEW IN TELEGRAM
😁42👎1
React state machines против “простого state”

Принято считать, что state machine — это что-то из мира enterprise
и слишком сложных приложений.

Мол:

👉 обычного useState достаточно
👉 зачем усложнять
👉 у нас же «просто форма»


Проблема в том,
что «простой state» очень быстро перестаёт быть простым.


Почему обычный state начинает ломаться

Пока состояний мало —
всё выглядит нормально:


const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [success, setSuccess] = useState(false)


Но потом появляются вопросы:

👉 может ли быть одновременно loading и success?
👉 что делать при retry?
👉 можно ли отправить форму повторно?
👉 что если запрос отменился?


Количество комбинаций начинает расти взрывным образом.


В чём идея state machine

State machine заставляет явно описывать:

👉 какие состояния существуют
👉 какие переходы между ними разрешены

Например:


idle → loading → success
idle → loading → error
error → loading



Невалидные состояния просто невозможно получить.


Почему это мощно

Логика становится предсказуемой

Вместо набора флагов —
у тебя конечный автомат.

Не:

👉 loading = false
👉 success = true
👉 error = ???

А:

👉 приложение находится в состоянии success

Меньше impossible states

Обычный state легко приводит к странным комбинациям:

👉 loading + error
👉 success + loading
👉 success без данных


State machine убирает целый класс багов.


Проще масштабировать

Когда сценариев становится больше:

👉 retries
👉 optimistic updates
👉 multi-step flows
👉 async transitions

обычный state начинает разваливаться.

FSM переносит сложность
из хаоса в структуру.

Но есть trade-off

State machine — это дополнительная абстракция.

Для:

👉 modal с двумя состояниями
👉 простого toggle
👉 маленького local state

это может быть overengineering.

Где state machine особенно полезна

👉 формы с несколькими шагами
👉 async flows
👉 onboarding
👉 complex UI states
👉 websocket / realtime UI
👉 payment flows

Там, где логика переходов важнее самих данных.

Главная мысль

Проблема обычного state не в том,
что он плохой.


А в том, что набор boolean-флагов
не масштабируется вместе со сложностью UI.


State machine — это способ
сделать сложные состояния явными и контролируемыми.
👎10👍54👀2
Promise.withResolvers() — новый способ работы с Promise

В JavaScript появился Promise.withResolvers().


Штука маленькая,
но код с Promise реально становится чище.


Раньше обычно писали так:


let resolve
let reject

const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})


Работает,
но выглядит странно.

Особенно это:


«вынесем resolve наружу»


Теперь можно нормально:


const { promise, resolve, reject } =
Promise.withResolvers()


👉 без лишнего конструктора
👉 без мутаций переменных
👉 без промежуточного boilerplate

Где это особенно полезно

👉 очереди
👉 event-based логика
👉 deferred pattern
👉 кастомные async abstractions

Почему это приятно

Раньше приходилось:

👉 создавать Promise вручную
👉 тащить resolve/reject наружу
👉 писать шаблонный код снова и снова

Теперь API делает это нативно.


Мелочь, а ощущается как функция,
которая давно должна была появиться.
👍27👎31
starting-style — правильные enter animations без JS

В CSS появился @starting-style.


Он решает старую проблему:
анимацию появления элемента,
который только что добавили в DOM.


Раньше для этого обычно:

👉 делали setTimeout
👉 дёргали классы через JS
👉 ловили reflow-хаки

Теперь можно нативно:


.modal {
opacity: 1;
transition: opacity .2s;
}

@starting-style {
.modal {
opacity: 0;
}
}



Элемент появляется сразу с анимацией.
Без двойного рендера и JS-костылей.


Почему это важно

Раньше браузер не успевал увидеть
начальное состояние элемента.

Из-за этого приходилось:

👉 форсить reflow
👉 откладывать изменение класса
👉 городить лишнюю логику

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

👉 с какого состояния начинать transition

Где особенно полезно

👉 dialog
👉 popover
👉 conditional rendering
👉 enter animations в UI

Почему это ощущается приятно

Анимации становятся:

👉 чище
👉 предсказуемее
👉 без лишнего JS


CSS постепенно забирает себе всё,
что раньше приходилось городить через JavaScript.
👍31👎6😱3
Forwarded from xCode Journal
🤣 Мем отлично отражает настроения в сообществе прямо сейчас

✖️ xCode Journal
Please open Telegram to view this post
VIEW IN TELEGRAM
😁1711
Isolated declarations — ускорение больших monorepo

В TypeScript есть флаг isolatedDeclarations.


Он нужен не для красоты типов,
а для скорости.


Проблема простая:

в больших monorepo генерация .d.ts
может становиться узким местом.

TypeScript часто должен анализировать соседние файлы,
чтобы понять, какие декларации вывести.

На маленьком проекте это почти незаметно.


На большом — начинает болеть.


Что делает isolatedDeclarations

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

Из-за этого TypeScript чаще требует явные типы.

Было:


export function getUser() {
return { id: 1, name: 'Alex' }
}


Лучше так:


type User = {
id: number
name: string
}

export function getUser(): User {
return { id: 1, name: 'Alex' }
}



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


Почему это важно

Когда проект растёт:

👉 TypeScript начинает сильнее зависеть от соседних файлов
👉 инкрементальная сборка замедляется
👉 генерация типов становится дорогой


Изоляция помогает компилятору работать параллельно и проще.


Где это особенно полезно

👉 большие monorepo
👉 библиотеки
👉 project references
👉 параллельная сборка
👉 CI, где каждая минута стоит денег

Главный trade-off

Ты немного платишь:

👉 более явными типами
👉 меньшим type inference
👉 дополнительным boilerplate

Но взамен получаешь:

👉 более быстрые сборки
👉 стабильный compile pipeline
👉 меньше скрытой сложности

Главная мысль


Это хороший пример взрослого engineering trade-off:
чуть больше явности в коде
ради скорости и предсказуемости системы.
👍2513🔥10🐳2👎1
Recursive type limits — почему TS иногда «умирает»

В TypeScript можно написать тип,
который выглядит красиво,
но заставляет компилятор страдать.


Особенно когда начинаются рекурсивные типы.


Например:

👉 глубокий DeepPartial
👉 парсинг строк на уровне типов
👉 сложные conditional types
👉 infer внутри infer

На маленьком примере всё работает.


В реальном проекте IDE внезапно начинает думать по 5 секунд.


Почему так происходит

TypeScript не вычисляет типы «бесплатно».

Каждый:

👉 conditional type
👉 union
👉 recursive шаг

нужно реально посчитать.

А если тип разворачивается слишком глубоко,
компилятор упирается в лимиты.

Отсюда знакомое:


Type instantiation is excessively deep
and possibly infinite



И это не всегда баг TypeScript.


Часто это сигнал,
что типовая модель стала слишком умной.

Где обычно всё ломается

Особенно опасны:

👉 рекурсивные mapped types
👉 огромные union’ы
👉 type-level parser’ы
👉 deeply nested generics
👉 utility types поверх utility types


Типы начинают взрываться комбинаторно.


Что обычно помогает

👉 не делать type-level акробатику без нужды
👉 ограничивать глубину рекурсии
👉 разбивать типы на более простые
👉 добавлять явные промежуточные типы
👉 не тащить сложные generic-типы в публичный API

Почему это важно

Сложные типы бьют не только по компиляции.

Они ухудшают:

👉 autocomplete
👉 responsiveness IDE
👉 читаемость кода
👉 onboarding новых людей


Иногда самый дорогой runtime —
это compile time.


Главная мысль

Хороший TypeScript —
это не когда типы поражают воображение.


Хороший TypeScript —
это когда их можно понять через полгода,
а IDE при этом не превращается в обогреватель.
9🔥5👍4👎2
ES2025: Импорт JSON-файлов как модулей

Введение
С выходом ECMAScript 2025 (ES2025) разработчики получили возможность напрямую импортировать JSON-файлы как модули в JavaScript-коде. Это упрощает работу с конфигурационными данными и другими статическими ресурсами, представленными в формате JSON.

Синтаксис импорта JSON-модулей
Для импорта JSON-файла используется ключевое слово import с указанием атрибута with { type: 'json' }. Это гарантирует, что импортируемый файл будет обработан как JSON-модуль.

Пример использования
Рассмотрим пример импорта конфигурационного файла config.json и обращения к его свойствам в коде.

import config from './config.json' with { type: 'json' };

.log(config.apiUrl); // Выводит значение свойства apiUrl из config.json
console.log(config.timeout); // Выводит значение свойства timeout из config.json


❗️Добавление поддержки импорта JSON-файлов как модулей в ES2025 упрощает работу с данными в формате JSON, делая код более чистым и понятным.

Источники
JSON Modules Can Now Be Imported in JavaScript in All Modern Browsers, CSS Modules to Follow.
New Features in ES2025 – BooleanBuffer.
🔥11👍2
Мой код после сотен правок и костылей
1😁637😱6👍1
Forwarded from xCode Journal
🤣 Не баг, а фича

✖️ xCode Journal
Please open Telegram to view this post
VIEW IN TELEGRAM
😁311👍1
Variadic tuple types — сложные сигнатуры без боли

До variadic tuple types
многие сложные сигнатуры в TypeScript
выглядели как наказание.

Особенно:

👉 curry
👉 compose
👉 middleware
👉 typed event emitter
👉 любые функции с «прокинь аргументы дальше»


Приходилось писать overload на overload
и дублировать типы вручную.


Как было раньше

Обычно появлялись:

👉 overload на overload
👉 ручные tuple-типы
👉 тонны дублирования

Типы быстро превращались
в нечитаемую простыню.

Что изменили variadic tuples

С их появлением стало намного проще
работать с остаточными аргументами на уровне типов.

Например:


type Fn<T extends unknown[]> =
(...args: [...T]) => void


Или собирать сигнатуры:


type Append<Args extends unknown[], Arg> =
[...Args, Arg]



Типы наконец научились нормально работать
с «переменным количеством аргументов».


Почему это важно

На практике это одна из тех TS-фич,
которые реально упростили жизнь библиотекам.

Без variadic tuples:

👉 Redux middleware typings
👉 router APIs
👉 compose/curry utilities

были бы ещё страшнее.

Где начинается тёмная магия

Проблемы начинаются,
когда variadic tuples комбинируют с:

👉 infer
👉 recursive types
👉 conditional types


Типовая система очень быстро
превращается в тёмный лес.


IDE начинает тормозить,
ошибки становятся нечитаемыми,
а compile time — расти.

Главная мысль

Variadic tuple types —
это действительно мощная фича.


Главное —
вовремя остановиться
и не превратить типы в отдельный язык программирования.
👎7👍4👀31
Страшная тайна российского айти

✖️ xCode Journal
Please open Telegram to view this post
VIEW IN TELEGRAM
😁51👎6😱2👍1
AbortSignal.any() и AbortSignal.timeout(): единая отмена fetch, таймеров и async-операций в production

Promise.race([fetch(), timeout]) часто маскирует проблему: ждать перестали, но работа могла не остановиться. В SPA, SSR, Node.js-сервисах, SDK и API clients это приводит к висящим запросам, таймерам и retry/backoff ниже по стеку.

Собирайте отмену вокруг AbortSignal

AbortSignal.timeout(ms) сам отменится по таймауту.
AbortSignal.any([...signals]) отменится, когда отменится любой входной signal.

Так в один контракт попадают:
* caller отменил операцию
* истек timeout
* клиент закрыл соединение
* сервис уходит в graceful shutdown

const shutdown = new AbortController();

async function loadUser(id: string, opts: { signal?: AbortSignal } = {}) {
const signal = AbortSignal.any([
AbortSignal.timeout(1500),
shutdown.signal,
...(opts.signal ? [opts.signal] : []),
]);

signal.throwIfAborted();

const res = await fetch(`https://api.example.com/users/${id}`, { signal });

await sleep(100, signal); // backoff тоже отменяем

const data = await res.json();
signal.throwIfAborted();

return data;
}


Типичная ошибка

Не останавливайтесь на fetch. Один и тот же signal стоит передавать в retry, polling, очереди, sleep/timer helpers и свои async-функции. Иначе верхний слой "отменился", а нижний продолжает держать ресурсы.

Практические нюансы

* AbortSignal одноразовый: если aborted === true, нужен новый controller или timeout
* AbortSignal.any() - это fan-in, а не fan-out: он не отменяет исходные контроллеры
* смотрите на reason: timeout часто дает TimeoutError, abort - AbortError или ваш reason
* при обертках над setTimeout, stream, listener или socket чистите ресурсы при abort

Вывод:
Отмена должна быть частью контракта async-функции, а не локальным Promise.race на краю системы.
👍103👎3🔥2
АЙТИШНИКИ БЕСПЛАТНОЕ ОБУЧЕНИЕ сборник курсов, инструментов и книг

Проект «TERMINAL» стал крупнейшей библиотекой бесплатного образования. В одном канале собраны курсы, книги, полезные инструменты и практические тренажёры для всех разработчиков

🎓 Практические курсы и задания

🪽 Книги и статьи известных авторов

😮‍💨 Полезные инструменты и ресурсы

🌟 IT-новости и инсайды

Обучение по всем направлениям: SQL, Python, Frontend, PHP, C++, Golang, GIT, Linux, QA, Java, Vibe-coding, Infosec и др.

Ценишь знания, подпишись: Terminal_tg
Please open Telegram to view this post
VIEW IN TELEGRAM
2
AsyncLocalStorage в Node.js: request-scoped контекст для логов, трассировки и транзакций без prop drilling

В production это нужно в Node.js-сервисах, SSR, API integration и backend-for-frontend слоях, где requestId, traceId, tenant или транзакция должны проходить через async-код. Частая ошибка - протаскивать ctx через десятки методов или, наоборот, прятать в нём бизнес-состояние.

Как это выглядит

AsyncLocalStorage использует async_hooks и привязывает store к async execution flow: promise, timeout, I/O callback и большинству стандартных API Node.js.

type Ctx = { requestId: string; traceId?: string; tx?: unknown };

const als = new AsyncLocalStorage<Ctx>();

app.use((req, _res, next) => {
als.run({
requestId: req.headers['x-request-id']?.toString() ?? randomUUID(),
traceId: req.headers.traceparent?.toString(),
}, next);
});

const getCtx = () => als.getStore();

function logInfo(msg: string, meta = {}) {
const c = getCtx();

logger.info({
requestId: c?.requestId,
traceId: c?.traceId,
...meta,
}, msg);
}


Теперь сервисный код не знает про HTTP middleware и req, но логи автоматически получают correlation metadata.

Транзакции без протаскивания tx

Для DB слоя можно делать withTransaction(), который запускает als.run({ ...ctx, tx }, fn), а getDbExecutor() возвращает ctx.tx ?? db. Trade-off хороший: меньше шума в API сервисов, но граница ответственности остаётся инфраструктурной, а не бизнесовой.

Где границы

* не заменяйте аргументы функции: бизнес-данные передавайте явно;
* не кладите в store req, res, большие payload или ORM graph;
* context не пересекает process boundary: worker threads, очереди, cron jobs и другие сервисы требуют явной передачи ids;
* нестандартные callback-based библиотеки могут разорвать async chain.

Практическое правило: используйте AsyncLocalStorage для логов, tracing, tenant/user metadata, аудита и текущей транзакции, а не для скрытого глобального состояния.

Вывод:
AsyncLocalStorage полезен, когда request-scoped инфраструктурный контекст улучшает observability и DX, но не размывает явные границы бизнес-логики.
👀4👍32
Совет на ближайшие годы — изучайте ВАЙБ-КОДИНГ

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

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

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

Подписывайтесь, нас уже 45 тысяч: @vibecoding_tg
👎21😱2😁1