Браузер и фронтенд-рантайм
Bailout, мемоизация и tearing
Компонент, обёрнутый в React.memo, всё равно рендерится при каждом рендере родителя. Ничего не изменилось. Memo на месте. Открываете DevTools Profiler — причина говорит «props changed», но prop — это объект с идентичным содержимым. Проблема не в memo. Проблема — инлайновый объектный литерал как prop: каждый рендер новая ссылка, и Object.is всегда сообщает об изменении.
Bailout: как React пропускает работу. Не каждый компонент в перерендеривающемся поддереве действительно перезапускается. Когда React достигает fiber во время render, он проверяет: изменились ли props (через Object.is на каждом prop)? Изменилось ли state? Есть ли pending-обновление контекста, которое потребляет этот fiber? Если ничего из этого не верно, React делает bailout — клонирует fiber из alternate, переиспользует всё существующее дочернее поддерево по ссылке и вообще не вызывает функцию компонента.
Именно поэтому ссылочная стабильность так важна: дочерний компонент, обёрнутый в React.memo, делает bailout только если его props ссылочно равны. Передайте инлайновый {} или стрелочную функцию как prop — и bailout не будет работать при каждом рендере, потому что свежий литерал никогда не равен предыдущему через Object.is. useMemo и useCallback существуют именно для сохранения этих ссылок, чтобы bailout мог сработать.
Ментальная модель: рендеринг родителя не автоматически перерендеривает дочерние компоненты — он предлагает им шанс сделать bailout, а стабильные ссылки — это то, что позволяет им этим шансом воспользоваться.
{} или () => {} как prop → новая ссылка каждый рендер → bailout никогда не срабатывает, даже с React.memoState хранится на fiber, а не в компоненте. Функциональный компонент не имеет экземпляра — это просто функция, которую React вызывает. Так где же useState хранит значение между рендерами? На fiber. Каждый fiber держит связный список записей хуков — по одной на каждый вызов useState/useReducer/useEffect, в порядке вызова. React продвигает cursor через этот список, пока ваш компонент вызывает хуки.
Вот глубинная причина Правил Хуков: хуки должны вызываться безусловно и в одном и том же порядке при каждом рендере, потому что React идентифицирует каждый хук исключительно по его позиции в последовательности вызовов, а не по имени. Поместите useState под if — и при рендере, где условие переключилось, каждый последующий хук читает неправильную запись. Это также объясняет сохранение состояния: state переживает рендер, потому что fiber переживает; state уничтожается при уничтожении fiber — при размонтировании, при смене типа или при смене ключа.
Почему это работает
Почему хуки не именованные? Записи хуков могли бы храниться по имени вместо позиции, устраняя требование порядка. Но имена должны быть уникальными в компоненте, превращая каждый вызов хука в lookup по строковому ключу вместо продвижения cursor. Lookup по позиции — O(1) на хук и не требует дополнительного рантайма. Трейдофф: обязательный порядок вызовов в обмен на минимальные накладные расходы на рендер.
Tearing и useSyncExternalStore. Прерываемый рендеринг вводит опасность под названием tearing: если внешний store меняется между двумя слайсами, компоненты первого слайса видели старое значение, а компоненты позднего слайса — новое, поэтому закоммиченный UI внутренне несогласован — «разорван». Собственное состояние React не может рваться, потому что React контролирует, когда оно меняется; внешние stores могут, потому что React этим не управляет.
useSyncExternalStore исправляет это: он даёт React функцию subscribe и функцию getSnapshot. React читает снимок, и если обнаруживает, что снимок изменился во время конкурентного рендера, перезапускает рендер, гарантируя, что каждый компонент в одном commit наблюдал одно и то же значение store. Именно поэтому любая библиотека состояния, интегрирующаяся с React 18 (Redux, Zustand, Jotai), маршрутизирует через useSyncExternalStore.
Контекст и каскад перерендеривания. Provider контекста рядом с корнем дерева, чьё значение — свежий объектный литерал при каждом рендере, вызывает перерендеривание всего поддерева потребителей. Потребители контекста перерендериваются при каждом изменении значения провайдера по ссылке — а свежий литерал всегда отличается. Исправление: мемоизировать значение контекста через useMemo, чтобы его ссылка была стабильной, когда содержимое не меняется. Это наиболее частая причина перформанс-проблем в реальных React-приложениях, обнаруживаемая Profiler-ом как массовый перерендеринг несвязанных компонентов.
Дашборд медленно перерендеривается при каждом нажатии клавиши в несвязанном поле поиска. React Profiler показывает почти каждый компонент на странице перерендеривающимся — даже те, чьи props не изменились. Где проблема?
Компонент, обёрнутый в `React.memo`, всё равно рендерится каждый раз, когда рендерится родитель. Props выглядят неизменёнными. Какова наиболее вероятная причина?
Почему нельзя прочитать обновлённое значение state синхронно сразу после вызова `setState`?
Какой API React существует специально для предотвращения 'tearing' — когда компоненты в одном commit наблюдают разные значения внешнего store во время конкурентного рендера?
- 01Компонент обёрнут в React.memo, но рендерится при каждом рендере родителя. Опишите диагностику и исправление.
- 02Почему хуки должны вызываться в одном и том же порядке при каждом рендере?
- 03Что такое tearing и как useSyncExternalStore предотвращает его?
React делает bailout для компонента, когда Object.is находит все его props и state неизменёнными — клонирует fiber и полностью пропускает вызов функции. React.memo делает bailout условным на props; useMemo и useCallback сохраняют стабильные ссылки, позволяющие bailout срабатывать. Хуки хранятся как позиционный связный список на fiber, именно поэтому порядок вызовов должен быть безусловным — пропуск хука разрушает cursor для каждого последующего. State живёт на fiber, а не в функции компонента, поэтому state сохраняется между рендерами и исчезает при размонтировании, смене типа или смене ключа. В конкурентном режиме внешние stores могут вызывать tearing — компоненты в одном commit видят разные значения — потому что store может измениться между слайсами рендера. useSyncExternalStore обнаруживает это и перезапускает рендер, поэтому каждая серьёзная библиотека состояния React маршрутизирует через него.
встречается в143
- Почему GraphQL получает N+1junior
- Механика DataLoader: батчинг на границе тикаmiddle
- Контракты batch-функции: порядок, формы, ошибкиmiddle
- Federation и lookahead: батчинг за пределами DataLoadermiddle
- Защита сложности запросов: depth, cost, persisted queriesmiddle
- Senior GraphQL API: scheduling-контракт, изоляция арендаторов, наблюдаемостьsenior
- Зачем идемпотентность: безопасные retryjunior
- Серверный state machine: четыре состояния idempotency keymiddle
- Outbox и inbox: effectively-once через dual-write границуmiddle
- Конкурентность и архитектура кеша для идемпотентности на масштабеsenior
- Наблюдаемость, production-инциденты и дизайн для глобального масштабаsenior
- Что такое cache stampede и почему он делает всё хужеjunior
- Лок и single-flight: ограничение параллельных rebuildmiddle
- XFetch: вероятностное раннее истечение без координацииmiddle
- Stale-while-revalidate и CDN request coalescingmiddle
- Детектирование stampede и дизайн TTL для продакшенаmiddle
- Метастабильный сбой, fencing-токены и production-постмортемыsenior
- Что такое отношение: таблицы, строки, ключи и ограниченияjunior
- Ограничения, ключи и типы данных Postgresmiddle
- Нормальные формы, денормализация и почему схемы «прилипают»middle
- JSONB, массивы и когда side table побеждаетmiddle
- Heap-хранилище, TOAST и выравнивание колонокsenior
- Целостность схемы: deferral, версионирование и сбои в продакшнеsenior
- Реляционная модель vs документные, wide-column, граф и key-valuesenior
- Index-only scan, Visibility Map и INCLUDEsenior
- Типичные сбои в продакшне и аудит индексовsenior
- pg_statistic, ANALYZE и производственная наблюдаемостьmiddle
- Производственные режимы отказа и стабильность плановsenior
- MVCC: как Postgres раздаёт согласованные снимкиjunior
- Заголовок tuple и механика снимковmiddle
- HOT-обновления и уровни изоляцииmiddle
- VACUUM, bloat и autovacuummiddle
- CLOG, XID wraparound и MultiXactsenior
- SSI и production-тюнинг autovacuumsenior
- Реальные провалы MVCC, deployment-паттерны и распределённые снимкиsenior
- Connection pool: зачем амортизировать стоимость backend Postgresjunior
- Режимы PgBouncer: session, transaction и statementmiddle
- Размер пула: формула (ядра × 2) + шпинделей и двухуровневый стекmiddle
- Исчерпание пула и idle-in-transaction: сценарий отказа в 3 ночиmiddle
- Миграция на transaction mode: план развёртывания и prepared statements в PgBouncer 1.21middle
- Процессная модель Postgres и почему увеличение max_connections снижает производительностьsenior
- Ландшафт пулеров 2026, serverless connection storms и полная таксономия отказовsenior
- Что такое миграция схемы и почему она заменяет ad-hoc DDLjunior
- ADD COLUMN: мгновенно в PG 11+ против перезаписи в старом Postgresjunior
- Режим отказа очереди блокировок: почему мгновенный DDL может заморозить базуmiddle
- Безопасные DDL-паттерны: NOT VALID, CONCURRENTLY и исправления небезопасных операцийmiddle
- Expand-contract: нулевой простой для ломающих изменений схемыmiddle
- Advisory-блокировки, инструменты миграций и координация деплояsenior
- Таксономия сбоев миграций и дисциплина продакшнаsenior
- Зачем нужно шардирование: потолок одного Postgresjunior
- Выбор ключа шарда: стратегии hash, range, list и directorymiddle
- Партиционирование против шардирования: одно слово, два разных понятияmiddle
- Ко-локация и Citus: инвариант, делающий шардирование пригодным к использованиюmiddle
- Режим отказа hot shard: обнаружение, изоляция и долгосрочная политикаmiddle
- Schema-based шардирование и альтернативы мультиарендностиsenior
- Онлайн-решардинг, 2PC и операционная стоимость шардированияsenior
- Семь актов: от CREATE TABLE до Citusjunior
- Акты 1–3 в глубину: схема, индексы и статистика планировщикаmiddle
- Акты 4–6 в глубину: MVCC bloat, connection pooling и безопасные миграцииmiddle
- Акт 7 в глубину: шардинг, co-location и семиуровневый каскад трейдоффовmiddle
- Наблюдаемость, антипаттерны и производственный триажsenior
- Роли Raft, term и почему majority-кворум предотвращает split brainjunior
- Как Raft реплицирует log entry и решает, что его безопасно коммититьmiddle
- Выборы лидера в Raft: таймауты, правила голосования и четыре свойства безопасностиmiddle
- Raft в реальном мире: partition, медленный диск и клиентская маршрутизацияmiddle
- Расширения Raft: pre-vote, learner, snapshot и линеаризуемые чтенияsenior
- Raft в production: membership change, Multi-Raft и observabilitysenior
- Где происходит data fetching — и почему это решает LCPjunior
- Fetch waterfall''''ы — диагностика и лечение через Promise.allmiddle
- React Server Components и Suspense streamingmiddle
- Клиентский кэш: TanStack Query, SWR и stale-while-revalidatemiddle
- LCP, prefetch и race conditions в интерактивном fetchingmiddle
- Senior internals: RSC payload, слои кэша и production паденияsenior
- Трёхстороннее рукопожатие TCPjunior
- Номера последовательности и состояние соединенияmiddle
- DNS: что делает и зачем существуетjunior
- Обход резолвера: перенаправления, типы записей и gluemiddle
- TTL, кеширование и распространение DNSmiddle
- Рукопожатие за 1 RTT: key share и ECDHEmiddle
- Возобновление сессии и 0-RTTmiddle
- WebSocket: HTTP-апгрейд до постоянного соединенияjunior
- Формат WebSocket-фрейма: opcodes, маскирование, фрагментацияmiddle
- Backpressure в WebSocket: когда клиенты не успеваютmiddle
- Реконнект: jittered backoff, thundering herd, восстановление сообщенийsenior
- WebSocket в масштабе: HTTP/2 мультиплексирование, permessage-deflate, C10Msenior
- WebSocket в production: прокси, безопасность и распределённая архитектураsenior
- Что делают обратные проксиjunior
- Health checks, connection draining и slow startmiddle
- Session affinity, consistent hashing и правильное решениеmiddle
- Retry-бури, circuit breakers и load sheddingsenior
- Устойчивая архитектура LB: anycast, zone-aware маршрутизация и observabilitysenior
- Почему QUIC, а не TCP+TLSjunior
- Connection ID и миграция сетиmiddle
- Возобновление 0-RTT и шифрование пакетовsenior
- DDoS: что это и почему работаетjunior
- Атаки усиления и истощение состоянияmiddle
- Ограничение скорости: алгоритмы и архитектураmiddle
- WAF, межсетевые экраны, mTLS и HSTSmiddle
- Отравление DNS-кэша и BGP-перехватsenior
- Эшелонированная защита и экономика атакsenior
- DNS, TCP, TLS по очереди: куда уходят миллисекундыmiddle
- Перехват прокси и шлюзы безопасности: rate limiter, WAF, mTLSmiddle
- Альтернативные пути: QUIC 0-RTT, WebSocket upgrade, миграция соединенияmiddle
- Наблюдаемость: распределённые трейсы, USE/RED и семплированиеsenior
- Устойчивость: каскадные повторы, circuit breakers и error budgetsenior
- Что такое три сигнала: метрики, логи, трейсыjunior
- Зачем нужны структурные логи: дневник против таблицыjunior
- Схема продакшн-лога: поля, которые несёт каждая строкаmiddle
- PII-редакция и log injectionsenior
- OTel Logs Data Model и audit-логи как подсистемаsenior
- SLI, SLO и error budget: надёжность в числахjunior
- Error budget policy, latency SLO и составные journeysmiddle
- Продакшн-отказы SLO, самонаблюдаемость, безопасность и общая картинаsenior
- Петля инцидента: от пейджера до постмортема до предотвращенияmiddle
- Cache lines и false sharing: когда параллелизм замедляет кодmiddle
- SIMD и data layout: AoS vs SoA и разница в 4–8xmiddle
- Cache-oblivious алгоритмы, PGO и production failuressenior
- GC в production: наблюдаемость, безопасность, edge cases и управление флотомsenior
- Batching: амортизируй фиксированную цену каждой операцииjunior
- Окно батчинга: размер и время ожиданияmiddle
- Batching в Kafka и Postgresmiddle
- io_uring и наблюдаемость пакетированияmiddle
- От Nagle до io_uring: эволюция пакетированияmiddle
- Backpressure, изоляция сбоев и безопасность батчей в продакшенеsenior
- CI enforcement и RUM: делаем бюджеты рабочимиmiddle
- V8 JIT-пайплайн, HTTP-приоритеты и безопасность bundlesenior
- Цикл performance: дисциплина, а не проектjunior
- Классификация и исправление: сопоставление family bottleneck с методамиmiddle
- Observability-стек и CI gates: ловить регрессии до выпускаmiddle
- От инцидента к enforcement: SLO burn до верифицированного исправления за 35 минутmiddle
- Культура, экономика и масштаб performancesenior
- At-most-once, at-least-once, exactly-once: три контракта доставкиjunior
- Три ножки сбоя — где реально происходят дубликаты и потериmiddle
- Consumer-side dedup: самый дешёвый путь к exactly-once processingmiddle
- Kafka exactly-once semantics: idempotent producer и транзакцииmiddle
- SQS visibility timeout, DLQ и outbox patternmiddle
- Exactly-once в production: impossibility-доказательство, гибридные паттерны и реальные инцидентыsenior
- Что такое OAuth и почему пароли — не ответjunior
- Authorization code flow с PKCEmiddle
- Валидация ID-токена и управление JWKS-кешемmiddle
- Ротация refresh-токенов и scope-based least privilegemiddle
- Sender-constrained токены: DPoP и mTLSsenior
- OAuth в production: audience атаки, observability и реальные провалыsenior