en
Feedback
Frontender's notes [ru]

Frontender's notes [ru]

Open in Telegram

Ведущий канал о современном фронтенде: статьи, новости, практики, вайбкодинг и автоматизация фронта ИИ-агентами. Личный блог автора - @just_genych По вопросам рекламы или разработки - @g_abashkin

Show more

📈 Analytical overview of Telegram channel Frontender's notes [ru]

Channel Frontender's notes [ru] (@frontendnoteschannel_ru) in the Russian language segment is an active participant. Currently, the community unites 32 276 subscribers, ranking 4 215 in the Technologies & Applications category and 20 090 in the Russia region.

📊 Audience metrics and dynamics

Since its creation on невідомо, the project has demonstrated rapid growth, gathering an audience of 32 276 subscribers.

According to the latest data from 30 June, 2026, the channel demonstrates stable activity. Although there has been a change in the number of participants by -332 over the last 30 days and by -12 over the last 24 hours, overall reach remains high.

  • Verification status: Not verified
  • Engagement rate (ER): The average audience engagement rate is 7.74%. Within the first 24 hours after publication, content typically collects 4.63% reactions from the total number of subscribers.
  • Post reach: On average, each post receives 2 498 views. Within the first day, a publication typically gains 1 495 views.
  • Reactions and interaction: The audience actively supports content: the average number of reactions per post is 13.
  • Thematic interests: Content is focused on key topics such as браузер, api, css, интерфейс, загрузка.

📝 Description and content policy

The author describes the resource as a platform for expressing subjective opinions:
Ведущий канал о современном фронтенде: статьи, новости, практики, вайбкодинг и автоматизация фронта ИИ-агентами. Личный блог автора - @just_genych По вопросам рекламы или разработки - @g_abashkin

Thanks to the high frequency of updates (latest data received on 01 July, 2026), the channel maintains relevance and a high level of publication reach. Analytics show that the audience actively interacts with content, making it an important point of influence in the Technologies & Applications category.

32 276
Subscribers
-1224 hours
-897 days
-33230 days
Posts Archive
Типизация императивных handlebars-шаблонизаторов в JSX через branded типы и кастомные JSX-фабрики Переход с Handlebars на React часто оставляет за собой строковые шаблоны с {{#each}} и {{#if}}. Прямая передача таких строк в JSX ломает TypeScript, но есть способ это контролировать на уровне типов. Branded типы как контракт данных Branded тип - это строка с уникальным символом, которая не может быть создана без специальной фабрики. Обычная строка не скомпилируется в такой тип, что предотвращает случайную передачу сырого шаблона.
type HandlebarsTemplate = string & { __brand: 'Handlebars' };

function hbs(strings: TemplateStringsArray, ...values: any[]): HandlebarsTemplate {
  return strings.reduce((acc, str, i) => acc + str + (values[i] || ''), '') as HandlebarsTemplate;
}

function jsx(tag: string, props: { template?: HandlebarsTemplate } | null, ...children: any[]) {
  if (tag === 'Template' && props?.template) {
    return compileAndRender(props.template);
  }
  return React.createElement(tag, props, ...children);
}
Типичная ошибка и её устранение Если передать сырую строку напрямую, TypeScript выдаст ошибку:
// ❌ Ошибка: Type 'string' is not assignable to type 'HandlebarsTemplate'
<Template template="<div>{{user}}</div>" />
Корректный вариант с фабрикой:
const template = hbs&lt;div&gt;{{#if user}}Hello {{user.name}}{{/if}}&lt;/div&gt;;

function MyComponent() {
  return <Template template={template} />;
}
Production-oriented практика Чтобы кастомная JSX-фабрика работала, настрой Babel или TypeScript: добавь @jsx jsx в начале файла или укажи jsxFactory: 'jsx' в tsconfig. Для гибридных проектов это гарантирует, что ни один сырой шаблон не проскочит в рендер без компиляции. Если шаблон использует переменные контекста, добавь generics для типизации payload:
function hbs<T>(strings: TemplateStringsArray, ...values: any[]): HandlebarsTemplate<T>;
Trade-offs Дополнительный слой абстракции усложняет читаемость и увеличивает bundle, если шаблонов много. Используй такой подход только в пограничных сценариях миграции, а не как постоянную практику. Вывод: Branded типы и кастомные JSX-фабрики позволяют безопасно встраивать императивные шаблоны в декларативный JSX, предотвращая runtime-ошибки на этапе компиляции.

Typed custom hooks: constraints и infer для строгих конфигов Часто вижу, как custom hooks принимают конфиги, где TypeScript выводит mode: string вместо 'edit' | 'view', а permissionsstring[] вместо конкретных литералов. В production это ведет к потере автодополнения и багам на стороне потребителей. Constraints: фиксируем рамки Используй extends уже на уровне дженериков хука, чтобы сузить допустимые типы. Без него TS выводит широкие типы, с ним — точные литералы, если исходные данные переданы с as const.
function useFeature<M extends string, P extends string[]>(config: {
  mode: M;
  permissions: P;
}) {
  // TS: M, P как литералы
}
Infer: выводим сложные зависимости Когда конфиг содержит вложенные коллбэки или динамические ключи, infer в условных типах автоматически извлекает нужные типы из переданного объекта. Это спасает от ручного аннотирования.
type ConfigResult<T> = T extends { mode: infer M; permissions: infer P }
  ? { mode: M; permissions: P }
  : never;
Типичная ошибка: забытый as const Без as const TS выведет string, а не литералы. А если конфиг приходит из API — этот подход бесполезен. Зато для статически заданных конфигов в хуках (фичи, A/B-тесты, permission guards) это чистый профит: типы не протекают, код самодокументируется. Вывод: Constraints с extends и вывод через infer дают строгую типизацию factory-функций и конфигурационных объектов без потери читаемости и с проверками на уровне компиляции.

Попросили Claude создать WCAG-доступный DataPicker на React и потратили 3 дня на доработки Выбор даты кажется небольшой задач
Попросили Claude создать WCAG-доступный DataPicker на React и потратили 3 дня на доработки Выбор даты кажется небольшой задачей в UI, пока не попробуешь сделать его по-настоящему WCAG-доступным. Нам понадобился настраиваемый DataPicker на React для процесса записи на прием к врачу, где пользователи, работающие с keyboard navigation, и люди, использующие screen reader’ы, должны были выбрать дату без лишних затруднений. Claude сделал нам хорошую первую версию: структуру компонента, ARIA-атрибуты, базовую keyboard navigation и логику календаря. На первый взгляд результат выглядел почти готовым. Затем мы запустили NVDA, VoiceOver и протестировали сценарий keyboard navigation. Фокус выходил за пределы диалогового окна; некоторые даты озвучивались неверно; переключение между месяцами сопровождалось слишком тихим звуком; нажатие клавиши «Esc» закрывало календарь, но оставляло пользователя без контекста; режим высокой контрастности Windows нарушал отображение выбранного состояния. Код выглядел нормально, но UX оставлял желать лучшего. В этой статье мы рассмотрим реальную работу, стоящую за WCAG-доступным DataPicker'ом: где AI сэкономил нам время; где он не справился; как нам помог WAI-ARIA APG ; какие детали нам пришлось исправлять вручную и почему доступность нельзя проверить, просто прочитав сгенерированный код. Читать далее

Два способа создания доступного DatePicker'а с помощью AI: 80/20 или системное проектирование DatePicker потребовался на Reac
Два способа создания доступного DatePicker'а с помощью AI: 80/20 или системное проектирование DatePicker потребовался на React и TypeScript с корректной работой keyboard navigation, screen reader'а, управляемым состоянием и проверками доступности. Первый способ — дать AI четкий запрос, получить 80% кода, остальное доработать руками. Модель генерирует структуру календаря, атрибуты ARIA, базовую keyboard navigation и логику работы с датами. Затем начинаются проблемы: поведение фокуса становится нестабильным, возникают конфликты обработчиков событий, озвучивание screen reader'ами требует тестирования, небольшое изменение в логике может нарушить календарь. Второй способ — системное проектирование с AI-агентом. Включает PRD, декомпозицию задач, правила агента, внешнюю верификацию, Vitest, Playwright, сборку Vite, проверки типов и строгий цикл: агент не может двигаться дальше, пока не пройден текущий шаг. Далее разбирается, в чем AI действительно помог, где начал сбиваться, почему одного большого запроса недостаточно, как The Verifier изменил процесс и почему задача инженера сводится к контролю над замыслом, архитектурой, контрактами и стоимостью изменений. Читать далее на Habr

React 19: ErrorBoundary с типизацией, error.cause и строгое логирование Границы ошибок в React долго выглядели как костыль для асинхронщины: раньше ErrorBoundary отлавливал только синхронные ошибки, а запросы с then/catch просто пролетали мимо. В 19 версии это починили через use и новые хуки, но типизация все равно остается местом, где можно наступить на грабли в production. Интерфейс и цепочка ошибок Интерфейс ErrorBoundaryState с Error | null, getDerivedStateFromError ловит ошибку, componentDidCatch отправляет в лог. Но что действительно спасает в реальных кейсах — это error.cause из ES2022. Когда fetch падает с 401, ты пробрасываешь не строку, а объект: * error.cause?.status для проверки статуса * error.cause?.message для текста
interface AppError extends Error {
  cause?: {
    status: number;
    message: string;
  };
}
Production: сужение и безопасность Передавать полный stack клиенту — плохая идея. Там пути файлов и внутренние адреса. Мы обрезаем первые 200 символов, если ошибка некастомная, и никогда не светим причину наружу: error.cause только во внутренний логгер. Схема логирования через instanceof: * если error instanceof AppError — пишем структурированно с типом, причиной и стеком * если просто Error — обрезаем стек, кидаем предупреждение * если вообще не Error — логируем как строку и показываем generic fallback Типичная ошибка instanceof не работает, если ошибка прилетела из другого iframe или realm. Там error.cause может быть сериализованным объектом, и придется проверять по полям вручную. Встречал такое при интеграции с микросервисными виджетами. Еще один момент — не засовывай всю логику в componentDidCatch: вынеси ее в отдельную функцию handleFatalError с возвратом never и переиспользуй, иначе при рефакторинге придется переписывать каждый Boundary. Вывод: Типизированный ErrorBoundary с error.cause и строгим логированием через instanceof — это надежный паттерн для обработки асинхронных ошибок, который дает консистентность и безопасность в production. Источники: React docs про Error Boundaries, MDN про Error.cause, React 19 release notes про use() hook и async errors.

Типизация ErrorBoundary в React 19: асинхронные ошибки, error.cause и production-логирование ErrorBoundary долго был слабым звеном — он не ловил ошибки из промисов, useEffect или setTimeout. React 19 не меняет классовый подход, но появление error.cause и правильная типизация через instanceof делают обработку асинхронных ошибок предсказуемой и безопасной. Почему any в state — ошибка Состояние границы должно быть строго типизировано: Error | null. В getDerivedStateFromError проверяй cause через instanceof Error, не через truthy check. Это исключает мусор из продакшена, где cause может быть строкой, числом или undefined. Production-кейс с fetch При HTTP 500 выбрасывай:
throw new Error('HTTP error', { cause: res.status })
В границе проверяй:
if (error.cause instanceof Error) {
  // Логируем полный стек
} else if (typeof error.cause === 'number') {
  // Игнорируем, если статус < 500
}
Это позволяет не слать в Sentry временные сетевые сбои. Типизация cause и instanceof Используй instanceof для разграничения классов ошибок — HTTPError, NetworkError, ValidationError. Так ты контролируешь, что идёт в Sentry, а что — только в консоль. Без этого логгер захлебнётся шумом. Вывод: Типизированный ErrorBoundary с error.cause и instanceof превращает асинхронные ошибки из невидимых багов в управляемые инциденты.

Как вывести типы контекстов во вложенных layout без ручных интерфейсов в React Router v7 Если вы используете TanStack Router (React Router v7) с вложенными layout, то наверняка сталкивались с ситуацией, когда родительский layout прокидывает loader-контекст, а дочерний компонент вынужден вручную объявлять интерфейс и кастить. Типовая ошибка — забыть обновить интерфейс при изменении структуры данных, что ведет к багам на проде. Как работает типовая цепочка Система типов строится вокруг дженериков createRoute. Каждый роут наследует контекст от родителя через getParentRoute(). TypeScript автоматически мержит результаты loader родительского и текущего роута:
const rootRoute = createRootRoute()({
  loader: () => ({ user: 'John' })
});

const layoutRoute = createRoute({
  getParentRoute: () => rootRoute,
  loader: ({ context }) => ({
    theme: context.user === 'John' ? 'dark' : 'light'
  })
});

const pageRoute = createRoute({
  getParentRoute: () => layoutRoute,
  loader: ({ context }) => {
    // context: { user: string, theme: string }
    return context.theme;
  }
});
Ни одного явного интерфейса или каста. Система выводит тип контекста из цепочки родительских роутов. Production-кейс: аутентификация с правами Практический пример: вложенный layout с авторизацией. Родитель кладет user, дочерний — permissions:
const authLayout = createRoute({
  getParentRoute: () => rootRoute,
  loader: () => ({ user: fetchUser() })
});

const adminLayout = createRoute({
  getParentRoute: () => authLayout,
  loader: ({ context }) => ({
    permissions: fetchPermissions(context.user.id)
  })
});
Страница получает { user, permissions } без ручного объявления. Если переименовать user в currentUser — TypeScript подсветит все места, где используется старый ключ. Типичная ошибка и trade-off Ошибка: ручное объявление interface IContext = { user: string } и последующий as any при несоответствии. Это ломает всю type-safety и ведет к runtime-ошибкам при изменении данных. Trade-off: полагаться на вывод типов чуть сложнее читать в IDE, чем явные интерфейсы, но выигрыш в надежности при рефакторинге — код сам документирует контракты, и компилятор ловит несоответствия. Вывод: Используйте автоматический вывод типов контекстов через цепочку getParentRoute, чтобы избавиться от ручных интерфейсов и кастингов, повысив надежность и упростив рефакторинг в production-коде.

Signal vs Observable: почему ваш граф зависимостей взрывается в production Когда в корзине интернет-магазина total = price * qty, затем скидка от суммы, затем налог — Observable (RxJS) на каждое изменение price пересчитывает все подписки, даже если данные не изменились. Разработчики навешивают distinctUntilChanged на каждую цепочку, и через месяц граф превращается в лапшу. Как работают Signal Signal (SolidJS, Vue 3.4+, Preact Signals, Svelte 5) делает вычисления ленивыми. Он отслеживает, какие сигналы реально читаются в computed, а не подписывается на всё подряд. Два сигнала, зависящих от одного источника, не подписываются дважды. При изменении источника пересчитываются только те, кто на него ссылается, а не всё дерево. Пример на псевдокоде Solid:
count = signal(1)
price = signal(100)
total = computed(() => count() * price())
discount = computed(() => total() > 500 ? 0.1 : 0)
final = computed(() => total() * (1 - discount()))
При изменении count пересчитываются total и final. Discount пересчитывается только если total пересечет порог. В RxJS через combineLatest discount будет пересчитываться на каждое изменение count и price, даже если total не изменился. Меньше кода — больше бесполезной работы. Когда Observable все еще нужен Observable (RxJS) хорош для асинхронных цепочек: debounce, switchMap, WebSocket. Это декларативные потоки, где важны трансформации во времени. Но при сотне зависимостей начинается цирк: каждый чих триггерит пересчёт, дебажить dependency hell — отдельный квест. Типичная ошибка — использовать Observable для синхронного графа зависимостей, где сигналы дают ровно то же самое без лишних пересчётов. Практический совет Если у вас в проекте уже RxJS — не переписывайте всё. Для нового фича с interdependent состояниями (формы, редакторы, дашборды) закладывайтесь на сигналы. В продакшене с сотнями зависимостей сигналы дают на 30-50% меньше бесполезных перерисовок и упрощают отладку: граф зависимостей прозрачен, а не взрывается на каждой итерации. Вывод: Signal — для синхронных графов с предсказуемыми изменениями, Observable — для event-driven асинхронных потоков, и выбор между ними — это trade-off между производительностью вычислений и гибкостью трансформаций.

🤣 Общаться о синтаксисе теперь тоже можно через агентов ✖️ xCode Journal
🤣 Общаться о синтаксисе теперь тоже можно через агентов ✖️ xCode Journal

requestAnimationFrame и React-батчинг: почему дёргается drag-and-drop Сделал перетаскивание элементов в production? На быстрых движениях начинаются дёргания и выпадающие кадры. Проблема знакомая: requestAnimationFrame вызывается до того, как React применяет батч из setState. Особенно если rAF висит на mousemove вне реактовских обработчиков. В React 18+ батчинг работает в рамках одного события, но rAF срабатывает до мерджа обновлений. Результат: анимация читает старую позицию, React применяет новые значения с задержкой, элемент дёргается. Решение с flushSync Для чтения DOM-значений (позиция, размер) используй useLayoutEffect — выполняется до показа кадра. Внутри drag-логики добавь синхронный вызов:
const handleMouseMove = (e: MouseEvent) => {
  ReactDOM.flushSync(() => {
    setPos((prev) => ({
      x: prev.x + e.movementX,
      y: prev.y + e.movementY
    }));
  });
  requestAnimationFrame(updateStyles);
};
flushSync заставляет React применить батч синхронно перед следующим кадром. Дёргания уходят. Предупреждение Не злоупотребляй flushSync — он ломает Concurrent Mode и режет производительность, если вызывать на каждом mousemove. Типичная ошибка: считать его универсальным решением без оценки trade-offs. Альтернативы - Замени mousemove/touchmove на pointer-events — они синхроннее в браузере. - Если хочешь не трогать React-состояние внутри кадра, храни позиции в useRef и обновляй DOM руками через rAF. Но это редкий кейс, и он разрушает реактивность. Вывод: requestAnimationFrame и React-батчинг конфликтуют по умолчанию — контролируй синхронизацию через flushSync или выноси анимации за пределы реактовского цикла, иначе даже гладкий код споткнётся на быстром перемещении.

Почему useSyncExternalStore проигрывает Zustand и Jotai на композитных сторах и как селекторы убивают производительность Пытался вчера прикрутить useSyncExternalStore к композитному стору в React 19. Думал, что если это API из коробки, то и проблем с производительностью не будет. На деле — пришлось переписывать половину. Бенчмарк на 1000 подписчиков: примитивы vs вложенные объекты На простом сторе (один селектор, один примитив) разница в микросекундах — Jotai ~0.15ms, Zustand ~0.18ms, useSyncExternalStore ~0.22ms. Можно не париться. Как только в сторе появляется вложенный объект — начинается треш. Zustand без shallow перерисовывает всех подписчиков при любом изменении. Даже если твой селектор брал только state.user, а кто-то обновил state.posts. Причина: Zustand при каждом вызове селектора сравнивает результат через Object.is с предыдущим. Если селектор возвращает новый объект — привет, ререндер. Jotai с производными атомами — та же петрушка. atom(get => { return { user: get(userAtom), posts: get(postsAtom) } }) — создаёт новый объект на каждый вызов. Jotai также сравнивает через Object.is, так что ререндер гарантирован. Грабли с селекторами: деструктуризация убивает производительность Ошибка, которую я вижу на каждом проекте — деструктуризация в теле селектора:
const { user, posts } = useStore(state => ({
  user: state.user,
  posts: state.posts
}))
Каждый вызов селектора создаёт новый объект. Object.is никогда не вернёт true. Правильный подход — писать селекторы на отдельные поля:
const user = useStore(state => state.user)
const posts = useStore(state => state.posts)
Тогда Object.is вернёт true, если ссылка не изменилась. Дополнительные меры: Zustand — используйте shallow для сравнения. Jotai — atomWithComparator. Экономия до 40% ререндеров. Когда useSyncExternalStore — зло для бизнес-логики useSyncExternalStore вообще не про бизнес-логику. Его пилили под внешние источники: WebSocket, IndexedDB. Внутри React он не батчит подписки, так что при 1000 подписчиков и частых изменениях — 1.2ms на ререндер. Jotai с мемоизацией — 0.3ms. Zustand с shallow — 0.6ms. Разница в 4 раза — это уже не погрешность. Сейчас на проекте простая архитектура: простые состояния — Zustand, меньше кода. Композитные данные — Jotai с atomFamily и splitAtom. useSyncExternalStore — только если прикручиваешь что-то внешнее. Вывод: Микрооптимизация селекторов и отказ от композитных возвращаемых объектов — это не premature optimization, а разница между 1.2ms и 0.3ms ререндера при масштабировании на 1000+ подписчиков.

Типизация WebSocket событий: как discriminated unions + brand checks делают стейт-машину предсказуемой и спасают от багов с payload Каждый, кто писал real-time фичи на React или Vue, сталкивался: диспатчишь событие WebSocket, а payload — не того типа. Структура похожа, семантика разная, и TypeScript молчит. Ошибка уходит в production. Discriminated unions — база, но не панацея Стандартный подход: поле type сужает остальные поля. Но если два события содержат sessionId с разным смыслом, TS не заметит подмены. На уровне типов всё ок, в рантайме — треш. Brand checks — второй рубеж Добавьте фиктивное поле __brand: 'open' в payload. В рантайме его нет, типы не гоняют лишние данные. Компилятор не даст смешать payload разных событий. Это имитация номинативной типизации в структурной системе TypeScript. Стейт-машина перестаёт принимать невесть что: переходы становятся предсказуемыми. Пример типизации:
type WSMessage =
  | { type: 'OPEN'; payload: { sessionId: string } & { __brand: 'open' } }
  | { type: 'MSG'; payload: { userId: number; text: string } & { __brand: 'msg' } }
  | { type: 'ERROR'; payload: { error: string } & { __brand: 'err' } };
Типичная ошибка Думать, что brand checks — это всё. WebSocket может прислать что угодно. Типы не панацея. В production обязательно докидывайте валидацию схемы на каждое входящее сообщение через Zod или io-ts. Практический совет Используйте brand checks как инструмент для разработчика, чтобы не протащить payload не туда при сборке стейта. А рантайм-валидацию — как первую линию защиты от кривых данных с сервера. Вместе они дают надёжность и предсказуемость в real-time архитектуре. Вывод: Discriminated unions сужают типы по значению, brand checks защищают от семантических багов, а рантайм-валидация — от любых неожиданностей с сервера: три уровня защиты для production-grade стейт-машины.

Реактивный DataV: оптимизация 10k+ точек с event-driven аннотациями и hit-тестингом Когда на графике 10 000+ точек, часто выбирают: производительность или интерактивность. Прямой рендер в SVG тормозит, Canvas летает, но теряет hover и click. Это ложный компромисс. Гибридный подход Решение — Canvas для рендера точек (никаких DOM-узлов, только пиксели). Hover и click выносятся в event-driven аннотации через raycasting. Считай только при движении мыши, а не на каждый фрейм. Ленивые аннотации и кеширование Рендери только те аннотации, которые попадают в viewport, и кешируй результат. Иначе каждый чих дергает рендер на 10k точках. Пример hit-тестинга Без фреймворков, на TypeScript:
class ScatterPlot {
  handleMouseMove(e) {
    for (const p of this.points) {
      if (Math.abs(mouseX - p.x) <= p.r * 2 &&
          Math.abs(mouseY - p.y) <= p.r * 2) {
        if (Math.hypot(mouseX - p.x, mouseY - p.y) <= p.r) {
          this.showAnnotation(i);
          return;
        }
      }
    }
  }
  showAnnotation(index) {
    requestAnimationFrame(() => this.renderAnnotationLayer());
  }
}
Сначала проверка по bounding box, потом по расстоянию. Для 10k точек O(n) срабатывает за пару миллисекунд. QuadTree даёт O(log n), но не всегда нужен. Предупреждение о типичной ошибке Главный подвох: не забудь сбросить аннотацию при уходе мыши с точки. Иначе график зависнет с подписями на все 10k, и пользователь решит, что интерфейс сломался. Вывод: Event-driven аннотации с hit-тестингом — это trade-off между точностью интерактивности и производительностью рендера, где Canvas даёт скорость, а raycasting сохраняет UX.

🤯 Девушка получила оффер в OpenAI и поделилась своим опытом поиска работы Внутри статьи она подробно расписывает этапы собес
🤯 Девушка получила оффер в OpenAI и поделилась своим опытом поиска работы Внутри статьи она подробно расписывает этапы собеседований, лайфхаки и делится учебными ресурсами, которые ей помогли. Плюс девушка великодушно оставила ссылки на свой Notion с полезными заметками по математике и LLM. ✖️ xCode Journal

Мемоизация в React 19: когда useMemo и memo уже не панацея, а новые use() и React Forget меняют правила игры Сколько раз вы писали useMemo(() => expensiveComputation(a, b), [a, b]) и сомневались, нужно ли это вообще? Или оборачивали компонент в memo, надеясь, что поверхностное сравнение пропсов не окажется дороже самого рендера? В React 19 старые подходы перестают быть единственно верными: появились инструменты, которые делают ручную мемоизацию узким, а не основным решением. Почему старые инструменты не идеальны Мемоизация не бесплатна. useMemo хранит ссылки и пересчитывает зависимости при каждом рендере. memo добавляет сравнение пропсов, и если дерево сложное, а данные меняются часто, выигрыша нет. В production я видел проекты, где 80% вызовов useMemo и useCallback не давали прироста — это была оптимизация ради оптимизации без профилирования. Типичная ошибка: мемоизировать все подряд, а не только горячие пути. Новый хук use() — отказ от явного кеширования промисов Хук use() компилируется в синхронный рендер и убирает необходимость вручную кешировать асинхронные данные. Пример: вместо того чтобы писать useEffect с загрузкой и оборачивать результат в useMemo, вы просто используете use(fetchUsers()). Компилятор сам решает, когда перерендерить компонент, без лишних проверок и ссылочных сравнений. Это особенно полезно для SSR и Suspense — мемоизация тут не нужна, рендер планируется автоматически. React Forget — компилятор как замена ручной оптимизации React Forget анализирует граф вызовов и сам встраивает кеширование. Если функция вызывается с теми же аргументами, компилятор возвращает прошлое значение, причём без утечек памяти. Разработчику не надо думать, где ставить useMemo — это происходит на уровне анализа зависимостей. Практический совет: используйте это на побочных проектах уже сейчас, но не спешите переписывать всё в проде. Для 95% сценариев React Forget достаточно, но на очень горячих путях — например, рендер таблицы на 10 тысяч строк — ручной useMemo всё ещё может быть оправдан. Типичная ошибка: мемоизация как архитектурный костыль Если ваш код на 80% состоит из useMemo и useCallback, это повод пересмотреть архитектуру, а не добавлять новые обёртки. use() берёт на себя загрузку данных, Suspense — ленивую подгрузку, а React Forget — всё остальное. Главный trade-off: экономия времени на ручной мемоизации против возможных дополнительных рендеров на этапе доработки компилятора. В production это редко становится проблемой, если дерево компонентов спроектировано с учётом React Forget. Вывод: Будущее за компиляторами, которые избавляют от рутины — useMemo и memo остаются инструментами для узких горячих путей, а не ежедневной практикой.

Object.assign() и structuredClone() в React стейте: баги с прототипами и Date При immutable-обновлении вложенных объектов в React стейте разработчики часто полагаются на поверхностное копирование через spread или Object.assign(), забывая, что эти методы не сохраняют прототипы и сериализуют Date в строку. Это приводит к тихим багам, которые проявляются только в production при работе с классами или сложными типами. Баг с прототипами: Object.assign() обнуляет методы класса Когда объект содержит экземпляр класса, Object.assign({}, obj) копирует только собственные свойства, игнорируя прототип. Например, методы полностью теряются:
class User {
  constructor(name) { this.name = name; }
  greet() { return Hi, ${this.name}; }
}

const original = new User('Alice');
const copy = Object.assign({}, original);
console.log(copy.greet()); // TypeError: copy.greet is not a function
structuredClone() решает эту проблему для сериализуемых типов, но классы с методами не поддерживаются — код упадет с DOMException. Решение: если в стейте лежат инстансы классов, используйте кастомную функцию глубокого копирования с проверкой прототипа через Object.getPrototypeOf. Баг с Date: structuredClone() превращает дату в строку structuredClone() сериализует Date в строку, как JSON.stringify. После клонирования методы Date (getTime, toISOString) перестают работать:
const state = { date: new Date('2024-12-25') };
const clone = structuredClone(state);
console.log(clone.date.getTime()); // TypeError: clone.date.getTime is not a function
В React стейте это особенно опасно для UI-компонентов, которые полагаются на методы Date (например, календари или таймеры). Типичная ошибка: разработчики думают, что structuredClone() копирует Date нативно, но по спецификации оно заменяет Date на строку UTC. Совет: используйте structuredClone только для данных, которые проходят полный список сериализуемых типов (Map, Set, ArrayBuffer), а для Date заводите отдельный хелпер или библиотеку вроде Immer. Trade-off: поверхностное vs глубокое копирование - Поверхностное копирование (spread, Object.assign) быстрое и простое, но не подходит для вложенных структур с прототипами или ссылочными типами (Date, Map). Риск: мутация общего стора через ссылки. - Глубокое копирование (structuredClone, Immer) безопаснее, но медленнее и несовместимо с некоторыми типами. Практический совет: для production используйте Immer, который корректно работает с прототипами и Date, а structuredClone применяйте только для изолированных данных (cookies, clipboard). Вывод: immutable-обновление в React требует осознанного выбора метода копирования в зависимости от типов данных в стейте, а не слепого использования spread "на всякий случай".

😁 Пункта про стоимость и требуемые характеристики к железу не хватает ✖️ xCode Journal
😁 Пункта про стоимость и требуемые характеристики к железу не хватает ✖️ xCode Journal

Глубокая типизация GraphQL-ответов без codegen: generics и conditional types в TypeScript Codegen для GraphQL часто превращается в головную боль: настройка схемы, синхронизация версий, регенерация при каждом изменении. Но если вы используете Relay с фрагментами или работаете в микросервисе с часто меняющейся схемой, можно обойтись без codegen. Типичная ошибка — вручную писать типы для каждого запроса, что для вложенных структур с пагинацией превращается в копипасту. Почему codegen не всегда нужен Codegen добавляет слой абстракции, который требует CI/CD-интеграции и синхронизации с серверной схемой. В проектах с частыми изменениями или фрагментарной архитектурой Relay это может стать узким местом. Альтернатива — вывести типы из самого запроса через TypeScript. Подход: generic + conditional type Создаем маппер типов GraphQL на TypeScript и utility type, который рекурсивно "проходит" по структуре запроса, обнаруженной через as const. Этот тип заменяет строки на реальные типы из схемы, обрабатывая вложенность и массивы.
// Маппер типов схемы
type GraphQLTypeMap = {
  ID: string;
  String: string;
  User: { id: string; name: string; posts: Post[] };
  Post: { id: string; title: string; comments: Comment[] };
  Comment: { id: string; text: string; author: User };
};

// Рекурсивный conditional type
type UnwrapGraphQLResponse<T> = T extends { __typename: infer Name }
  ? T & GraphQLTypeMap[Name extends keyof GraphQLTypeMap ? Name : never]
  : T extends (infer U)[]
    ? UnwrapGraphQLResponse<U>[]
    : T;

// Запрос с as const
const query = {
  user: {
    id: true,
    name: true,
    posts: {
      title: true,
      comments: {
        text: true,
        author: { name: true }
      }
    }
  }
} as const;

// Тип ответа выводится автоматически
type Response = UnwrapGraphQLResponse<typeof query>;
Акцент на trade-offs Главный плюс — один дженерик на все запросы без codegen. Но это требует ручного поддержания GraphQLTypeMap, что для схем с 50+ типами становится трудоемким. Для union/interface типов conditional type может дать сбой — понадобится доплогика. Также метод не работает с динамическими selection set. Типичные ошибки * Забыть указать as const — без него TypeScript не зафиксирует структуру запроса. * Использовать при сложных интерфейсах: conditional type не всегда корректно обрабатывает discriminated unions — это потребует дополнительных проверок. * Не синхронизировать маппер: если схема меняется, типы рассинхронизируются, и вы получите ложные гарантии. Практический совет Применяйте этот подход для микросервисов с простой, стабильной схемой или когда codegen конфликтует с Relay-фрагментарной структурой. В production я так типизировал запросы в проекте, где схема менялась раз в неделю, а codegen падал из-за кэширования — это сэкономило время без потери безопасности типов. Вывод: Generics и conditional types — это легковесная альтернатива codegen для типизации вложенных GraphQL-ответов, но она требует ручного управления маппером и подходит для схем ограниченного размера.

AI-инструменты можно любить и ненавидеть, но работать без них в IT уже практически невозможно 🤩 Коллеги из AvitoTech 11 июля
AI-инструменты можно любить и ненавидеть, но работать без них в IT уже практически невозможно 🤩 Коллеги из AvitoTech 11 июля зовут в их офис на Лесной на AI Hardcore Day. Приглашают тех, кто каждый день сталкивается с AI в работе и даже пишет своих AI-агентов. Обещают доклады и нетворкинг-сессию на террасе после — и всё это без записи. 💫 Среди тем: — Spec-Driven Development: теория, инструменты, практика. — Разработка и тестирование MCP для внутренних агентных систем аналитики. — Выпрямляем руки агентов: как сделать MCP удобными и действительно полезными. — Атаки на GenAI-агентов: OWASP на практике. ➡ Регистрация тут!

Типизация forwarded refs в React 19: баги с ref как prop, инфер типов в HOC и render-props с useImperativeHandle React 19 сделал ref обычным prop, что на первый взгляд упрощает API. Однако на практике разработчики часто сталкиваются с неочевидными багами типов, особенно в production-коде с обёртками. Основная ошибка — игнорировать явную типизацию в угоду кажущейся простоте. 1. ref как prop без forwardRef — неочевидный баг Если передать ref как обычный prop без forwardRef, TypeScript не поймёт, что это forwarded ref:
function MyInput({ ref, ...props }: { ref: React.Ref<HTMLInputElement> }) {
  return <input ref={ref} />;
}
TypeScript выдаст ошибку, так как не видит контекст Refs. Решение — оборачивать в forwardRef и явно указывать generic:
const MyInput = forwardRef<HTMLInputElement, Props>((props, ref) => ...);
Это гарантирует корректную инференцию типов и совместимость с React 19. 2. Инфер типов в HOC с forwarded refs — теряется связь Когда оборачиваешь компонент с ref в HOC, типы часто ломаются. Типичная ошибка: забыть явно пробросить generic для ref:
function withLogger<T extends object>(
  Component: React.ForwardRefExoticComponent<T & { ref: React.Ref<HTMLDivElement> }>
) {
  return forwardRef<HTMLDivElement, T>((props, ref) => {
    console.log('Rendered');
    return <Component {...props} ref={ref} />;
  });
}
Без явного ForwardRefExoticComponent TypeScript будет гадать, выведя неправильный тип ref. Практический совет: всегда указывай generic для forwarded ref в HOC. 3. Render-props с useImperativeHandle — неправильный тип экземпляра useImperativeHandle в React 19 работает с forwarded refs, но типизация render-props проваливается, если не объявить интерфейс явно:
interface ImperativeActions { focus: () => void; }

const Input = forwardRef<ImperativeActions, {}>((_, ref) => {
  useImperativeHandle(ref, () => ({ focus: () => console.log('Focus') }));
  return <input />;
});

function Outer({ children }: { children: (ref: React.RefObject<ImperativeActions>) => React.ReactNode }) {
  const ref = useRef<ImperativeActions>(null);
  return <>{children(ref)}</>;
}
Частая ошибка: TypeScript выводит ImperativeActions как any. Предупреждение: используй React.ElementRef<typeof Input> вместо ручного объявления, чтобы избежать рассинхронизации типов. Вывод: В React 19 явная типизация forwarded refs с generic-типами в каждом слое — не опция, а необходимость для production-кода, иначе баги с инференцией ломают компоненты с HOC и render-props.