Суть Читай реальные сниппеты React/TS, предсказывай баг формы и выбирай фикс с наибольшим рычагом — derived state, normalized vs вложенно и colocation.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги формы прячутся в коде, который компилируется и рендерится нормально — пока не сработает путь обновления, который он забыл покрыть. Прочитай каждый сниппет, найди второй источник истины или неправильно размещённый state, затем выбери фикс, который сеньор делает первым.
Цель
Отработай цикл, который ты прогоняешь, ревьюя код state: прочитай компонент, заметь значение, которое надо выводить, или данные с неправильной для их паттерна доступа формой, и тянись к структурному фиксу до любой библиотеки.
Heads-up useCallback мемоизирует идентичность функции; он не решает реальную проблему — хранение значения, которое можно вычислить. reduce место в рендере, а не за state.
Heads-up items — это проп; копирование его в локальный state добавит ещё один источник истины, который разойдётся с родителем. Фикс — меньше хранимых значений, а не больше.
Heads-up Синхронизация derived state эффектом — задокументированный антипаттерн: рендер сначала с устаревшим значением, затем ещё раз после эффекта. Вывод в рендере убирает и окно устаревания, и лишний рендер.
Сниппет 2 — вложенно vs normalized
// state.users[].posts[] — user встроен и в поле author каждого postfunction renameUser(state, id: string, name: string) { return { ...state, users: state.users.map(u => u.id === id ? { ...u, name } : u), // posts по-прежнему несут post.author = {...старый user...} };}
Викторина
Completed
Переименование обновляет массив users, но список постов всё ещё показывает старое имя. Какое изменение формы — настоящий фикс?
Heads-up Это патчит симптом и сохраняет дублирование — каждому будущему меняющемуся полю нужен тот же fan-out, и один пропущенный путь снова разойдётся. Удаление встроенных копий — устойчивый фикс.
Heads-up Заморозка предотвращает мутацию, а не устаревание — замороженный post.author всё ещё держит старое имя. Проблема в дублированных данных, что устраняет normalization.
Heads-up Это мутируемые на клиенте сущности, а не server cache для рефетча. Библиотека кэша не чинит клиентскую форму, дублирующую одного user по многим постам; это делает byId-словарь.
Сниппет 3 — поднятый, слишком широкий state
// store приложенияconst useStore = create((set) => ({ mouseX: 0, mouseY: 0, setMouse: (x, y) => set({ mouseX: x, mouseY: y }),}));function Dashboard() { const { mouseX, mouseY } = useStore(); // подписка на весь store return <ExpensiveCharts /* ререндер на каждом mousemove */ />;}
Викторина
Completed
Почему движение мыши роняет фреймрейт этого дашборда и какой фикс?
Heads-up Библиотека store — не узкое место; узкое место — слишком широкая подписка. Компонент, читающий весь store, ререндерится на любое изменение; фикс — более узкая область, а не другая библиотека.
Heads-up memo помогает, только если пропсы стабильны, но сам Dashboard ререндерится на каждом mousemove, потому что подписан на меняющийся store. Сначала почини область подписки; memo — пластырь.
Heads-up Позиция мыши — эфемерное, покадровое UI-состояние, никогда не шарящееся view-состояние. URL бы забил историю. Это надо колоцировать в одном компоненте, который его читает.
Сниппет 4 — серверные данные как клиентский state
function Orders() { const [orders, setOrders] = useState<Order[]>([]); useEffect(() => { fetch('/api/orders').then(r => r.json()).then(setOrders); }, []); // также рендерится в соседнем <OrdersSummary/> со своим идентичным fetch}
Викторина
Completed
Два компонента прогоняют один и тот же паттерн fetch-в-useState. Какой набор проблем создаёт форма и какой фикс?
Heads-up Флаг загрузки — наименьшая часть. Проблема формы в том, что server cache трактуется как клиентский state, что также стоит дедупа, общего кэша и инвалидации — всё это библиотека кэша даёт сразу.
Heads-up Глобальный store дедуплицирует копию в памяти, но fetch, свежесть, рефетч и инвалидацию ты всё равно пишешь руками. Данные — server cache; библиотека кэша с ключом по запросу делает всё это.
Heads-up Идентичные эндпоинты всё равно означают два сетевых запроса, две копии кэша и независимую свежесть — ровно тот drift и расход, что устраняет библиотека server cache.
Итог
Каждый баг формы выше читается прямо из кода: эффект, синхронизирующий значение, которое можно вывести (вычисляй в рендере), авторы, встроенные в посты, так что переименование устаревает (нормализуй в byId + join), высокочастотный state, поднятый в глобальный store, ререндерящий весь мир (колоцируй или возьми срез селектором), и fetch, затянутый в useState, который должен быть server cache (используй библиотеку кэша с ключом по запросу). Прочитай компонент, найди второй источник истины или state, размещённый не там, где его читатели, и почини форму до того, как тянуться к библиотеке.